一、基础框架的建立
1、使用vue-cli脚手架创建一个基础的vue3.x+ts项目
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、效果:
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、效果:
四、组件库打包
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
即可打包为库文件
打包之后会生成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-…