手把手,如何搭建一个通用组件库?(文档+样式+按需打包
之前的文章# 手把手,如何搭建一个通用组件库,并发布到npm?搭建了一个简单的组件库框架,并发布到了npm。
在文章结果也留了几个个坑,
1.没有样式
2.文档系统也没有
3.组件也不能按需加载
我们今天来一一解决。
样式系统
搭建之前,先来看看element-plus的样式。
主要分为几个部分,公共样式,混入函数,每个组件对应的样式。
我看了其他几个库,也大概是这种模式。
先看一下var_scss文件。
通过map定义选项,然后通过map.get来获取对应的值,具体的样式,通过各种函数来进行计算。
大概思路有了,就来搭建我们自己的样式。
首先是全局样式,以及公用样式。
具体的组件样式跟函数,我放在组件同级目录中。
文档系统
文档系统用的是vitepress。
相比于老大哥vuepress。
vitepress跟vite兼容会更好一些,更轻量一些,但是毕竟比较新,可能坑也会多一些。大家可自行选择。
快速上手,我就不介绍了,官方有详细的说明。
安装好了之后,项目跑起来,就能看到一个简单的文档了。
现在我们就可以直接md在上面写vue语法了。
并不是很方便,对吧——button需要写两次
我们希望的是写一次,既有效果,又能展示代码片段。
所以,引入一个插件——vitepress-theme-demoblock。
markdown: {
config(md) {
// 这里可以使用markdown-it插件
md.use(demoBlockPlugin, {
scriptImports: ["import * as bubuUI from '@/index'"],
scriptReplaces: [
{
searchValue: /import ({.*}) from 'bubu-ui'/g,
replaceValue: (s, s1) => `const ${s1} = bubuUI`,
},
],
});
},
},
复制代码
scriptImports里面的配置是将doc文档中的import引入替换成本地文件,这样打包之后就不需要做修改了。开发时请求本地文件,打包后使用npm版本。
注意:vitepress-theme-demoblock插件暂不支持vue3.2.44以后得setup写法。需要使用defineComponent写法,且引入库需要使用单引号。
参考如下。
<template>
<bu-checkbox v-model="val" label="A"></bu-checkbox>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const val = ref(false);
return {
val,
};
},
});
</script>
复制代码
docs的详细请求,大家可以看一下官方文档。
我当时是直接拷贝了vitepress官网上的配置,然后修改侧边栏菜单。
配置好的文档长这样。
按需打包
我们使用ui库一般有2种方式。
一种是全局引入。
import BuBuUI from "bubu-ui"
app.use(BuBuUI)
复制代码
另一种是按需引入。
我们就只管用,不需要管引入的问题,比如我用bu-button,就自动到对应的包中去拿组件。
import BuButton from "bubu-ui/components/button"
app.use(BuButton)
复制代码
所以我们需要,把组件单独打包出来。
我们自己写一个脚本,通过vite的buid方法来构建。
import { defineConfig, build } from 'vite';
build({
build: {
// 配置跟vite.config.ts文件基本相同
}
})
复制代码
这个只是全局打包,还需要多走一步,就是遍历components文件夹的组件,找到index.ts入口文件,重新执行一遍build打包。
打包出来的文件如下,components中的就是单独打出来的组件包。
到这里,还有个问题,ts文件在src/components中,而打包出来的子组件,在components中。(如果打包的时候指定一样的目录,会相互覆盖)
我的解决方案就是写了个拷贝函数,直接把components下的组件,直接拷贝到src/components中。如果哪位大佬有比较优雅的方式,欢迎指正留言,感谢感谢。
// 如果目标文件夹不存在,创建目标文件夹
if (!fse.existsSync(destPath)) {
fse.mkdirSync(destPath);
}
let files;
// 读取源文件夹下的所有文件和子文件夹
try {
files = fse.readdirSync(srcPath);
} catch (err) {
return;
}
// 遍历所有文件和子文件夹
files.forEach((file) => {
// 拼接文件路径
const srcFilePath = join(srcPath, file);
const destFilePath = join(destPath, file);
// 如果是文件夹,递归复制文件夹
if (fse.statSync(srcFilePath).isDirectory()) {
copyFolder(srcFilePath, destFilePath);
} else {
// 如果是文件,复制文件
fse.copyFileSync(srcFilePath, destFilePath);
}
});
复制代码
现在单独的组件包也有了,还差最后一步。
按需引入
安装unplugin-vue-components,这也是element-plus用的方案。
配置替换规则。
Components({
dts: true,
resolvers: [
(componentName) => {
if (componentName.startsWith('Bu')) {
return { name: componentName, from: `bubu-ui/src/components/${componentName.slice(2)}` }
}
}
]
})
复制代码
作用就是,如果有组件是以Bu开头的,就从ui库的src/components下引入组件。然后通过vue的components方法注册组件。
<template>
<BuButton type="primary">nihao</BuButton>
</template>
复制代码
以上的组件使用方式,就相当于如下写法
<template>
<BuButton type="primary">nihao</BuButton>
</template>
<script lang="ts">
import { defineComponent } from "vue"
import { BuButton } from "bubu-ui/src/components/button"
defineComponent({
components: {
BuButton
}
})
</script>
复制代码
总结
到这里,一个通用的组件库基本算是搭建完成了,也能发布上线使用了。
但是要真正封装出一个好用的ui组件库,还是有很多细节需要实现。
举几个例子
1.element-plus支持手动按需导入
<template>
<ElButton type="primary">nihao</ElButton>
</template>
<script lang="ts">
import { ElButton } from "element-plus"
</script>
复制代码
他的入口文件,并没有把每个组件都打包进去。
这个有大佬知道怎么弄的,也欢迎联系我,感谢。
2.组件开发过程中的相互引用
在组件开发过程中,如果存在组件相互引用,那么我们最好的方式是开发的时候引用本地,打包上线的时候引用的是对应的模块。
这个就涉及到包模块管理了。
3.element-plus的类型封装
element支持如下2种使用方式,又能当插件,又能当组件。
// 1
import { ElButton } from "element-plus"
app.use(ElButton)
// 2
import { ElButton } from "element-plus"
app.component(ElButton)
复制代码
在element-plus中,在组件上添加了一个install方法,导出的均为组件本身。
先到这吧。
后续如果有新的感悟,或者对架构有新的思考再分享。