读完本篇,你可以了解到如何将组件库打包成各种格式
上一篇里提到了启动服务前会先进行一下组件库的打包,运行的命令为:
varlet-cli compile
显然是varlet-cli
提供的一个命令:
处理函数为compile
,接下来我们详细看一下这个函数都做了什么。
// varlet-cli/src/commands/compile.ts
export async function compile(cmd: {
noUmd: boolean }) {
process.env.NODE_ENV = 'compile'
await removeDir()
// ...
}
// varlet-cli/src/commands/compile.ts
export function removeDir() {
// ES_DIR:varlet-ui/es
// LIB_DIR:varlet-ui/lib
// HL_DIR:varlet-ui/highlight
// UMD_DIR:varlet-ui/umd
return Promise.all([remove(ES_DIR), remove(LIB_DIR), remove(HL_DIR), remove(UMD_DIR)])
}
首先设置了一下当前的环境变量,然后清空相关的输出目录。
// varlet-cli/src/commands/compile.ts
export async function compile(cmd: {
noUmd: boolean }) {
// ...
process.env.TARGET_MODULE = 'module'
await runTask('module', compileModule)
process.env.TARGET_MODULE = 'esm-bundle'
await runTask('esm bundle', () => compileModule('esm-bundle'))
process.env.TARGET_MODULE = 'commonjs'
await runTask('commonjs', () => compileModule('commonjs'))
process.env.TARGET_MODULE = 'umd'
!cmd.noUmd && (await runTask('umd', () => compileModule('umd')))
}
接下来依次打包了四种类型的产物,方法都是同一个compileModule
,这个方法后面会详细分析。
组件的基本组成
以Button
组件为例看一下未打包前的组件结构:
一个典型组件的构成主要是四个文件:
.less:样式
.vue:组件
index.ts:导出组件,提供组件注册方法
props.ts:组件的props定义
样式部分Varlet
使用的是less
语言,样式比较少的话会直接内联写到Vue
单文件的style
块中,否则会单独创建一个样式文件,比如图中的button.less
,每个组件除了引入自己本身的样式外,还会引入一些基本样式、其他组件的样式:
index.ts
文件用来导出组件,提供组件的注册方法:
props.ts
文件用来声明组件的props
类型:
有的组件没有使用.vue
,而是.tsx
,也有些组件会存在其他文件,比如有些组件就还存在一个provide.ts
文件,用于向子孙组件注入数据。
打包的整体流程
首先大致过一遍整体的打包流程,主要函数为compileModule
:
// varlet-cli/src/compiler/compileModule.ts
export async function compileModule(modules: 'umd' | 'commonjs' | 'esm-bundle' | boolean = false) {
if (modules === 'umd') {
// 打包umd格式
await compileUMD()
return
}
if (modules === 'esm-bundle') {
// 打包esm-bundle格式
await compileESMBundle()
return
}
// 打包commonjs和module格式
// 打包前设置一下环境变量
process.env.BABEL_MODULE = modules === 'commonjs' ? 'commonjs' : 'module'
// 输出目录
// ES_DIR:varlet-ui/es
// LIB_DIR:varlet-ui/lib
const dest = modules === 'commonjs' ? LIB_DIR : ES_DIR
// SRC_DIR:varlet-ui/src,直接将组件的源码目录复制到输出目录
await copy(SRC_DIR, dest)
// 读取输出目录
const moduleDir: string[] = await readdir(dest)
// 遍历打包每个组件
await Promise.all(
// 遍历每个组件目录
moduleDir.map((filename: string) => {
const file: string = resolve(dest, filename)
if (isDir(file)) {
// 在每个组件目录下新建两个样式入口文件
ensureFileSync(resolve(file, './style/index.js'))
ensureFileSync(resolve(file, './style/less.js'))
}
// 打包组件
return isDir(file) ? compileDir(file) : null
})
)
// 遍历varlet-ui/src/目录,找出所有存在['index.vue', 'index.tsx', 'index.ts', 'index.jsx', 'index.js']这些文件之一的目录
const publicDirs = await getPublicDirs()
// 生成整体的入口文件
await (modules === 'commonjs' ? compileCommonJSEntry(dest, publicDirs) : compileESEntry(dest, publicDirs))
}
umd
和esm-bundle
两种格式都会把所有内容都打包到一个文件,用的是Vite
提供的方法进行打包。
commonjs
和module
是单独打包每个组件,不会把所有组件的内容都打包到一起,Vite
没有提供这个能力,所以需要自行处理,具体操作为:
- 先把组件源码目录
varlet/src/
下的所有组件文件都复制到对应的输出目录下; - 然后在输出目录遍历每个组件目录:
- 创建两个样式的导出文件;
- 删除不需要的目录、文件(测试、示例、文档);
- 分别编译
Vue
单文件、ts
文件、less
文件;
- 全部打包完成后,遍历所有组件,动态生成整体的导出文件;
以compileESEntry
方法为例看一下整体导出文件的生成:
// varlet-cli/src/compiler/compileScript.ts
export async function compileESEntry(dir: string, publicDirs: string[]) {
const imports: string[] = []
const plugins: string[] = []
const constInternalComponents: string[] = []
const cssImports: string[] = []
const lessImports: string[] = []
const publicComponents: string[] = []
// 遍历组件目录名称
publicDirs.forEach((dirname: string) => {
// 连字符转驼峰式
const publicComponent = bigCamelize(dirname)
// 收集组件名称
publicComponents.push(publicComponent)
// 收集组件导入语句
imports.push(`import ${
publicComponent}, * as ${
publicComponent}Module from './${
dirname}'`)
// 收集内部组件导入语句
constInternalComponents.push(
`export const _${
publicComponent}Component = ${
publicComponent}Module._${
publicComponent}Component || {}`
)
// 收集插件注册语句
plugins.push(`${
publicComponent}.install && app.use(${
publicComponent})`)
// 收集样式导入语句
cssImports.push(`import './${
dirname}/style'`)
lessImports.push(`import './${
dirname}/style/less'`)
})
// 拼接组件注册方法
const install = `
function install(app) {
${
plugins.join('\n ')}
}
`
// 拼接导出入口index.js文件的内容,注意它是不包含样式的
const indexTemplate =