从0到1,搭建一个VUE组件库维护的基本框架

一、基础框架的建立

1、使用vue-cli脚手架创建一个基础的vue3.x+ts项目

image-20210325102038520

2、项目目录结构整理

2.1、修改src目录为examples

examples目录作为所有示例页面的文件入口目录,同时清理文件夹中由vue-cli默认添加的一些无用的示例文件,

由于修改了原有的src目录,所以同时需要对vue.config.js进行修改,使得脚手架能够正确的启动项目:

module.exports = {
  pages: {
    examples: {
      // page 的入口
      entry: 'examples/main.ts',
      // 模板来源
      template: 'public/index.html',
      // 在 dist/index.html 的输出
      filename: 'index.html',
      // 当使用 title 选项时,
      // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
      title: 'Examples'
    }
  }
}
 

2.2、创建packages目录

packages目录包含组件库中所有的组件以及入口等

2.3、创建typings目录

在项目根路径下创建typing目录,并且将examples目录下的shims-vue.d.ts文件移动到当前目录下。

2.4、修改tsconfig.json文件

修改tsconfig.json文件中的include字段,将修改后的examples目录和packages目录包含在其中。同时typings目录也需要被添加到该配置中。

{
    "include": [
        "examples/**/*.ts",
        "examples/**/*.tsx",
        "examples/**/*.vue",
        "packages/**/*.ts",
        "packages/**/*.tsx",
        "packages/**/*.vue",
        "typings/**/*.ts"
    ]
}
 

2.5、添加一些风格规范和样式规范的配置文件

这个地方根据团队编码风格或个人编码风格设置,不作过多的赘述。

小结:

修改后整个项目目录结构大致如下:

.
├── examples
│   ├── App.vue
│   └── main.ts
├── packages
│   ├── index.ts
├── public
│   ├── favicon.ico
│   └── index.html
├── typings
│   └── shims-vue.d.ts
├── README.md 
├── babel.config.js
├── tsconfig.json
├── package.json
└── vue.config.js
 

二、组件的创建

1、组件A

在packages目录下创建目录comp-a并且在该文件夹下创建src/comp-a.vue文件和index.ts文件

comp-a.vue文件代码如下

<template>
  <div style="width: 200px; background-color: red">comp-a</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'CompA'
})
</script>

 

index.ts作为组件的入口,目的是为了导出组件并且添加install方法

import { App } from 'vue'

import CompA from './src/comp-a.vue'

// 为组件添加注册方法
CompA.install = (app: App): void => {
  app.component(CompA.name, CompA)
}

export default CompA

 

2、组件B

组件B的创建与组件A同理,不做过多赘述,只展示代码

comp-b.vue

<template>
  <div class="comp-b">comp-b</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'CompB'
})
</script>

<style lang="scss">
.comp-b {
  width: 200px;
  background-color: green;
}
</style>

 

index.ts

import { App } from 'vue'

import CompB from './src/comp-b.vue'

// 为组件添加注册方法
CompB.install = (app: App): void => {
  app.component(CompB.name, CompB)
}

export default CompB

 

3、组件入口

在packages/index.ts文件中编写代码,作为整个组件库的导出统一导出目录

import { App } from 'vue'

import CompA from './comp-a'
import CompB from './comp-b'

const components = [CompA, CompB]

// 注册所有的组件
const install = function (app: App): void {
  components.forEach((component) => {
    app.component(component.name, component)
  })
}

// 导出注册方法
export default {
  install
}

// 导出 单个组件
export { CompA, CompB }

 

三、组件的使用

1、全局使用

1.1、在examples/main.ts中导入组件,通过app.use注册所有组件

import { createApp } from 'vue'
import App from './App.vue'

import Components from '../packages'

const examples = createApp(App)
examples.use(Components)
examples.mount('#app')

export default examples

 

1.2、在页面中使用:

<template>
  <div id="#app">
    <comp-a />
    <comp-b />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'App'
})
</script>

 

1.3、效果:

image-20210325114602000

2、在局部组件中引用

2.1、删除main.ts中对全局的引用

2.2、在页面中导入组件并且注册

<template>
  <div id="#app">
    <comp-a />
    <comp-b />
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

import { CompA, CompB } from '../packages'

export default defineComponent({
  name: 'App',
  components: { CompA, CompB }
})
</script>

 

2.3、效果:

image-20210325115053753

四、组件库打包

1、vue-cli打包

vue-cli工具提供了将vue打包为lib库的命令详细信息:cli.vuejs.org/zh/guide/bu…

在package.json的script字段下增加一条命令:

{
  "scripts": {
    "build:lib": "vue-cli-service build --target lib ./packages/index.ts"
  }
}
 

通过命令行执行

yarn build:lib
 

npm run build:lib
 

即可打包为库文件

image-20210325141310767

image-20210325141040896

打包之后会生成cjs、umd格式的库,demo.html文件描述了该如何在html界面中使用这个库。

2、rollup打包

vue-cli打包之后仅仅生成了cjs和umd格式的库,对于一个组件库而言,可能还需要使用esm格式的库,如果在typescript项目中使用,或许还需要.d.ts类型声明文件。vue-cli打包并没有为我们提供这些支持,所以需要使用到rollup,使得我们的库能够得到更好的支持

2.1、安装rollup依赖

  • rollup 打包工具
  • rollup-plugin-terser 代码压缩工具
  • plugin-node-resolve 第三方工具模块定位插件
  • rollup-plugin-typescript 处理typescript文件
  • rollup-plugin-vue vue处理插件
  • rollup-plugin-css-only 处理样式(本文中未用到)
npm install -D rollup rollup-plugin-terser @rollup/plugin-node-resolve rollup-plugin-typescript2 rollup-plugin-vue rollup-plugin-css-only
 

yarn add -D rollup rollup-plugin-terser @rollup/plugin-node-resolve rollup-plugin-typescript2 rollup-plugin-vue rollup-plugin-css-only
 

2.2、ESM-bundle

  • 在项目跟目录下创建build文件夹,用于存放打包需要的脚本文件
  • 在build文件夹下创建rollup.config.esm-bundle.js,使用rollup打包出具有所有组件的esm模块,以及各个组件的类型声明文件

rollup.config.esm-bundle.js配置文件:

/* eslint-disable */
import path from 'path'
import pkg from '../package.json'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import vue from 'rollup-plugin-vue'
import typescript from 'rollup-plugin-typescript2'

const deps = Object.keys(pkg.dependencies || {})

export default [
  {
    input: path.resolve(__dirname, '../packages/index.ts'), //入口
    output: {
      format: 'es',
      file: 'lib/index.esm.js'
    },
    plugins: [
      terser(),
      nodeResolve(),
      vue({
        target: 'browser', // 服务端渲染使用 'node'
        css: false,
        exposeFilename: false
      }),
      typescript({
        tsconfigOverride: {
          compilerOptions: {
            declaration: true
          },
          include: ['packages/**/*', 'typings/vue-shim.d.ts'],
          exclude: ['node_modules']
        },
        abortOnError: false
      })
    ], // 插件
    external(id) {
      return /^vue/.test(id) || deps.some((k) => new RegExp('^' + k).test(id))
    }
  }
]

 
  • 在package.json文件中添加脚本,用于打包项目
{
  "scripts": {
    "build:esm-bundle": "rollup --config ./build/rollup.config.esm-bundle.js"
  }
}
 

2.3、ESM

在上面已经创建了关于esm-bundle的一个全量模块,但是组件库大多数时候仍需要实现按需加载,所以需要针对各个单独的小组件进行打包,由于我们规定了组件的入口文件为index.ts,所以打包即便的更为简单,rollup.config.esm.js

/* eslint-disable */
import path from 'path'
import pkg from '../package.json'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import vue from 'rollup-plugin-vue'
import typescript from 'rollup-plugin-typescript2'

const deps = Object.keys(pkg.dependencies || {})

import fs from 'fs'

const readdirectory = (dir) => { //递归目录结构
  const list = fs.readdirSync(dir)
  const ret = []
  list.forEach((filename) => {
    const dist = path.resolve(dir, filename)
    const stat = fs.statSync(dist)
    if (stat.isFile()) {
      if (filename === 'index.ts') {
        ret.push(dist)
      }
    } else {
      ret.push(...readdirectory(dist))
    }
  })
  return ret
}

const indexlist = readdirectory(path.resolve(__dirname, '../packages')) // 读取所有的入口文件

const configs = indexlist
  .filter((dist) => dist.indexOf('packages\index.ts') === -1) // 忽略packages/index.ts 只编译子模块
  .map((dist) => ({
    input: dist, //入口
    output: {
      format: 'es',
      file: dist.replace('packages', 'lib').replace('.ts', '.js') // 设置输出目录
    },
    plugins: [
      terser(),
      nodeResolve(),
      vue({
        target: 'browser', // 服务端渲染使用 'node'
        css: false,
        exposeFilename: false
      }),
      typescript({
        tsconfigOverride: {
          compilerOptions: {
            declaration: false // 编译模块时不输出类型声明
          },
          include: ['packages/**/*', 'typings/vue-shim.d.ts'],
          exclude: ['node_modules']
        },
        abortOnError: false
      })
    ], // 插件
    external(id) {
      return /^vue/.test(id) || deps.some((k) => new RegExp('^' + k).test(id))
    }
  }))

export default configs

 
  • 添加package.json中的脚本
{
  "scripts": {
    "build:esm": "rollup --config ./build/rollup.config.esm.js"
  }
}
 

2.4、UMD

  • UMD格式脚本与esm格式基本相同,只不过需要指定output.name属性
/* eslint-disable */
import path from 'path'
import pkg from '../package.json'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import vue from 'rollup-plugin-vue'
import typescript from 'rollup-plugin-typescript2'

const deps = Object.keys(pkg.dependencies || {})

export default [
  {
    input: path.resolve(__dirname, '../packages/index.ts'), //入口
    output: {
      format: 'umd', // umd格式
      file: 'lib/index.umd.js', // 输出文件
      name: pkg.name // 指定name
    },
    plugins: [
      terser(),
      nodeResolve(),
      vue({
        target: 'browser', // 服务端渲染使用 'node'
        css: false,
        exposeFilename: false
      }),
      typescript({
        tsconfigOverride: {
          compilerOptions: {
            declaration: true
          },
          include: ['packages/**/*', 'typings/vue-shim.d.ts'],
          exclude: ['node_modules']
        },
        abortOnError: false
      })
    ], // 插件
    external(id) {
      return /^vue/.test(id) || deps.some((k) => new RegExp('^' + k).test(id))
    }
  }
]

 

2.5、组合打包

  • 在packages中添加脚本,一次性打包出所有的库文件,而不必每次依次执行各个打包命令
{
    "scripts": {
        "build": "yarn build:esm && yarn build:esm-bundle && yarn build:umd",
        "build:esm-bundle": "rollup --config ./build/rollup.config.esm-bundle.js",
        "build:esm": "rollup --config ./build/rollup.config.esm.js",
        "build:umd": "rollup --config ./build/rollup.config.umd.js",
    }
}
 

五、发布

1、npm账号

1.1、注册账号,注册地址: www.npmjs.com/signup (已有账号)请忽略这一步

1.2、登录,通过命令行执行登录

npm login
 

登录成功检查:

npm whoami
 

2、修改package.json

2.1、设置private字段为false, 为true时 npm拒绝发布

{
    "private": false
}
 

2.2、设置入口以及类型声明

{
    "main": "lib/index.umd.js",
    "module": "lib/index.esm.js",
    "typings": "lib/index.d.ts"
}
 

3、发布

3.1、设置版本号

修改package.json中的version字段,保证该字段与已经发布的字段不重复

3.2、发布

执行命令:(发布前确保当前分支已经时一个干净的分支,即不含有未提交的文件)

npm publish
 

总结

至此,从0到1搭建一个组件库的基本框架便已经完成,其中还有诸多细节需要处理,如:

  • css、scss等样式文件的处理
  • 工具和组件的分离
  • 使用文档库

思考:vue-cli的脚手架实际上已经使用了webpack,同时能够打包出umd格式的库文件,为什么还要使用rollup来进行打包?

正如rollup官方文档中所提到的,rollup对于es具有良好的支持,同时其强大的tree-shaking功能能够极大程度的压缩代码,通过对比发现,使用vue-cli打包的umd.min.js和使用rollup打包的umd.js文件相比较,vue-cli命令打包的文件大小为10,772 字节,而使用rollup打包的文件大小仅为963 字节,其中的优势想不再需要作过多的解释

这一片文章谨献给想要维护一个自己的组件库,但是无从下手的初学者,同时更多深层次的内容仍需要继续探索。处于工作中的各位,将来势必会走到维护公司内部的业务组件库的地步,看过这篇文章,不至于真正需要用到的时候手足无措。更多细节敬请批评指正。

最后附上整个过程中所涉及到的代码:

GitHub: gitee.com/AloneH/vue-…

Gitee: gitee.com/AloneH/vue-…

(1)
上一篇 2021年3月27日 上午11:36
下一篇 2021年3月27日 上午11:51

相关推荐

发表回复

登录后才能评论