封装自己的Vue组件库

4,162 阅读7分钟

快速原型开发

VueCLI中提供了一个插件可以进行原型快速开发。 需要先安装一个全局的扩展

npm install @vue/cli-service-global -g

当组件开发完成之后可以运行vue serve来查看组件的运行效果

结合第三方组件库

除了可以从零开发自定义组件之外,还可以在第三方组件的基础之上,进行二次开发。

例如结合ElementUI来进行组件库的开发,则需要安装和配置,这里使用vue add element来进行安装,这个命令会自动安装babel及所依赖的插件,还会在package.json中生成对应的配置。

Monorepo

使用Monorepo的概念,来实现每个组件相互独立,而不是当只需要其中某个组件的时候,还要下载一个完整的包。
如果使用monorepo,代码中要有一个packages的文件夹,里面存放已经拆分出逻辑的各个组件的代码,每个组件相当于一个独立的模块,具有自己的package.json文件

StoryBook

StoryBook是一个可视化的组件展示平台,在隔离的开发环境中,以交互的方式展示组件。StoryBook在主程序之外运行,因此用户可以独立开发组件而不必担心应用程序特定的依赖关系。
使用StoryBook把组件开发完毕之后,可以快速的找到相应的组件。

关于安装

npx -p @storybook/cli sb init --type vue
yarn add vue
vue yarn add vue-loader vue-template-compiler --dev

安装完成之后,可以运行npm run storybook启动storybook,启动成功之后,会自动生成配置文件.storybook/main.js和实例代码文件夹stories,配置文件中设置了各个组件的路径(默认是示例代码的路径)。

yarn工作区的使用

在项目根目录的package.json文件中设置如下代码来开启工作区。

"private": true,
"workspaces": [
	"./packages/*"
]

其次给工作区根目录安装开发依赖

yarn add jest -D -W

给指定工作区安装依赖

yarn workspace kl-button add lodash@4

给所有工作区安装依赖

yarn install

Lerna

加入组件库开发完毕,要将其提交到github或者npm上,这个时候可以使用Lerna,可以方便将所有包统一发布。

Lerna是一个优化使用gitnpm管理夺宝仓库的工作流工具,用于管理具有多个包的JavaScript项目,它可以一键把代码提交到gitnpm仓库。

全局安装Lerna之后,使用lerna init来进行初始化lerna的配置。如果当前项目没有使用git进行管理的话,则会自动创建.git文件夹,当然,如果项目根目录下没有packages文件夹的话,也会自动创建出来。

关于发布,使用yarn lerna命令会自动把packages文件夹下的所有组件都发布到npm上。此时要注意可能组件名字会与当前已有的包重复,此时会提示相应信息,只需要改组件名字就好。还要注意此时的yarnnpm的源都要设置成初始的源,不要是淘宝的源,否则会报错。

关于单元测试

组件开发完毕,在发布之前,还需要进行单元测试的工作,使用单元测试的目的,是为了发现组件内部可能发生的错误。组件的单元测试,是使用单元测试工具,对组件的各种状态和行为进行测试,确保组件发布之后,在项目中使用组件不会导致程序出现错误。

使用单元测试的好处:

  • 提供描述组件行为的文档
  • 节省手动测试的时间
  • 减少研发新特性时产生的bug
  • 改进设计
  • 促进重构

特别要注意的是jest默认不支持ESM规范,所以使用import时是识别不了的,这里可以使用babel去处理

yarn add --dev babel-jest @babel/core @babel/preset-env babel-plugin-transform-es2015-modules-commonjs -W

之后配置babel.config.js

module.exports = {
    presets: [
        [
            "@babel/preset-env",
            {
                targets: {
                    node: "current"
                }
            }
        ]
    ],
    plugins: ["transform-es2015-modules-commonjs"]
};

还需要在jest.config.js中添加如下代码:

transformIgnorePatterns: ["<rootDir>/node_modules/(?!(lodash-es|other-es-lib))"]

之后再执行yarn jest就不会再有ESM的问题了

关于打包

相比webpack来说,打包组件库或者框架Rollup更方便一些。因为webpack需要自己配置Tree-shaking,即使配置了Tree-shaking,打包出来的结果也要比Rollup更加臃肿,而Rollup默认就支持Tree-shaking

安装依赖:

yarn add rollup rollup-plugin-terser rollup-plugin-vue@5.1.9 vue-template-compiler -D -W

配置文件rollup.config.js

import { terser } from 'rollup-plugin-terser'
import vue from 'rollup-plugin-vue'
module.exports = [
    {
        input: 'index.js',
        output: [
            {
                file: 'dist/index.js',
                format: 'es'
            }
        ],
        plugins: [
            vue({
                // Dynamically inject css as a <style> tag
                css: true,
                // Explicitly convert template to render function
                compileTemplate: true
            }),
            terser()
        ]
    }
]

之后在package.json中添加scripts

// 注意 此时的package.json并不是根目录下的,而是每个组件自己的package.json
"build": "rollup -c"

运行yarn workspace kl-button run build就会在kl-button下生成打包之后的文件。这个命令的意思是使用yarn运行名为kl-button的工作区下的build命令,即刚在kl-button文件夹下的package.json中添加的build命令。

但是这一单独的打包一个组件也太繁琐了些,使用yarn的一些插件可以实现将所有的组件一起打包。

yarn add @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve -D -W

其中@rollup/plugin-json可以让rollupjson文件作为模块加载,@rollup/plugin-node-resolve可以将依赖的第三方包打包进来。
之后在项目根目录下创建rollup的配置文件rollup.config.js文件

import fs from 'fs'
import path from 'path'
import json from '@rollup/plugin-json'
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'

const isDev = process.env.NODE_ENV !== 'production'

// 公共插件配置
const plugins = [
    vue({
        // Dynamically inject css as a <style> tag
        css: true,
        // Explicitly convert template to render function
        compileTemplate: true
    }),
    json(),
    nodeResolve(),
    postcss({
        // 把 css 插入到 style 中
        // inject: true,
        // 把 css 放到和js同一目录
        extract: true
    })
]

// 如果不是开发环境,开启压缩
isDev || plugins.push(terser())

// packages 文件夹路径
const root = path.resolve(__dirname, 'packages')

module.exports = fs.readdirSync(root)
    // 过滤,只保留文件夹
    .filter(item => fs.statSync(path.resolve(root, item)).isDirectory())
    // 为每一个文件夹创建对应的配置
    .map(item => {
        const pkg = require(path.resolve(root, item, 'package.json'))
        return {
            input: path.resolve(root, item, 'index.js'),
            output: [
                {
                    exports: 'auto',
                    file: path.resolve(root, item, pkg.main),
                    format: 'cjs'
                },
                {
                    exports: 'auto',
                    file: path.join(root, item, pkg.module),
                    format: 'es'
                },
            ],
            plugins: plugins
        }
    })

然后在根目录的package.json文件中配置脚本

"build": "rollup -c"

之后在每个组件对应的package.json中配置:

"main": "dist/cjs/index.js",
"module": "dist/es/index.js",

main是组件打包的出口,也是使用时的入口,而module则是存放依赖的第三方包的位置。
配置完成之后在根目录下运行yarn build命令,就会自动打包所有packages下的组件。

关于清理

node_modules的删除可以使用lerna原本的命令lerna clean,而清理dist文件夹则需要使用第三方库来实现,此处使用rimraf:

yarn add rimraf -D -W

之后给每个组件的package.json配置一个命令:

"del": "rimraf dist"

之后运行yarn workspaces run del即可运行所有包下的del命令,把dist目录删除掉,然而每次新建一个组件都要创建相同的文件是不可取的。

使用模板创建组件

因为现在组件的结构已经稳定,即每个组件中文件结构基本是一致的,所以可以抽取一个模板,后续需要时就可以通过plop基于模板快速生成一个新的组件。

yarn add plop -W -D

添加模板文件(在源码中的plop-template文件夹下,内容过多此处不再展示),之后配置plop的配置文件plopfile.js:

module.exports = plop => {
    plop.setGenerator('component', {
        description: 'create a custom component',
        prompts: [
            {
                type: 'input',
                name: 'name',
                message: 'component name',
                default: 'MyComponent'
            }
        ],
        actions: [
            {
                type: 'add',
                path: 'packages/{{name}}/src/{{name}}.vue',
                templateFile: 'plop-template/component/src/component.hbs'
            },
            {
                type: 'add',
                path: 'packages/{{name}}/__tests__/{{name}}.test.js',
                templateFile: 'plop-template/component/__tests__/component.test.hbs'
            },
            {
                type: 'add',
                path: 'packages/{{name}}/stories/{{name}}.stories.js',
                templateFile: 'plop-template/component/stories/component.stories.hbs'
            },
            {
                type: 'add',
                path: 'packages/{{name}}/index.js',
                templateFile: 'plop-template/component/index.hbs'
            },
            {
                type: 'add',
                path: 'packages/{{name}}/LICENSE',
                templateFile: 'plop-template/component/LICENSE'
            },
            {
                type: 'add',
                path: 'packages/{{name}}/package.json',
                templateFile: 'plop-template/component/package.hbs'
            },
            {
                type: 'add',
                path: 'packages/{{name}}/README.md',
                templateFile: 'plop-template/component/README.hbs'
            }
        ]
    })
}

之后运行yarn plop就会自动运行创建组件的逻辑,当然组件名字还是需要我们自己输入的。
源码直通车