一个关于使用了 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。组件使用的越多,可能插件越明显。

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

相关推荐
Angel_girl3195 分钟前
vue项目使用svg图标
前端·vue.js
難釋懷9 分钟前
vue 项目中常用的 2 个 Ajax 库
前端·vue.js·ajax
爱生活的苏苏34 分钟前
vue生成二维码图片+文字说明
前端·vue.js
拉不动的猪36 分钟前
安卓和ios小程序开发中的兼容性问题举例
前端·javascript·面试
贩卖纯净水.1 小时前
浏览器兼容-polyfill-本地服务-优化
开发语言·前端·javascript
前端百草阁1 小时前
从npm库 Vue 组件到独立SDK:打包与 CDN 引入的最佳实践
前端·vue.js·npm
且白2 小时前
vsCode使用本地低版本node启动配置文件
前端·vue.js·vscode·编辑器
程序研2 小时前
一、ES6-let声明变量【解刨分析最详细】
前端·javascript·es6
疯狂的沙粒2 小时前
在uni-app中如何从Options API迁移到Composition API?
javascript·vue.js·uni-app
xiaominlaopodaren2 小时前
Three.js 光影魔法:如何单独点亮你的3D模型
javascript