背景
一个页面是由多个远程组件组件(如下图)组成,然后页面渲染器加载这些远程组件组成一个页面,最近在做性能优化专项,第一个想到的是减少 远程组件体积。
过程
组件开发采用Vite+Vue3+ElementPlus. ElementPlus配置使用的auto import方式 以下都用简单的组件进行示例展示。组件代码.
- 使用
rollup-plugin-visualizer
分析包体积内容, element-plus竟然没有被external
掉,这个external
的话包体积应该会掉下去很多。
2. 配置 external
json
rollupOptions: {
external: ['vue', 'axios','element-plus'],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue',
axios: 'axios',
'element-plus':'ElementPlus'
}
}
}
- 本来已经大功告成的时候,发现打包后还是没有
external
掉, 后来自己调试了一段时间发现使用了auto import
这个配置时,external
ElementPlus 会失效。后来试试想着按需引入 的话是不是能external
掉。 - 发现按需引入 可以有效减小体积并成功
external
掉ElementPlus,为了提供更好的开发体验,能不能编写一个插件 ,打包时自动将按需引入的Element组件插入到Vue SFC的script部分。 - 着手开始写这个插件
vite-plugin-extract-encycomp
。
插件vite-plugin-extract-encycomp
目标 : 写一个插件来自动把template里面的所有 Element组件
自动生成一个引入语句到插入到 Vue script里面。然后插件必须在Vue 编译插件之前。这样Vue插件执行的时候,执行我们转换后的代码就可以了。
vue
<template>
<div class="table-footer" v-loading="loading">
<el-button class="swwwwww-aa">{{ name }}</el-button>
<el-checkbox v-model="checked1" label="Option 1"></el-checkbox>
<el-input v-model="input" clearable> </el-input>
</div>
</template>
// 当 script 里面出现这三个组件 el-button,el-checkbox,el-input时。我们需要得到一个如下语句
// ' import { ElButton, ElCheckbox, ElInput } from 'element-plus' '
<template>
<div class="table-footer" v-loading="loading">
<el-button class="swwwwww-aa">{{ name }}</el-button>
</div>
</template>
// 当 script 里面出现el-button时。我们需要得到一个
// ' import { ElButton} from 'element-plus' '
动手准备(中间跳过了很多踩坑过程...)
Q1: rollup插件开发这个网上一搜就有很多。
Q2: 怎么能够提取出 SFC template 里面是组件的标签或者指令?
Q3: 怎么知道代码中是否已经引入了 ElementPlus 包的组件?
Q4: 怎么知道SFC 是Vue2或者Vue3?(Vue2组件引入方式不一样)
Q5: 怎么插入代码?
开发(这里主要演示Vue3版本)
A1: 看了文档先创建一个简单的rollup插件
js
export default function extractElComponentsPlugin() {
return {
name: 'rollup-plugin-extract-el-components',
async transform(code, id) {
}
}
}
A2: 使用 @vue/compiler-sfc
先获取 SFC template和script,然后通过compileTemplate方法就能拿到组件和用的指令了。
js
import { parse, compileTemplate } from '@vue/compiler-sfc'
// 获取 SFC template和script
export function getSfcContent(code) {
const sfcParse = parse(code)
let sfc_parse_script = ''
if (sfcParse?.descriptor?.scriptSetup?.content) {
// vue3 setup
sfc_parse_script = sfcParse?.descriptor?.scriptSetup?.content
} else {
// vue2
sfc_parse_script = sfcParse?.descriptor?.script?.content
}
// 获取 SFC 模板内容
const sfcParseTemplate = sfcParse?.descriptor?.template?.content
return {
sfc_parse_script,
sfcParseTemplate
}
}
export function getTemplateTag(templateContent) {
const { ast } = compileTemplate({
source: templateContent
})
const directives = ast.directives.map((item) => `v${item}`)
return [...ast.components ,...directives]
}
A3: 我用的是 @babel/traverse
解析成ast,然后提取已经引入的组件。
js
import traverse from '@babel/traverse'
export function getHasImportElementPlusComp(ast) {
const imports = []
// 使用AST遍历工具来查找引用了element-plus的import语句
traverse(ast, {
ImportDeclaration(path) {
const source = path.node.source.value
if (source === 'element-plus') {
path.node.specifiers.forEach((specifier) => {
if (specifier.type === 'ImportSpecifier') {
imports.push(specifier.imported.name)
}
})
}
}
})
return imports
}
A4: 还是用 @vue/compiler-sfc
javascript
import { parse } from '@vue/compiler-sfc'
export function sfcType(code) {
const sfcParse = parse(code)
if (sfcParse?.descriptor?.scriptSetup?.content) {
// vue3 setup
return 'vue3'
}
// vue2
return 'vue2'
}
A5:这个就比较简单啦,因为我们直接可以插入到 Vue Sfc 的 script开头,所以直接拼接就可以了。然后替换到源码的script部分
javascript
if (sfcType(code) === 'vue3') {
// 如果有匹配的组件名,插入 import 语句
const importStatement = getImportStatement(uniqueComponentNames)
const source = code.replace(
/(?<=<script\b[^>]*>)[\s\S]*(?=<\/script>)/,
`${importStatement}${sfc_parse_script}`
)
return source
}
这样一个插件就可以了耶。当然实现过程还是有很多细节的。让我们来测试一下,打包一个组件。 直接从126KB -> 1.54KB。组件使用的越多,可能插件越明显。
有其他的实现方案欢迎大佬们评论。