总结我司组件库项目的基础搭建部分
就是指在一个大的项目仓库中,管理多个模块/包(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
- <div>
- <Button />
- div>
- <script lang="ts" setup>
- import { Button } from 'myown-ui'
- script>
不出意外的话你的页面就会展示我们刚刚写的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这些组件库的源码,借鉴下它们的设计思想。