Rollup打包优化

1,581 阅读7分钟

背景

在做一键webpack转vite的工具中rollup打包升级时,发现在某个项目中,使用vite后本地冷启动很快了,但是使用rollup打包,在公司构建工具上打包部署需要180s左右,M1本地打包也在102s左右,打包时间过于长,打包时长长也说明体积有优化空间,所以开始研究怎么优化体积与打包时长

制定方向

  1. 体积的减少
  2. 打包速度的提升

前期准备

安装rollup-plugin-visualizer 查看包体积分布、各插件占比情况

一、安装rollup-plugin-visualizer
yarn add rollup-plugin-visualizer
二、使用rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer’;

return {
	...
	plugins: [
    	...
    	visualizer(),
        ...
    ],
    ...
}

三、执行打包
yarn build

执行打包后会在根目录生成stats.html文件,在浏览器中直接打开即可

Snipaste_2023-06-26_15-19-36.png

四、rollup-plugin-visualizer视图

Snipaste_2023-06-26_15-22-17.png

五、根据视图分析
  1. root包大小在没优化前是10.22MB

  2. vendor包大小在没优化前是9.33MB,依赖项占比87%

  3. 分析几个比较大的包,分别有ant-design-vuelodashwcharts

    1. wcharts占2.88MB,转存失败,建议直接上传图片文件
    2. ant-design-vue占1.87MB,转存失败,建议直接上传图片文件
    3. lodash占用547KB,转存失败,建议直接上传图片文件,可以看到lodash被哪些文件引用,可以方便排查和修改
  4. 重复包:wcharts下有echarts、然后还有单独的echarts包打入vendor,需要排查下项目内是否有重复引用

  5. 观察到lodash 和 lodash-es重复引用

六、策略
  1. 根据echarts重复引用的问题,将echarts引用删除
  2. 将重复的lodash库改为lodash-es引用
  3. ant-design-vue按需加载
  4. 因为wcharts的包比较大,占比28%,计划使用external将wcharts从vendor剥离,使用cdn形式引入,需要观察下白屏时间是否增加
  5. 路由懒加载

优化实现

一、删除重复引用

1、项目中main.js删除echarts的多余引用

Snipaste_2023-06-26_15-25-09.png

2、将lodash改为lodash-es引用,lodash使用的是旧版es5模块语法,而lodash-es使用的是es6模块语法,像webpack、rollup这种打包工具可以通过tree shaking将多余的代码摇掉,lodash打完包是547KB,而lodash-es打完包是154KB,差距还是挺大的。

Snipaste_2023-06-26_15-26-17.png

二、ant-design-vue 按需加载

项目中原用的全局引用antd-vue,这里计划改成使用unplugin-vue-components/vite来实现按需加载,插件原理大致是:识别template中的a-input、a-button等,然后在全局组件编译后通过正则拿到入参a-input转成AInput,交给解析器,之后修改vue编译后的代码,注入import { AInput } from ant-design-vue/es的代码

安装
yarn add unplugin-vue-components -D
使用
import Components from 'unplugin-vue-components/vite’;
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers’;

return {
	...
	plugins: [
    	...
    	Components({
        	 	// ui库解析器,可以自定义
                resolvers: [
                    AntDesignVueResolver({
                    // 参数配置可参考:https://github.com/antfu/unplugin-vue-components/blob/main/src/core/resolvers/antdv.ts
                    // 自动引入 ant-design/icons-vue中的图标,需要安装@ant-design/icons-vue
                        resolveIcons: true
                    })
                ]
            })
        ...
    ],
    ...
}

此插件还可以使用其他的UI组件库,引入相对应的解析器就可以了,比如使用的是elemenPlus组件库,可以使用ElementPlusResolver解析器 vant使用VantResolver, elementUI可以使用ElementUiResolver解析器,都可以实现按需加载,且不需要在单独文件中再次写引用语句。

遇到的问题
  1. 非模板组件例如message、nitification等组件使用失效
  2. unplugin-vue-components/vite 插件对于动态组件的引入识别异常
全局组件失效解决
  1. message、nitification等组件在全局单独引入,挂载在全局, 此方法最为稳妥,推荐
  2. 网上推荐的插件vite-plugin-style-import据说也可以解决这个问题,大致用法如下,但经过我的实验发现:首先这个插件需要下载1.xx版本,否则会启动报错,改为1.xx版本后实验message组件也是展示不出的,不推荐
import styleImport, {
  AndDesignVueResolve,
  VantResolve,
  ElementPlusResolve
} from 'vite-plugin-style-import'


plugins: [
    styleImport({
      resolves: [
        AndDesignVueResolve(),
        VantResolve(),
        ElementPlusResolve()
      ],
      // 自定义规则
      libs: [
        {
          libraryName: 'ant-design-vue',
          esModule: true,
          resolveStyle: (name) => {
            return `ant-design-vue/es/${name}/style/index`
          }
        }
      ]
    })
  ],
  // 引用使用less的库要配置一下
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true
      }
    }
  }
动态组件失效解决

针对动态组件不生效的问题,我的解决方案是建立了一个extra.js单独存放需要引入UI库的组件,例如按需加载后以下代码是加载不出a-input的,之前没问题是因为所有组件注册在了实例中

<component :is="AInput">
// extra.js 文件
import 'ant-design-vue/es/input/style/css';
import input from 'ant-design-vue/es/input';

const config = {AInput: input};
export default config;
// control.js
import extra from './extra';

for (const i in extra) {
    app.component(i, extra[i]);
}
效果

实现了删除重复引用、lodash改为lodash-es、antd-vue按需加载后,总包大小从10.22MB降至7.49MB, 包体积减少26%左右的空间

Snipaste_2023-06-26_16-07-09.png

打包速度从102s左右降至75s左右,提升速率26%左右

Snipaste_2023-06-26_16-07-37.png

三、三方库使用cdn形式引入

external是什么?

防止将某些 import 的包打包到 bundle中,而是在运行时再去从外部获取这些扩展依赖,一般情况下是将大体积且基本无变动的第三方包处理为external,针对此项目最大的依赖包是wcharts,占比28%,wcharts是在echarts上二次封装的。

实现

有两种方式实现,一种是external+html配置cdn地址的方式,一种是引入vite-plugin-cdn-import插件形式配置cdn地址自动插入到html模板中

external+html

  1. 在配置文件中增加external

    external:[wchars]
    
  2. 在html中增加cdn引入,地址来自xingtu的文档:xingtu.58xinghuo.com/pages/wchar…

    <script src="https://c.58cdn.com.cn/git/cdn/echarts/5.1.1/echarts.min.js"></script>
    <script src="https://wos2.58cdn.com.cn/MnOjIhGfMnSn/wcharts/wcharts-1.0.16-umd.min.js"></script>
    

vite-plugin-cdn-import方式

import importToCDN from 'vite-plugin-cdn-import';

plugins:[
	importToCDN({
        modules: [
            {
                name: 'echarts', // 包名称
                var: 'echarts', // 在项目引用的名称
                path: 'https://c.58cdn.com.cn/git/cdn/echarts/5.1.1/echarts.min.js' // cdn地址
            },
            {
                name: '@dpd/wcharts',
                var: 'Wcharts',
                path: 'https://wos2.58cdn.com.cn/MnOjIhGfMnSn/wcharts/wcharts-1.0.16-umd.min.js'
            }
        ]
    })
]
效果

因为这个项目是共建项目,所以在external生效之前采集了包大小数据

root包大小8.41MB,vendor包大小7.35MB,打包速度在43s左右

Snipaste_2023-06-26_16-11-21.png

Snipaste_2023-06-26_16-11-40.png

通过处理external后,root包大小为4.99MB,vendor包为3.92MB, 打包速度降至 25s左右,在x86上打包大概在70s左右

Snipaste_2023-06-26_16-12-14.png

Snipaste_2023-06-26_16-12-29.png

Snipaste_2023-06-26_16-12-56.png

包体积较之前减少了40.6% ,打包速度较之前提升了43.4%

白屏时间

我们使用DOMContentLoaded,即当一个 HTML 文档被加载和解析完成,来记录白屏时间,来观察处理external后的白屏时间是否有增加

在已缓存多次刷新情况下计算,处理了external后DOMContentLoaded时间都保持在1s内,且均值在517ms( 采集次数10次 ,处理external前的DOMContentLoaded均值在936ms,白屏时间也有所提升

四、图片压缩

使用vite-plugin-imagemin插件进行图片优化

import viteImagemin from 'vite-plugin-imagemin'

plugins: [
	viteImagemin({
      optipng: {
        optimizationLevel: 3, // png图片的质量分,从0-70是最小的影响,不会对图片进行改变,7是最大的影响,中间逐步递增对图片的改变
      },
      mozjpeg: {
        quality: 20, // jpeg的质量分,0-100 
      }
    })
]

详细的options见:github.com/vbenjs/vite…

安装插件打包后会给出优化的百分比,可以直观的看到优化效果

Snipaste_2023-06-26_16-15-48.png

五、路由懒加载

实现路由懒加载有两种形式

1、直接import

{
    path: '/NewMarketing/signIn',
    name: 'Market',
    component: () => import('../views/designer/index.vue')
}

2、defineAsyncComponent方法,vue3新定义的异步组件方法

{
    path: '/NewMarketing/signIn',
    name: 'Market',
    component: () => defineAsyncComponent(() => import('../views/designer/index.vue'))
}

这两种方式都会将组件独立分chunk引用,所以这里需要注意如果是完全前后端分离,可以使用路由懒加载的形式去优化,如果不是完全分离的话需要兼容模板的入口js、入口css

总体效果

在经过了删除重复引用、改用小体积工具包、按需加载、external的优化后,包总体积从之前的10.22MB降至4.99MB,ARM架构打包速度从102s左右降至25s左右,X86架构上打包速度从180s左右降至70s左右,包大小基本减少了一半,打包速度较之前提升了75%左右,在X86上打包速度较之前提升了61%左右

总结

  1. 优化前需要收集数据,webpack可以安装webpack-bundle-analyzer,rollup安装rollup-plugin-visualizer直观的查看项目的包情况,能直观看到哪些包的体积较大需要优化。
  2. 有些优化是可以在创建项目初期就规定好,比如UI组件库按需引入、如果使用像lodash中的方法较少可以自己定义,无需引入,如果引入的话也引入lodash-es
  3. 包体积较大且基本无更新的三方包可以external处理,能大大缩小包体积,减少打包速度
  4. 注意在处理UI组件库按需加载时,如果之前是全局加载,需要考虑动态组件、message这种全局挂载组件的处理