哈喽大家好,我是广州小井。最近分享了一篇关于Vue3组件库打包的文章,其中有提到我将组件代码跟样式代码进行了分离,这样为了更好的组织样式,根源解决组合组件样式引入的问题。具体是怎么做的?听我慢慢道来~
前情回顾
其中相关联的文章有以下,详细了解可以点进去看看:
这里我就简单地介绍一下之前的情况。其实做组件库的打包升级、拆分CSS样式代码 的目的就是为了解决之前通过 unplugin
类插件实现组件自动按需引入 的问题,这一点上一篇文章也多次强调了。为了让不清楚的同学了解到当前的问题,我还是再次简单地描述一下问题所在,当然详细的话看 组件库实战------按需加载工程化 这篇文章会有更好的理解。
回到正题,之前为了推广组件库的落地使用,我开始往组件库的生态建设 上做了点事情,目的就是为了让其他的开发者更低成本、更轻松地接入组件库到目前的项目中。于是我就着手在 unplugin-vue-components 的基础上扩展了一个自己组件库的 resolver
函数来实现自动按需引入。
最后的作用可以达到以下代码块的效果:
html
<template>
<div>
<vc-table />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
如上代码块,我们直接可以使用 vc-
开头的组件,并且无需手动引入其组件和样式代码。当我们不使用该类插件时,我们需要这样做才能达到正常使用的效果:
html
<template>
<div>
<vc-table />
</div>
</template>
<script>
// 引入组件
import { VcTable } from '@xxx/vc-element-plus'
// 引入样式
import 'element-plus/es/components/table/style/css'
import '@xxx/vc-element-plus/es/components/table/style/css'
export default {
name: 'App',
components: {
VcTable // 注册组件
}
}
</script>
如上所示,当我们要按需使用 一个 vc-
类组件的时候,要手动引入 vc组件
、element-plus的样式代码
、vc组件的样式代码
,还要注册组件,非常的繁琐。如果说你需要向使用了 unplgin
做自动按需引入组件和样式的团队推广你的组件库,不解决这个问题大概率是不会有人理你的。
所以之前我的解决方案很简单,写了个 resolver
函数:解析 Vc
开头的组件,引入 Vc的组件代码
和 element-plus的样式代码
。因为当时的组件数很少,并且都是基于 element
扩展的,所以这样做已经解决了当时 99% 的问题了。
但随着组件库的发展,组件数增多了,也有一些自己内部实现的组件了,上述的方案就行不通了。比如当我门的 vc-table
中还使用了 el-select
、el-tag
等其他组件时,上述方案的样式引入就有问题了,这也就是我老说的组合组件的样式引入问题。
光说不清晰,我们依然通过代码来演示。当 vc-table
组件组合使用了其他 element
组件时,即使使用了 unplugin
的自动导入功能,我们还需要手动导入其他样式。代码如下:
html
<template>
<div>
<vc-table />
</div>
</template>
<script>
import 'element-plus/es/components/select/style/css'
import 'element-plus/es/components/tag/style/css'
export default {
name: 'App'
}
</script>
没错,如果照这样发展,开发者的心智负担就非常重了。他得知道你的 vc-组件
中使用了什么其他的组合组件,并且每次使用都需要自动手动把 element
的样式引入进来...别说了,组件库做成这样别人肯定不会接的。
基于上面的种种问题,上一篇文章我们进行了组件库的代码和样式拆分,并且升级了打包方案,志在解决这个棘手的问题。接下来就来看看我是如何解决问题的吧。
丝滑按需引入的前置工作
前文铺垫了很长篇幅的前情回顾、问题介绍,目的就是为了让大家深刻感受到当前组件库发展遇到的问题。根本问题就是已经使用了自动按需引入插件的项目再接入我们组件库的时候成本会非常高。
当然,如果你说将 element
的组件和 vc-xx
的组件都全局引入不就完美解决了?没错,话是这么说,如果组件、样式都是全局引入的话就不会有这么多的问题了,直接一把梭哈。但不巧的是,我这边的项目 90% 的都是用了 unplgin 插件的自动按需引入方式来开发的。所以,你总不能要求每一个业务线的团队都按照你的要求来开发项目吧...(主要是没人愿意听)
所以,该下功夫的地方还是得下功夫,接下来直接开干吧。首先看看上一篇文章过后,我们组件库打包出来的样式代码是怎么样的了:
如上图所示,每个组件的样式都被打成了一个 css
文件了。所以我们只需要将每个样式,通过给组件中的一个索引文件去引入就好了。比如说 button
的组件,他的 style
目录下有一个 index.js
的索引文件。我们来看看这个文件怎么用:
如上图所示,可以看到第一行代码中引入了一个 scss
的文件。没错,这就是引入 vc-button
样式的地方。大家可能发现怎么是 scss
文件而不是 css
文件呢?其实这个上文也有讲到,主要是为了组件的开发环境中使用的,在打包阶段,我已经通过 rollup
插件对其进行了修改 ,最终输出的会是一个 css
的路径。(感兴趣的可以看上一篇组件的文章)
好了,这下大家可能也发现了,代码的第二行便是引入了 element
的样式了。也就是说我把 el-button
的样式代码也在这个索引文件中引用了。此时或许你已经明白,我是如何解决组合组件的样式引入问题的吧?
回到前情回顾中的一个场景,如果此时我的 vc-table
中使用了其他的 element
组件,那我们便可以通过这个索引文件进行统一引入。比如以下代码中:
js
// vc-table 的 style 目录下的 index.js 文件
import '@xxx/vc-element-plus/theme/table.scss'
import 'element-plus/es/components/table/style/css'
import 'element-plus/es/components/popover/style/css'
import 'element-plus/es/components/select/style/css'
import 'element-plus/es/components/tag/style/css'
...
如上代码可以看到,有了索引文件,我们便可以完成对所有组件样式的管理了,不管你要引入 popper
还是 tag
还是其他种种组件的样式都可以。另外,因为我把 vc-类
的组件样式都拆分了 ,所以即便我们是要使用 vc-类
的组件来组合,也一样不是问题了。比如我的 vc-table
要组合使用 vc-button
,索引文件便可以如下编写:
js
// vc-table 的 style 目录下的 index.js 文件
import '@xxx/vc-element-plus/theme/table.scss'
import '@xxx/vc-element-plus/theme/button.scss'
import 'element-plus/es/components/table/style/css'
...
好吧,这种用法非常灵活,我就不一一例举了,大家能get到点就行。大的实现方案定下来后,剩下的工作便是搬运工的角色了。我把整个组件库的所有组件的样式索引文件都做了个整理 ,让他们分别引入自己的样式代码 和依托的 element 的样式代码。
改造自动引入函数
因为前面我已经对 vc-类
组件的样式做了一个整理,所以组件库的所有组件都有一个唯一的样式索引文件 ,里面有他需要用到的所有样式文件的引入。所以接下来我们就要改造我们的 unplugin
插件中的 resolver
函数了,用回前文的图来说明。
-
之前他的作用是这样的:
-
改造后要变成这样:
好吧,其实这个实现比起我们 1.0
版本的 resolver
函数来得更容易(偷笑.jpg),因为实现起真的比之前还简单。之前为了无痛复用 element
的 sideEffects
(也就是样式代码),我还稍稍思考了2分钟,这次是压根不需要思考!
直接看看我的 resolver
函数 2.0
:
js
export const VcElementPlusResolver = () => {
return async(VcComponentName: string) => {
if (!VcComponentName.startsWith('Vc')) return
const componentName = kebabCase(VcComponentName.slice(2))
const result = {
name: VcComponentName,
from: "@xxx/vc-element-plus",
sideEffects: [
// 样式引入的核心在这里,直接引入组件的索引文件
`@xxx/vc-element-plus/es/${componentName}/style/index`
],
}
return result
}
}
如果你看过 组件库实战------按需加载工程化,那么肯定能感觉到 2.0
版本的代码跟实现更简单!一切都是依着 unplugin 的插件规范开发,在 sideEffects
属性中,我们不需要再复用 element
的样式索引文件了,因为我们已经有了自己的一个索引文件(也就是有注释的那一行)。这样一来,我们便可以比较丝滑地借助 unplugin
的能力,实现真正的按需、自动导入组件、样式代码的功能了。
如此一来,我们也顺利解决了之前一直遗留下来的痛点问题,业务方可以丝滑的接入了。比如看看一些实战的接入:
html
<template>
<vc-card header="制品">
制品
</vc-card>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Products'
})
</script>
啊,真的非常的丝滑,跟用原生的 element-plus
一样,这下开发者接入也没有任何的心智负担了。
一些配置和注意点
package.json的导出配置
为了使接入的时候一些 import 的路径更加优雅,大家可以按需配置这些吧,也不是必要的。不过配置的时候要注意,根据自己当前项目的打包结果来写配置关系,特别是一些产物的路径关系。
json
"exports": {
".": {
"import": "./dist/es/index.js",
"require": "./dist/lib/index.cjs"
},
"./es/*": "./dist/es/*.js",
"./theme/*": "./dist/theme/*"
},
如上配置,我更多是希望通过 /es
和 /theme
这种路径来引入。比如这样配置后,我就可以在业务项目中这样引入 vc-类
的样式文件。以 button
为例:
js
import '@xxx/vc-element-plus/theme/button.css'
如上配置,打包工具就会根据你的配置,到 dist/theme
下面去找 button.css
这个文件了。这一点看个人喜好吧,反正不是非必需的配置。
external的配置
因为做组件库,所以我们一般会把 element-plus
、vue
等库都 external
调,然后在 pkj
的 peerDependencies
声明这些库和版本,要求接入项目按要求安装。好吧,扯远了,接着回来,那 external
是要处理什么问题呢?
我们首先看看不另外配置 external
的情况下,打包出来的产物有什么问题。当前的 external
配置:
js
external: ['element-plus', 'vue', 'vue-router', '@element-plus/icons-vue']
这样打下来的产物基本都没问题,但是样式索引文件出现了一个现象:
好吧,目测是把原本引入的 element-plus
的路径给解析了,不是说好的 external
了吗。。。好吧,既然有问题,那就得解决,毕竟都走到最后一公里了。这样的样式索引文件,如果发包后在业务项目中使用是有问题的。当然,我的解决思路也很简单,只要保证我的索引文件中的 element-plus
的样式路径不被处理就行了。
也就是说我要保证这个样式索引文件在业务项目中也是这样引入 element-plus
的样式文件就行了,因为只要业务项目中按要求安装了对应版本的 element-plus
,这行样式代码就能生效了!
一开始我还想着写个插件来解决这个问题,但是我一想,为什么配置了 external
也没起作用呢?后来查了查资料,加上自己的猜想(因为这个配置里面是可以写正则的 ),猜测 external
是一个全等匹配的方式来判断要不要 external
的。比如:
js
import { xx } from 'element-plus' // 命中 external 的 'element-plus' 配置
import { xx } from 'element-plus/es/xxx' // 未命中
上面的 'element-plus/es/xxx'
不会命中 external
规则,所以也就会被打包工具正常处理了。所以这里我打算写个正则去验证一下,搞一个 'element-plus/es/components/'
开头,以 'style/css'
结尾的正则。写正则确实不是我的强项,所以我用 GPT 几秒钟写了个:
js
// 省略其他的配置
external: [/^element-plus\/es\/components.*\/style\/css$/]
这样一下来,我们再打包一次看看效果:
完美,发包!今晚就上线!