作为一个前端人,如果可以拥有一个自己的组件库真的非常有成就感!!!
这是我上星期刚手撸的一个组件库-JAUNS-UI,组件较少,主要是为了学习,感兴趣的掘友们可移步体验。
pnpm + monorepo 环境搭建
今天通过 pnpm
和 monorepo
项目模式来手撸一个组件库。
安装 pnpm
npm i pnpm -g
开始搭建项目环境
新建一个项目文件夹,执行初始化操作,会发现多了 package.json
文件
pnpm init
增加配置项,根目录下新建文件 .npmrc
,写入如下配置
shamefully-hoist = true
为什么要这么配置呢?
- 提升依赖到根级别:允许在npm安装时跨范围提升依赖(所有依赖都安装在项目的根
node_modules
目录下),这可能会导致某些依赖版本的不一致。
比如你根目录下有两个目录 A
和 B
, A
和 B
都安装了 lodash
依赖,配置了这个之后,根目录的 node_modules
也会出现 lodash
依赖,这种配置适合我们现有的场景,多个包使用相同的依赖,如果多个包使用相同的依赖但版本却不一样就会出问题,不能这么配置
接下来我们来新建文件 pnpm-workspace.yaml
其作用是定义 monorepo
中的工作区范围和配置
packages:
- packages/*
- play
- docs
以上我们就是定义了三个目录
packages
存放组件包的代码play
测试组件库运行的前端项目docs
组件库的文档
如果不配置pnpm-workspace.yaml
文件
- 依赖项不共享:每个包之间的依赖都是独自安装,当多个包有相同依赖时,不能共享,导致依赖项冗余
- 版本一致性:不同包之间相同依赖的版本不一致,导致不一样的行为
所以通过 pnpm
结合配置实现性能的优化处理
开发组件
接下来我们就可以直接开始开发组件了,因本例子组件库是通过vite + vue + ts
来搭建,先安装一下相关的依赖最为全局依赖,这样我们工作区域内的包就无需重复安装
pnpm i vue @vitejs/plugin-vue typescript vite vue-tsc -D -w
其中 -w
代表在根目录下安装依赖
基础配置完成之后,我们来开始开发组件
在 packages
目录下新建 components
文件夹,执行初始化 pnpm init
,并修改 package.json
文件的包名 name
和入口文件main
。
// package.json
{
"name": "@test-ui/components",
"version": "0.0.1",
"description": "",
"main": "index.ts",
"peerDependencies": {
"vue": "^3.4.35"
},
"keywords": [],
"author": "",
"license": "MIT"
}
以 button
组件为例,创建如下目录
packages/components
├─ src
├─ button
├─ src
└─ button.vue // 组件代码
└─ index.ts // 用于导出button组件
└─ index.ts // 集中导出src下的所有组件
我们来写一下 button
组件,并默认导出组件
// button.vue
<template>
<button>按钮组件</button>
</template>
<script lang='ts' setup>
defineOptions({
name: 'TeButton'
})
</script>
// button/index.ts
import TeButton from './src/button.vue'
export {
TeButton
}
// index.ts
export * from './button'
基本的 button
组件就已经开发完成了,我们来测试一下
测试组件
首先切换到 play
目录,我们快速创建一个 vite + vue + ts
的项目
npm create vite@latest
接着我们通过 -w
将我们的私有包安装到根目录下
pnpm i @test-ui/components -w
发现根目录的 package.json
的依赖中多了 "@test-ui/components": "workspace:^"
,其中的 workspace
代表安装的是工作区的依赖包,因为在执行pnpm
安装的时候,pnpm
会优先在工作区的目录下找对应的依赖,如果找到了则会建立对应的软连接。
我们在 play
目录新建的项目中的 App.vue
来引入对应组件
<script setup lang="ts">
import { TeButton } from '@test-ui/components'
</script>
<template>
<te-button></te-button>
</template>
运行效果如下
哇!!! 成功了欸
这样一个简单的组件就完成了!
全局引入组件库
假设我们需要全局使用组件库的话怎么办?
我们在组件包里就需要提供一个install
的方法来全部引入。
// packages/components/index.ts
import { App } from 'vue'
import { TeButton } from './button'
const components = [
TeButton
]
export default {
install: (app: App) => {
for (const c of components) {
app.component(c.name, c)
}
}
}
在 play
目录的项目中的 main.ts
中全局引入,app
页面中不单独引入按钮组件
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import testUi from '@test-ui/components' // 新增
createApp(App).use(testUi).mount('#app') // 变更
// App.vue
<script setup lang="ts">
</script>
<template>
<te-button></te-button>
</template>
看看效果
全局引入成功了!
组件打包
我们使用vite
来打包,首先在 packages/components
目录下创建个vite
的配置文件 vite.config.ts
,如下
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
build: {
rollupOptions: {
external: ["vue"], // 忽略打包vue文件
output: [
{
format: 'es',
entryFileNames: '[name].js',
dir: './dist',
preserveModulesRoot: 'src'
}
]
},
lib: {
entry: "./index.ts"
},
},
plugins: [vue()],
})
打包我们的入口文件 index.ts
在 package.json
文件中增加脚本执行命令
// package.json
"scripts": {
"build": "vite build"
}
执行命令 pnpm build
可以发现多了一个dist
的目录,里面有一个 index.js
的文件,现在我们来测试一下打包的文件是否有效
我们来更改当前包的入口文件为打包后的文件如下
// package.json
"main": "dist/index.js"
然后看看 play
目录下运行的项目是否正常
哇,效果一样,代表打包成功咯!
npm 发布
npm 发布就很简单,切换到packages/components
目录,配置需要发布到npm上的包的内容,增加如下配置,只发布 dist
里的内容
// package.json
"files": [
"dist"
],
其中package.json
中的 name
就是代表的包名,即你 pnpm install [name]
的 name
npm login
npm publish
执行以上命令,npm login
先获取npm账号的认证,然后就可以发布!
总结
这是一个最简单的一个组件库方案,欠缺了很多细节处理,业内中的组件库会涉及到的细节很多,比如全局引入组件,按需引入组件,样式的打包等等。
如果要了解完整的组件库的方案,可以看看 element plus
的源码,我自己开发的 组件库-JAUNS-UI 也是参考了element plus
的源码来开发的,受益匪浅。