如何利用 `Vite` 和 `Rollup` 的配置,实现一个高度优化的生产环境打包方案?

Vite & Rollup:打造极致优化的生产环境打包方案

大家好!我是老码,今天给大家带来一场关于 Vite 和 Rollup 的深度融合,打造极致优化生产环境打包方案的讲座。别担心,咱们不搞枯燥的理论,用最接地气的方式,把这些高大上的工具玩转起来。

1. 为什么是 Vite + Rollup?

你可能会问,Vite 不是已经很快了吗?为什么还要 Rollup?

Vite 在开发阶段确实快如闪电,它的核心在于利用浏览器原生 ES Module 的能力,无需打包,就能实现近乎实时的热更新。 但是,生产环境的打包,我们需要的是极致的优化,比如代码压缩、Tree Shaking、代码分割等等。而 Rollup 在这些方面有着更强大的控制力和灵活性。

简单来说,Vite 擅长“快”,Rollup 擅长“精”。 我们的目标是,开发阶段用 Vite 享受速度,生产阶段用 Rollup 追求极致优化,两全其美!

2. 配置策略:Vite 管面子,Rollup 管里子

我们的配置思路是:

  • Vite: 负责基础的构建流程、插件集成、静态资源处理等“面子”工程。
  • Rollup: 负责更深层次的代码优化,Tree Shaking,代码分割,以及一些高级的自定义构建逻辑,也就是“里子”工程。

具体做法就是: Vite 作为 Rollup 的一个插件,将 Vite 处理好的代码交给 Rollup 进行进一步的优化。

3. 实战:一个简单的 React 项目

咱们以一个简单的 React 项目为例,一步一步地配置。

3.1 项目初始化

npm create vite my-vite-rollup-project --template react
cd my-vite-rollup-project
npm install

3.2 安装 Rollup 和相关插件

npm install rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-terser @rollup/plugin-babel --save-dev
  • rollup: Rollup 本身。
  • @rollup/plugin-node-resolve: 用于解析 Node.js 模块。
  • @rollup/plugin-commonjs: 用于将 CommonJS 模块转换为 ES 模块。
  • rollup-plugin-terser: 用于代码压缩。
  • @rollup/plugin-babel: 用于代码转译,比如将 ESNext 语法转换为 ES5。

3.3 配置 vite.config.js

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { rollup } from 'rollup';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from 'rollup-plugin-terser';
import babel from '@rollup/plugin-babel';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    {
      name: 'rollup-build',
      apply: 'build',
      async closeBundle() {
        const bundle = await rollup({
          input: 'dist/index.html', //  Vite 构建输出的入口 HTML 文件
          plugins: [
            nodeResolve(),
            commonjs(),
            babel({
              babelHelpers: 'bundled',
              exclude: 'node_modules/**',
              presets: ['@babel/preset-env', '@babel/preset-react']
            }),
            terser(),
          ],
          output: {
            dir: 'dist',
            format: 'es', // 输出为 ES 模块
            sourcemap: true,
          },
        });

        await bundle.write({
          dir: 'dist',
          format: 'es',
          sourcemap: true,
        });
      },
    },
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            return 'vendor'; // 将 node_modules 中的代码打包到 vendor.js
          }
        },
      },
    },
  },
});

代码解释:

  • plugins 数组: Vite 的插件配置,这里我们使用了 react() 插件来支持 React,并且自定义了一个名为 rollup-build 的插件。
  • rollup-build 插件: 这个插件的关键在于 closeBundle 钩子函数,它会在 Vite 构建完成后执行。
    • rollup({...}): 调用 Rollup 进行构建。
    • input: 'dist/index.html': Rollup 的入口文件,这里指向 Vite 构建输出的 HTML 文件。 因为 Vite 最终会将 JS 注入到 HTML,所以我们以 HTML 作为入口。
    • plugins: [...]: Rollup 的插件配置,包括 Node.js 模块解析、CommonJS 模块转换、Babel 代码转译和代码压缩。
    • output: {...}: Rollup 的输出配置,指定输出目录、格式和 Sourcemap。
  • build.rollupOptions.output.manualChunks: Vite 的代码分割配置,这里我们将 node_modules 中的代码打包到 vendor.js 中,可以利用浏览器缓存。

3.4 配置 .babelrc.js (或者 babel.config.js)

module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-react'],
};

这个配置文件告诉 Babel 如何转换我们的代码,这里我们使用了 @babel/preset-env 来支持最新的 JavaScript 语法,并且使用了 @babel/preset-react 来支持 React 的 JSX 语法。

3.5 修改 package.json

修改 package.json 中的 scripts 字段,添加一个 build 命令:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "serve": "vite preview"
},

3.6 执行构建

npm run build

现在,Vite 会先进行构建,然后 rollup-build 插件会启动 Rollup 进行进一步的优化。最终的构建产物会在 dist 目录下。

4. 优化策略:精雕细琢,追求极致

上面的配置只是一个基础的框架,我们可以进一步地优化:

4.1 Tree Shaking

Tree Shaking 是指移除 JavaScript 代码中未使用的部分。 Rollup 对 ES 模块的 Tree Shaking 支持非常好。 通过合理地组织代码,可以有效地减少最终的包大小。

如何优化?

  • 使用 ES 模块: 确保你的代码和依赖库都使用 ES 模块。
  • 避免副作用: 尽量编写没有副作用的代码。 副作用是指函数或表达式除了返回值之外,还会修改程序的状态(比如修改全局变量)。
  • 最小化依赖: 尽量减少不必要的依赖。

4.2 代码分割 (Code Splitting)

代码分割是指将代码拆分成多个小的 chunk,按需加载。 可以有效地减少首屏加载时间。

Vite 已经内置了代码分割,但我们可以进一步优化:

  • manualChunks 函数:vite.config.js 中,我们可以使用 build.rollupOptions.output.manualChunks 函数来自定义代码分割策略。

    build: {
      rollupOptions: {
        output: {
          manualChunks(id) {
            if (id.includes('node_modules')) {
              if (id.includes('react') || id.includes('react-dom')) {
                return 'react'; // 将 React 相关的代码打包到 react.js
              }
              return 'vendor'; // 其他 node_modules 中的代码打包到 vendor.js
            }
    
            if (id.includes('src/components')) {
              return 'components'; // 将 components 目录下的代码打包到 components.js
            }
          },
        },
      },
    },

    代码解释:

    • 我们优先将 React 相关的代码打包到 react.js,然后将其他的 node_modules 代码打包到 vendor.js
    • 我们将 src/components 目录下的代码打包到 components.js
    • 这样可以更细粒度地控制代码分割,提高缓存利用率。
  • 动态导入 (Dynamic Import): 使用 import() 语法来实现按需加载。

    // 例子:按需加载组件
    async function loadComponent() {
      const { MyComponent } = await import('./MyComponent');
      // ...
    }

4.3 静态资源优化

  • 图片压缩: 使用图片压缩工具来减小图片的大小。 比如 imagemin
  • 字体优化: 使用 font-subsetting 来减小字体文件的大小。
  • CDN 加速: 将静态资源部署到 CDN 上,利用 CDN 的缓存和加速能力。

4.4 其他优化

  • HTTP/2: 启用 HTTP/2 协议,可以并行加载资源,提高加载速度。
  • Gzip 压缩: 对传输的资源进行 Gzip 压缩,减小传输体积。
  • Service Worker: 使用 Service Worker 来缓存静态资源,实现离线访问。

5. 总结:Vite + Rollup 的威力

特性 Vite Rollup
开发体验 快如闪电,热更新迅速 需要手动配置,相对繁琐
构建速度 相对较快 相对较慢
优化能力 基础优化,开箱即用 深度优化,可定制性强
适用场景 中小型项目,追求开发效率 大型项目,追求极致优化
学习成本 较低 较高

通过将 Vite 和 Rollup 结合起来,我们可以兼顾开发效率和生产环境的优化,打造出高性能的 Web 应用。

6. 进阶:更高级的配置

  • 自定义 Rollup 插件: 如果 Rollup 现有的插件无法满足你的需求,你可以编写自己的 Rollup 插件。
  • 多入口构建: 对于大型项目,你可能需要配置多个入口文件,Rollup 可以很好地支持多入口构建。
  • 模块联邦 (Module Federation): 使用 Module Federation 可以将不同的应用或组件拆分成独立的模块,按需加载。 Vite 和 Rollup 都支持 Module Federation。

7. 踩坑指南

  • 插件冲突: Vite 和 Rollup 的插件可能会有冲突,需要仔细排查。
  • 配置复杂: Rollup 的配置相对复杂,需要仔细阅读文档。
  • 构建时间长: 深度优化可能会导致构建时间变长,需要权衡利弊。

8. 最后的忠告

优化是一个永无止境的过程,我们需要不断地学习和探索,才能打造出更加优秀的 Web 应用。 希望今天的讲座对大家有所帮助! 记住,没有银弹,只有不断学习,不断实践! 感谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注