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

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

相关推荐
我认不到你19 分钟前
antd proFromSelect 懒加载+模糊查询
前端·javascript·react.js·typescript
scc214029 分钟前
spark的学习-06
javascript·学习·spark
我是苏苏1 小时前
C# Main函数中调用异步方法
前端·javascript·c#
转角羊儿1 小时前
uni-app文章列表制作⑧
前端·javascript·uni-app
Bio Coder2 小时前
学习用 Javascript、HTML、CSS 以及 Node.js 开发一个 uTools 插件,学习计划及其周期
javascript·学习·html·开发·utools
糊涂涂是个小盆友2 小时前
前端 - 使用uniapp+vue搭建前端项目(app端)
前端·vue.js·uni-app
Amd7942 小时前
Nuxt.js 应用中的 schema:written 事件钩子详解
生命周期·vite·配置·日志·nuxt·服务·钩子
凹凸曼打不赢小怪兽3 小时前
react 受控组件和非受控组件
前端·javascript·react.js
忠实米线4 小时前
使用pdf-lib.js实现pdf添加自定义水印功能
前端·javascript·pdf
明辉光焱4 小时前
[Electron]总结:如何创建Electron+Element Plus的项目
前端·javascript·electron