一个关于使用了 unplugin-auto-import 的组件打包优化

背景

一个页面是由多个远程组件组件(如下图)组成,然后页面渲染器加载这些远程组件组成一个页面,最近在做性能优化专项,第一个想到的是减少 远程组件体积

过程

组件开发采用Vite+Vue3+ElementPlus. ElementPlus配置使用的auto import方式 以下都用简单的组件进行示例展示。组件代码.

  1. 使用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'
        }
      }
    }
  1. 本来已经大功告成的时候,发现打包后还是没有external掉, 后来自己调试了一段时间发现使用了auto import这个配置时,external ElementPlus 会失效。后来试试想着按需引入 的话是不是能external掉。
  2. 发现按需引入 可以有效减小体积并成功external掉ElementPlus,为了提供更好的开发体验,能不能编写一个插件 ,打包时自动将按需引入的Element组件插入到Vue SFC的script部分。
  3. 着手开始写这个插件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。组件使用的越多,可能插件越明显。

有其他的实现方案欢迎大佬们评论。

相关推荐
Sheldon一蓑烟雨任平生4 小时前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
鱼与宇6 小时前
苍穹外卖-VUE
前端·javascript·vue.js
用户47949283569156 小时前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
裴嘉靖6 小时前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw2824266 小时前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽7 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁7 小时前
Angular【router路由】
前端·javascript·angular.js
时间的情敌7 小时前
Vite 大型项目优化方案
vue.js
西洼工作室8 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
樱花开了几轉8 小时前
element ui下拉框踩坑
开发语言·javascript·ui