Vite 分块优化

Flying
2023-07-04 / 0 评论 / 136 阅读 / 正在检测是否收录...

Vite 现在是越来越强大,无论是 Vue 工程,还是 React的工程,都可以体验 Vite 极速的服务启动。开发环境下,使用原生 ESM 文件,无需打包。在生产环境下,Vite借助 Rollup 来打包的,所以和其他打包工具一样,也有一个优化的问题。本文就来说说 Vite 工程怎样优化分块,因为我们很在乎打包后的文件大小。

vite-code-splitting.svg

以一个我先前做过的 Vue3 crud 工程为例,这个工程用到了Element plus UI 库,这个库是比较大,仅完整版的 min.js 就有 1M 左右,如果用 Vite 默认打包,会得到下面的警告提示:

✓ 1456 modules transformed.
dist/index.html                  0.42 KiB
dist/assets/index.310b9427.css   316.87 KiB / gzip: 43.16 KiB
dist/assets/index.972b2f34.js    1098.90 KiB / gzip: 340.02 KiB

(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

Vite 给出三个优化建议:

  • 使用动态 import() 对应用程序进行代码分割。如果我们用到路由,可以优先考虑这种方式。或者我们的应用程序有使用 Element plus,Antd 这种大型 UI 组件库,可以考虑按需导入组件,我们会稍后讲解这一优化。
  • 使用 build.rollupOptions.output.manualChunks 来优化分块,这个是我们要重点讲的内容。
  • 通过 build.chunkSizeWarningLimit 调整此警告的块大小限制。chunkSizeWarningLimit 参数值默认是 500 KB,调成 2M 就不会警告了。有点掩耳盗铃的意思,一般我们不会这样做,除非我们的网速特别快。
本质上,Webpack 可以使用 require.context 来动态导入组件,Vite 借助 import.meta.glob 可以实现类似功能。

按需导入组件

这里仅以按需自动导入为例,我们需要安装 [unplugin-vue-components]() 插件

npm i -D unplugin-vue-components
Element plus 官方文档的快速开始 说是需要安装 unplugin-vue-components 和 unplugin-auto-import 这两款插件。

然后把下列代码插入到你的 Vite 的配置文件中。

import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

const pathSrc = path.resolve(__dirname, 'src')
// https://vitejs.dev/config/
export default defineConfig({
  base: './',
  resolve: {
    alias: {
      '~/': `${pathSrc}/`,
    },
  },
  plugins: [
    vue(), 
    Components({
      resolvers: [
        ElementPlusResolver({
          importStyle: 'sass',
        })
      ],
      dts: 'src/type/components.d.ts',
    })
  ],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "~/styles/element/index.scss" as *;`
      }
    }
  }
})

unplugin-vue-components 实现了Vue 组件的按需自动导入。

  • dts:设置生成组件的全局声明文件,如果安装了 typescript 包,默认值 true,也接受自定义文件名的路径

本示例中,npm run dev 启动后,会在工程根目录/src/type 下自动生成 components.d.ts

  • resolvers:为流行的 UI 库(如 Vuetify、Ant Design Vue 和 Element Plus)提供了几个内置解析器,通过导入相应解析器来启用它们。

本示例中,我们使用 ElementPlusResolver来支持 Element Plus 组件的自动导入。

我们还通过配置 css.preprocessorOptions.scss.additionalData自定义组件主题

现在 npm run build 打包一下,看一下优化效果。

✓ 1505 modules transformed.
dist/index.html                  0.42 KiB
dist/assets/index.807e96e6.css   136.75 KiB / gzip: 19.12 KiB
dist/assets/index.9226c9ca.js    743.79 KiB / gzip: 231.22 KiB

不错,index.js,和 index.css 明显小了,不过js bundle 依旧很大。业务代码、组件库代码、Vue.js、和第三方库代码都在一个 bundle 中,文件能不大吗?得分块,也就是之前讲的“代码分割”,将 bundle(包)拆分就成了 多个 chunks(块)。

在 Vite/Webpack 中,一个入口对应一个 bundle,而模块(Modules)对应导入的代码库,比如说 Element Plus/vue.js/lodash。每个 bundle 可以由多个模块组成,但按需导入多个模块的 bundle 不一定比某个模块文件大。所以理想情况是将 bundle 按共享常用的模块分块,比如 Element Plus一个块,因为它文件比较大。Vue.js、和第三方库代码一个块。因为我们用的第三方库代码不多,文件比较小,不宜拆分太多的块。

manualChunks

Webpack 中,我们使用 SplitChunks 插件来分块,有很多的优化配置,感兴趣的同学可以参考我之前写的Webpack 分块优化。Vite 中,功能没有这么多,但够用了,我们可以使用 manualChunks 来分块。

manualChunks 允许创建自定义共享常用块。支持对象形式和函数形式分块。

对象形式

类型:[chunkAlias: string]: string[]

使用对象形式是,每个属性表示一个包含列出的模块及其所有依赖项(如果它们是模块图的一部分)的块,除非它们已经在另一个手动块中。块的名称将由属性键确定。

build: {
  rollupOptions: {
    output: {
      manualChunks: {
        element: ['element-plus']
      }
    }
  }
}

重新打包一下,看一下优化效果。

✓ 1505 modules transformed.
dist/index.html                   0.49 KiB
dist/assets/index.807e96e6.css    136.75 KiB / gzip: 19.12 KiB
dist/assets/index.e0922e3f.js     270.68 KiB / gzip: 72.58 KiB
dist/assets/element.d52a0af3.js   470.92 KiB / gzip: 157.68 KiB

还行,element.js 从之前的 index.js 拆分了出来,文件大小更合理了。

然而我们如果需要创建一个包含 node_modules 中的所有依赖项的块,用对象形式是不行的,因为node_modules 不是合法的模块名。我们得用更灵活的函数形式进行配置。

函数形式

类型:(id: string, {getModuleInfo, getModuleIds}) => string | void

使用函数形式时,每个解析后的模块 ID 将传递给函数。如果返回一个字符串,则将该模块及其所有依赖项添加到具有给定名称的手动块中。例如,以下代码会额外创建一个命名为 vendor 的 chunk,它包含所有在 node_modules 中的依赖:

manualChunks: (id) => {
  // console.log(id)
  if (id.includes('element-plus')) return 'element'
  if (id.includes('node_modules')) return 'vendor'
}

打印一下 id,我们会看到一大推的文件路径。Vite/Webpack 是以文件路径作为模块 ID 的,这也是 Node.js 的一贯做法。

E:/vue3-crud/node_modules/element-plus/es/components/color-picker/src/components/alpha-slider.mjs
E:/vue3-crud/node_modules/element-plus/es/components/color-picker/src/components/hue-slider.mjs
E:/vue3-crud/node_modules/dayjs/plugin/customParseFormat.js
E:/vue3-crud/node_modules/dayjs/plugin/advancedFormat.js
E:/vue3-crud/node_modules/lodash-es/add.js
E:/vue3-crud/node_modules/lodash-es/after.js
E:/vue3-crud/node_modules/@ctrl/tinycolor/dist/module/index.js
E:/vue3-crud/node_modules/@ctrl/tinycolor/dist/module/css-color-names.js
E:/vue3-crud/node_modules/axios/lib/core/transformData.js
E:/vue3-crud/node_modules/axios/lib/helpers/isAbsoluteURL.js
E:/vue3-crud/node_modules/async-validator/dist-web/index.js
E:/vue3-crud/node_modules/escape-html/index.js
// ...
E:/vue3-crud/src/utils/request.ts
E:/vue3-crud/src/components/AreaCascader/area.ts
// ...
借助 getModuleInfo, getModuleIds 参数可实现更复杂的分块配置。

跑一下 npm run build 看看最终优化效果。

✓ 1505 modules transformed.
dist/index.html                    0.63 KiB
dist/assets/index.a995fcfa.css     1.85 KiB / gzip: 0.62 KiB
dist/assets/element.35dfb808.css   134.88 KiB / gzip: 18.68 KiB
dist/assets/index.ad83645e.js      177.41 KiB / gzip: 37.60 KiB
dist/assets/element.de98a909.js    309.86 KiB / gzip: 98.25 KiB
dist/assets/vendor.afd46969.js     256.42 KiB / gzip: 93.77 KiB

vendor.jselement.css 也拆分出来了,大功告成,这下比较满意了。

总结

本文简述了 Vite 分块优化的背景,重点简述了两种分块优化:通过按需自动导入组件分割代码,设置 manualChunks 手动改进分块。

链接

1

评论 (0)

取消