总结我司组件库项目的基础搭建部分
就是指在一个大的项目仓库中,管理多个模块/包(package),这种类型的项目大都在项目根目录下有一个packages文件夹,分多个项目管理。大概结构如下:
-- packages
-- pkg1
--package.json
-- pkg2
--package.json
--package.json
简单来说就是单仓库 多项目。
使用pnpm
npm install pnpm -g && pnpm init
接下就是pnpm如何实现monorepo的了。
为了我们各个项目之间能够互相引用我们要新建一个pnpm-workspace.yaml文件将我们的包关联起来
packages:
- 'packages/**'
- 'examples'
这样就能将我们项目下的packages目录和examples目录关联起来了,当然如果你想关联更多目录你只需要往里面添加即可。根据上面的目录结构很显然你在根目录下新packages和examples文件夹,packages文件夹存放我们开发的包,examples用来调试我们的组件。examples里可以自行通过vue-cli创建vue3+vite项目来作为本地调试组件库的环境,很简单这里就不展开说了。
接下来就是要往我们的packages文件夹冲填充内容了。
一般packages要有utils包来存放我们公共方法,工具函数等
既然它是一个包,所以我们新建utils目录后就需要初始化它,让它变成一个包;终端进入utils文件夹执行:pnpm init 然后会生成一个package.json文件;这里需要改一下包名,我这里将name改成比如@myown-ui/utils表示这个utils包是属于myown-ui这个组织下的。所以记住发布之前要登录npm新建一个组织;例如myown-ui。
{
"name": "@myown-ui/utils",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
components是我们用来存放各种UI组件的包
新建components文件夹并执行 pnpm init 生成package.json
{
"name": "myown-ui",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
进入components文件夹执行
pnpm install @myown-ui/utils
你会发现pnpm会自动创建个软链接直接指向我们的utils包;此时components下的packages:
{
"name": "myown-ui",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@myown-ui/utils": "workspace:^1.0.1"
}
}
你会发现它的依赖@myown-ui/utils对应的版本为:workspace:^1.0.0;因为pnpm是由workspace管理的,所以有一个前缀workspace可以指向utils下的工作空间从而方便本地调试各个包直接的关联引用。
到这里基本开发方法我们已经知道啦;接下来就要进入正题了,开发一个button组件
在components文件夹下新建src,同时在src下新建button组件目录,在button目录下新建button.vue和index.ts。此时文件目录如下
-- components
-- src
-- button
-- button.vue
-- index.ts
-- package.json
在button/index.ts将button.vue导出
import Button from './button.vue'
export default Button
因为我们开发组件库的时候不可能只有button,所以我们需要一个components/index.ts将我们开发的组件一个个的集中导出
import Button from './button'
export {
Button
}
好了,一个组件的大体目录差不多就是这样了,接下来请进入我们的examples来看看能否引入我们的button组件
上面已经说过执行在workspace执行 pnpm i xxx的时候pnpm会自动创建个软链接直接指向我们的xxx包。
所以这里我们直接在examples执行:pnpm i myown-ui
此时你就会发现packages.json的依赖多了个
"myown-ui": "workspace:^1.0.0"
这时候我们就能直接在我们的测试项目下引入我们本地的components组件库了,启动我们的测试项目,来到我们的 examples/src/app.vue 直接引入Button
不出意外的话你的页面就会展示我们刚刚写的button组件了
很多时候我们在vue中使用一个组件会用的app.use 将组件挂载到全局。比如这样
import { createApp } from 'vue'
import { Button } from 'myown-ui'
const app = createApp(App)
app.use(Button)
app.mount('#app')
要使用app.use函数的话我们需要让我们的每个组件都提供一个install方法,app.use()的时候就会调用这个方法;
我们将button/index.ts调整为
import button from './button.vue'
const withInstall = (comp) => {
comp.install = (app) => {
//注册组件
app.component(comp.name, comp)
}
return comp
}
const Button = withInstall(button)
export default Button
此时我们就可以使用app.use来挂载我们的组件啦
在使用组件时我们也会一次性的把组件库的组件都注册了。比如这样
import { createApp } from 'vue'
import MyownUi from 'myown-ui'
const app = createApp(App)
app.use(MyownUi)
app.mount('#app')
在我们上面给每个组件都挂载了一个install方法的前提下,全局的组件注册其实不难,就是套多个循环而已。
在components/index.ts下引入所有组件并导出install方法
import * as components from './src/index'
export * from './src/index'
export default {
install: (app) => {
for (const comkey in components) {
app.component(components[comkey].name, components[comkey])
}
}
}
其实withInstall方法可以做个公共方法放到工具库里,因为后续每个组件都会用到,这里等后面开发组件的时候再调整
到这里组件开发的基本配置已经完成,最后我们对我们的组件库以及工具库进行打包,打包之前如果要发公共包的话记得将我们的各个包的协议改为MIT开源协议
...
"license": "MIT",
...
打包们这里选择vite,它有一个库模式专门为我们来打包这种库组件的。
前面已经安装过vite了,所以这里直接在components下直接新建vite.config.ts(配置参数文件中已经注释):
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig(
{
build: {
target: 'modules',
//打包文件目录
outDir: "es",
//压缩
minify: false,
//css分离
//cssCodeSplit: true,
rollupOptions: {
//忽略打包vue文件
external: ['vue'],
input: ['src/index.ts'],
output: [
{
format: 'es',
//不用打包成.es.js,这里我们想把它打包成.js
entryFileNames: '[name].js',
//让打包目录和我们目录对应
preserveModules: true,
//配置打包根目录
dir: 'es',
preserveModulesRoot: 'src'
},
{
format: 'cjs',
entryFileNames: '[name].js',
//让打包目录和我们目录对应
preserveModules: true,
//配置打包根目录
dir: 'lib',
preserveModulesRoot: 'src'
}
]
},
lib: {
entry: './index.ts',
formats: ['es', 'cjs']
}
},
plugins: [
vue()
]
}
)
这里我们选择打包cjs(CommonJS)和esm(ESModule)两种形式。
其实到这里就已经可以直接打包了;components下执行: pnpm run build你就会发现打包了es和lib两个目录,但是打包的组件只能给js项目使用,在ts项目下运行会出现一些错误,而且使用的时候还会失去代码提示功能,这样的话我们就失去了用ts开发组件库的意义了。所以我们需要在打包的库里加入声明文件(.d.ts)。
那么如何向打包后的库里加入声明文件呢? 其实很简单,只需要引入vite-plugin-dts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"
import dts from 'vite-plugin-dts'
export default defineConfig(
{
build: {...},
plugins: [
vue(),
dts({
//指定使用的tsconfig.json为我们整个项目根目录下掉,如果不配置,你也可以在components下新建tsconfig.json
tsConfigFilePath: '../../tsconfig.json'
}),
//因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个
dts({
outputDir:'lib',
tsConfigFilePath: '../../tsconfig.json'
})
]
}
)
因为这个插件默认打包到es下,我们想让lib目录下也生成声明文件需要再配置一个dts插件,暂时没有想到其它更好的处理方法~
然后执行打包命令你就会发现你的es和lib下就有了声明文件
其实后面就可以进行发布了,发布之前更改一下我们components下的package.json如下:
{
...
"main": "lib/index.js",
"module":"es/index.js",
"files": [
"es",
"lib"
],
...
}
解释一下里面部分字段
module
我们组件库默认入口文件是传统的CommonJS模块,但是如果你的环境支持ESModule的话,构建工具会优先使用我们的module入口
files
files是指我们需要发布到npm上的目录,因为不可能components下的所有目录都被发布上去
其实npm发包是很容易的,发布之前记得到npm官网注册个账户,如果你要发布@xx/xx这种包的话需要在npm新建个组织组织组织名就是@后面的,注册后npm login然后npm publish,再次发布的话记得更改版本号。
其实也可以写个脚本自动化打包和发布,这里就不展开说了。
其实开发一个组件库并不难,把基础的架构设计搭好了,剩下的组件更多的是业务功能上的开发。也可以去看看Antd、Element这些组件库的源码,借鉴下它们的设计思想。