前言
在《手把手系列:打造企业级vue-hooks》已经介绍了开发hooks库的方法。本文将基于上文的vue-hooks架构做延伸,补充组件库建设知识,如果还没阅读上篇文章,建议先阅读。
本文会把焦点放在构建脚本设计上,因为这是组件库最复杂部分,如果你有看过element-plus或者vant-ui的构建脚本,会发现它需要引入大量插件和工具实现构建,但也许你是不需要这么复杂的构建流程的。
因此,为了弄清楚哪些构建流程是必要的,我们先从最简单的构建脚本开始,一步步增加功能。
设计构建脚本
下面由简入深分析构建脚本的设计,建议结合源码阅读
项目准备
我们先把上节课的vue-hooks
项目拷贝一份重命名为vue-components
并修改package.json
的name
属性改为@xboss/components
。
从最简单的开始
以往vue组件一般基于SFC
单文件的形式开发,也就是把template
、script
、css
放在同一个.vue
文件,但是这种方式需要构建工具支持。
现在我们希望不依赖构建工具,用纯js实现组件库开发,那可以利用vue提供了一个叫渲染函数的方法编写组件,因为渲染函数本身就是以js的方式直接创建虚拟DOM,所以不需要多余的构建过程。
下面是用render函数编写的组件代码
// src/toggle-button/index.ts
import { defineComponent, h, ref } from 'vue';
const toggleButton = defineComponent({
setup() {
const state = ref(false);
const toggle = () => {
state.value = !state.value;
};
return {
state,
toggle
};
},
// 定义渲染函数
render() {
// h函数用来创建VNode
return h('button', { onClick: this.toggle }, this.state);
}
});
export default toggleButton;
在项目入口以插件的形式导出install方法,那使用者只要执行app.use(组件库名)
即可全局注册组件
// src/index.ts
import { App } from 'vue';
import toggleButton from './toggle-button';
const install = (app: App) => {
// 注册全局组件
app.component('ToggleButton', toggleButton);
};
// 作为插件导出,暴露install方法
export default {
install
};
增加JSX/TSX语法支持
如果基于render函数创建复杂布局是非常麻烦的,Vue建议使用更接近模板语法的JSX语法,但是要支持该语法需要Babel插件支持。
另外,由于vscode无法识别jsx语法,会出现下图的波浪线
VSCode支持JSX语法
# 安装插件
npm i eslint-plugin-vue vue-eslint-parser -D
在.eslintrc
增加vue3语法检测配置
module.exports = {
parser: 'vue-eslint-parser',
parserOptions: {
parser: {
js: 'espree',
ts: '@typescript-eslint/parser',
'<template>': 'espree'
}
},
plugins: ['@typescript-eslint'],
// extends有顺序关系,规则按从左到右应用,假设把prettier放最左边,他的规则会被后面规则覆盖
extends: [
'airbnb-base',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-recommended',
'prettier'
],
rules: {
'import/prefer-default-export': 'off',
'import/extensions': 'off',
'import/no-unresolved': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off'
}
};
修改.tsconfig.json
新增"jsx": "preserve",
{
"compilerOptions": {
"jsx": "preserve",
"declaration": true,
"declarationDir": "dist/types"
}
}
还要注意把.ts
文件修改为.tsx
Rollup支持JSX
#安装插件
npm i rollup-plugin-esbuild rollup-plugin-vue-jsx-compat -D
构建脚本修改为下面,其中导出文件修改为esm和umd格式,cjs在组件库不适用
// rollup.config.js
import esBuild from 'rollup-plugin-esbuild';
import vueJsx from 'rollup-plugin-vue-jsx-compat';
export default {
// 入口文件
input: 'src/index.ts',
// 分别导出esm和umd
output: [
{
dir: 'dist/esm',
format: 'esm'
},
// umd格式放cdn提供给浏览器直接使用
{
dir: 'dist/umd',
format: 'umd',
name: 'xboss',
globals: {
vue: 'Vue'
}
}
],
plugins: [
vueJsx(),
esBuild({
jsxFactory: 'vueJsxCompat',
tsconfig: 'tsconfig.json'
})
],
external: ['vue']
};
增加模板语法和sass语法
#安装依赖
npm i rollup-plugin-vue rollup-plugin-postcss node-sass -D
修改rollup.config.js
调试组件
由于开发过程需要频繁修改代码调试,如果每次都把组件构建发布到npm会过于麻烦。npm提供了本地发布的方法,可以执行npm link
命令将组件发布到本地全局仓库,然后在项目执行npm link @xboss/components
方法和组件建立关联,当组件更新项目依赖会自动更新。
但是我在调试过程中发现该方法会出现Vue多次引用问题,而且解决起来会比较麻烦,所以我改为vitepress
,它除了可以调试组件,还可以构建组件文档。
# 安装vitepress
npm i vitepress -D
接着创建docs
目录并创建导航配置,这里要注意的是.vitepress/theme/index.js
文件,vitepress在该文件提供vue实例,可利用该实例注册组件。官方文档
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme';
import xboss from '../../../dist/esm/index';
export default {
...DefaultTheme,
enhanceApp({ app }) {
app.use(xboss);
}
};
使用组件方法和在template写法一样
docs/components/toggle-button.md
## 介绍
toggle-button 组件用于切换按钮状态
## 快速入门
<toggle-button></toggle-button>
package.json增加构建命令,执行npm run docs:dev
查看效果
"docs:dev": "vitepress dev docs"
按需加载
假如现在项目有三个组件,还是按以前方法注册组件,当应用使用组件库时,无论组件是否被调用,都会被打包到最终应用资源。
import { App } from 'vue';
import tag from './tag';
import toggleButton from './toggle-button';
import icon from './icon';
const install = (app: App) => {
// 注册全局组件
app.component('ToggleButton', toggleButton);
app.component('Icon', icon);
app.component('Tag', tag);
};
// 作为插件导出,暴露install方法
export default {
install
};
下面是我截图vant组件库注册单个组件方法,分别支持use和component方法注册
为了实现这两种方式注册,我们把toggle-button组件改成下面的导出写法
// src/toggle-button/index.ts
import { App } from 'vue';
import toggleButton from './ToggleButton.vue';
// 支持app.use()方法注册
export const ToggleButton = {
install: (app: App) => {
app.component(toggleButton.name, toggleButton);
}
};
// 支持app.component()注册
export default toggleButton;
另外,由于打包出去的是一个js文件,我们还要在入口处理所有组件的导出
// src/index.ts
import { App } from 'vue';
import tag, { Tag } from './tag';
import toggleButton, { ToggleButton } from './toggle-button';
import icon, { Icon } from './icon';
const components = [tag, toggleButton, icon];
// 全局注册
const install = (app: App) => {
components.forEach((component) => {
// 因为导入的是组件,可以用app.component方法注册
app.component(component.name, component);
});
};
// 分别导出组件,实现按需加载
export { Tag, ToggleButton, Icon };
export default {
install
};
现在当在应用导入单个组件,tree shaking会把未使用代码清除。
总结
大家看到最终的构建脚本几十行代码就能满足我们基本需求,但是我在研究组件库构建过程中最麻烦的是技术选型,因为实现同一功能有很多开源方案,而有些方案由于缺乏维护是无法使用的,你要在众多方案中挑选合适的方案,这是颇有挑战。