2021 / 08 / 19

基于typescript的vue工具组件库开发

本文字数: 7203阅读时间: 18分钟

## 设计

目录结构

项目基于webpack4 + vue2.6 + typescript,因为最终可能需要向下兼容到老项目或者其他项目,所以还是需要编译成es5。 整个项目的结构是这样的

项目目录[ 项目目录 ]

project目录是用来测试组件能否正常使用,而src目录下才是组件库的文件,因为project目录没有必要参与打包但是也需要用webpack启动项目做测试,所以根据这两个目录分别做了不同的webapack的配置区分打包📦,减小体积。

webpack配置

基于project的webpack配置

const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
    entry: [
        "./src/main.ts",
        "file-loader?name=index.html!./src/index.html",
    ],
    output: {
        path: `${__dirname}/dist`,
        filename: 'bundle.js',
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    module: {
        rules: [
            { test: /\.vue$/, use: 'vue-loader' },
            { test: /\.scss/, use: ["style-loader", "css-loader", "less-loader",] },
            { test: /\.ts$/, loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/], transpileOnly: true } }
        ],
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
    devServer: {
        disableHostCheck: true,
    }
}

基于component的webpack.config

const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
    entry: [
        './src/index.ts',
    ],
    output: {
        path: `${__dirname}/lib`,
        filename: 'index.js',
        libraryTarget: 'commonjs',
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    module: {
        rules: [
            { test: /\.vue$/, use: 'vue-loader' },
            { test: /\.less/, use: ["style-loader", "css-loader", "less-loader", "sass-loader"] },
            {
                test: /\.ts$/, loader: 'ts-loader', options: {
                    appendTsSuffixTo: [/\.vue$/],
                    compilerOptions: {
                        declaration: true,
                        declarationDir: "./lib/types",
                    }
                }
            }
        ],
    },
    externals: [
        // include only relative assets
        function (context, request, callback) {
            if (!request.match(/(?:^|!)(?:\.|\.\.)?\//))
                return callback(null, `commonjs ${request}`)
            callback()
        }
    ],
    plugins: [
        new VueLoaderPlugin(),
    ],
}

在package.json加入对应的script

package.json

// ...
"scripts": {  
  "test": "echo \"Error: no test specified\" && exit 1",  
  "prepare": "webpack --mode production",  
  "project": "webpack --context project --config ./project/webpack.config.js --mode development",  
  "project:serve": "webpack-dev-server --hot --progress --context project --config ./project/webpack.config.js --mode development"  
},

tsconfig配置

这样,项目的webpack配置基本就完成了,除了配置webpack之外,还需要针对开发的组件做好兼容性的准备,所以两个目录下的tsconfig也是不一样的,开发的组件为了兼容js的项目,需要转码成es5的代码执行,这里直接展示出对应的tsconfig和自己使用的tslint。

/project/tsconfig.json

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization": false,
    "declaration": true,
    "declarationDir": "types",
    "strict": true,
    "target": "es5",
    "importHelpers": true,
    "noUnusedLocals": true,
    "sourceMap": true,
    "lib": [
      "dom",
      "es5",
      "es6",
      "es7",
      "es2015.promise"
    ]
  },
  "include": [
    "src/**/*.ts"
  ]
}

./tsconfig.json //根目录下

{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "outDir": "./built/",
    "strict": true,
    "importHelpers": true,
    "experimentalDecorators": true,
    "noImplicitReturns": true,
    "moduleResolution": "node",
    "strictPropertyInitialization": false,
    "noImplicitThis": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true
  },
  "include": [
    "./src/**/*"
  ],
  "lib": [
    "dom",
    "es5",
    "es6",
    "es7",
    "es2015.promise"
  ],
  "typeRoots": [
    "./node_modules/vue/types",
    "./src/types/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

tslint.json

{
  "defaultSeverity": "warning",
  "extends": [
    "tslint:recommended"
  ],
  "compilerOptions": {
    "experimentalDecorators": true,
    "allowJs": true
  },
  "linterOptions": {
    "exclude": [
      "node_modules/**",
      "src/components/**"
    ]
  },
  "rules": {
    "quotemark": [true, "single"],
    "indent": [true, "spaces", 2],
    "interface-name": false,
    "ordered-imports": false,
    "object-literal-sort-keys": false,
    "no-consecutive-blank-lines": false,
    "semicolon": ["none"],
    "no-debugger": false,
    "no-console": false,
    "trailing-comma": ["error", "never"],
    "member-access": false,
    "array-type": [false]
  }
}

vue基础项目配置

因为开发组件过程中肯定需要一个基础的项目来测试组件能否正常使用,所以在/project目录下我们新建一个基础的vu项目。

/project/src/App.vue

<template>
    <div>hello world!</div>
</template>
<script lang="ts">
    import {
        Vue,
        Component
    } from 'vue-property-decorator'
    @Component({})
    export default class App extends Vue {}
</script>
<style lang="scss">
    * {  
      margin: 0;  
      padding: 0;  
    }
</style>

/project/src/index.html

<!doctype html>
<html lang="zh-cn">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>example</title>
    </head>
    
    <body>
        <div id="app"></div>
        <script src="./bundle.js"></script>
    </body>

</html>

/project/src/main.ts

import Vue, * as vue from 'vue'
import App from './App.vue'

const Sample: vue.Component = App

window.addEventListener('load', () => {
    new Vue({
        render(h: vue.CreateElement) {
            return h(Sample)
        }
    }).$mount('#app');
})

/project/src/types.d.ts

declare module "*.vue" {  
  import Vue from 'vue'  
  export default Vue  
}

这样,一个最基础的Vue项目就配置成功了,可以通过yarn project:serve来启动项目查看写好的组件是否能够成功使用.

组件开发

同样的,组件开发也需要一个整体的入口和声明文件,因为我们编译后的代码是压缩过的es5的代码,没有可读性,所以也需要写好对应声明文件来供给实际的项目中使用它。

整体的目录结构是这样的:

组件-文件目录[ 组件-文件目录 ]

在入口文件这里,需要引用所有的组件,并且写好install的方法,供给Vue调用,这里的install的方法是有规定的写法和配置的,为了能让组件的配置定制化和成功导入Vue。

index.ts

import xxx from './components/xxx' // 写好的组件
import Vue, {
    PluginObject
} from 'vue'
import {
    version
} from '../package.json' // 获取版本

interface ICustomWindow {
    Vue: typeof Vue
}
declare let window: ICustomWindow

const components: {
    [key: string]: PluginObject < {} >
} = {
    xxx
}

export const install = (vue: typeof Vue, options = {}) => { // 把所有的组件导入
    Object.keys(components).forEach((key: string) => {
        vue.use(components[key])
    })
}

if (typeof window !== 'undefined' && window.Vue) { // 防止Vue没有注入window 
    install(window.Vue)
}
export {
    version,
    VOssImg
}

// 导入分为全量导入和按需加载
export default {
    install,
    version,
    ...components
}

这样,入口文件就写好了,组件的话需要写好install方法供给我们入口文件来调用。具体怎么写可以参考:用ts开发一个vue的组件

更多阅读