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

各位观众老爷,晚上好!我是你们的老朋友,今晚咱们聊聊如何用Vite和Rollup这对“双剑合璧”,打造一个极致优化的生产环境打包方案。别怕,我尽量把这个过程说得像在家门口下馆子一样轻松愉快。

开场白:Vite和Rollup,哥俩好,效率高

首先,咱们要明白Vite和Rollup各自的优势。Vite就像一个急性子的厨师,开发阶段讲究“快”,利用浏览器原生ESM,按需编译,闪电般的速度让你爱不释手。Rollup则像一个精益求精的大厨,生产环境打包时,专注于“精”,通过各种优化手段,让打包后的代码体积更小,性能更强。

简单来说:

工具 优势 适用场景
Vite 开发阶段,启动速度快,热更新迅速,基于ESM 大型项目,需要快速迭代,注重开发效率
Rollup 生产环境打包,Tree Shaking 强大,插件生态丰富,可定制性高,产物体积小,性能优化空间大 需要极致优化的生产环境代码,对代码体积和性能有较高要求的项目,例如:库、组件库、框架等

所以,理想的方案是:开发阶段用Vite,享受丝滑的开发体验;生产环境用Rollup,榨干代码的每一滴性能。

第一步:Vite构建,Rollup接棒

最基本的操作流程是:先用Vite构建出用于生产环境的中间产物,然后用Rollup对这些中间产物进行进一步的优化和打包。

  1. Vite 构建生产环境代码

    package.json中定义 Vite 的 build 命令:

    {
     "scripts": {
       "build:vite": "vite build"
     }
    }

    运行 npm run build:viteyarn build:vite,Vite 会将你的代码打包到 dist 目录(默认)。当然,你可以通过 vite.config.js 文件自定义构建配置,例如指定输出目录:

    // vite.config.js
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    
    export default defineConfig({
     plugins: [vue()],
     build: {
       outDir: 'dist_vite' // 指定输出目录
     }
    })
  2. Rollup 接棒优化打包

    接下来,我们需要安装 Rollup 及相关插件:

    npm install rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-terser --save-dev
    • rollup: Rollup 的核心库。
    • @rollup/plugin-node-resolve: 用于解析 Node.js 模块。
    • @rollup/plugin-commonjs: 用于将 CommonJS 模块转换为 ES 模块。
    • rollup-plugin-terser: 用于代码压缩和混淆。

    创建一个 rollup.config.js 文件,配置 Rollup:

    // rollup.config.js
    import { nodeResolve } from '@rollup/plugin-node-resolve';
    import commonjs from '@rollup/plugin-commonjs';
    import { terser } from 'rollup-plugin-terser';
    
    export default {
     input: 'dist_vite/index.js', // Vite 构建的入口文件
     output: {
       file: 'dist/bundle.js', // Rollup 打包后的输出文件
       format: 'es', // 输出格式,这里使用 ES 模块
       sourcemap: true, // 生成 Source Map,方便调试
     },
     plugins: [
       nodeResolve(), // 解析 Node.js 模块
       commonjs(), // 将 CommonJS 模块转换为 ES 模块
       terser(), // 压缩代码
     ],
    };

    package.json 中添加 Rollup 的构建命令:

    {
     "scripts": {
       "build:rollup": "rollup -c rollup.config.js"
     }
    }

    运行 npm run build:rollupyarn build:rollup,Rollup 就会根据你的配置,对 Vite 构建的中间产物进行优化和打包。

第二步:Rollup配置进阶,让优化更上一层楼

仅仅是上面的基础配置,还远远不够。 Rollup 的强大之处在于其高度可定制性,我们可以通过各种插件和配置项,对打包过程进行更精细的控制。

  1. Tree Shaking,摇掉无用代码

    Tree Shaking 是一种移除 JavaScript 代码中未引用代码的技术。 Rollup 对 Tree Shaking 的支持非常强大,能够有效地减小打包后的代码体积。默认情况下,Rollup 会自动进行 Tree Shaking,但为了确保其正常工作,你需要注意以下几点:

    • 使用 ES 模块语法 (importexport)。CommonJS 模块 ( requiremodule.exports) 对 Tree Shaking 不友好。
    • 避免副作用代码。副作用代码指的是那些在模块加载时会产生副作用的代码,例如修改全局变量。 Rollup 很难判断副作用代码是否被使用,因此可能会保留它们。
  2. 代码分割 (Code Splitting),按需加载

    代码分割是将你的代码分割成多个小的 chunk,然后按需加载这些 chunk。这可以减少初始加载时间,提高应用性能。 Rollup 支持多种代码分割策略,常用的有以下几种:

    • 基于入口点的代码分割: Rollup 会将每个入口点及其依赖的代码打包成一个 chunk。
    • 基于动态导入的代码分割: 使用 import() 语法动态导入模块时, Rollup 会将动态导入的模块及其依赖的代码打包成一个 chunk。

    要启用代码分割,你需要在 Rollup 的 output 配置中设置 format: 'es',并使用多个入口点或动态导入。

    例如,有多个入口点:

    // rollup.config.js
    export default {
     input: {
       main: 'src/main.js',
       about: 'src/about.js',
     },
     output: {
       dir: 'dist', // 输出目录
       format: 'es', // 输出格式
       entryFileNames: '[name].js', // 入口文件名称
       chunkFileNames: 'chunks/[name].js', // chunk 文件名称
       sourcemap: true,
     },
    };

    或者使用动态导入:

    // src/main.js
    async function loadAbout() {
     const { default: about } = await import('./about.js');
     console.log(about);
    }
    
    loadAbout();
  3. 插件生态,武装到牙齿

    Rollup 拥有丰富的插件生态,你可以利用这些插件来扩展 Rollup 的功能,例如:

    • @rollup/plugin-typescript: 用于支持 TypeScript。
    • rollup-plugin-vue: 用于支持 Vue 单文件组件。
    • rollup-plugin-postcss: 用于处理 CSS。
    • rollup-plugin-image: 用于处理图片。

    使用插件非常简单,只需要安装插件,然后在 rollup.config.jsplugins 数组中添加插件即可。

    // rollup.config.js
    import typescript from '@rollup/plugin-typescript';
    
    export default {
     // ...
     plugins: [
       typescript(), // 添加 TypeScript 插件
     ],
    };
  4. 代码压缩和混淆,让代码更苗条

    代码压缩和混淆是减小代码体积和提高代码安全性的重要手段。 Rollup 提供了 rollup-plugin-terser 插件,可以方便地进行代码压缩和混淆。

    // rollup.config.js
    import { terser } from 'rollup-plugin-terser';
    
    export default {
     // ...
     plugins: [
       terser({
         compress: {
           drop_console: true, // 移除 console.log 语句
         },
         mangle: true, // 混淆变量名
       }),
     ],
    };
  5. Scope Hoisting (实验性),提升运行性能

    Scope Hoisting 是一种将多个模块的代码合并到一个作用域中的技术。这可以减少函数调用次数,提高代码的运行性能。 Rollup 对 Scope Hoisting 的支持还处于实验性阶段,需要手动开启。

    // rollup.config.js
    export default {
     // ...
     treeshake: {
       moduleSideEffects: 'no-external', // 允许移除没有副作用的模块
       propertyReadSideEffects: false, // 允许移除对对象属性的无副作用的读取
       tryCatchDeoptimization: false, // 允许移除 try...catch 语句
     },
     context: 'this', // 确保 `this` 指向全局对象
     moduleContext: 'this', // 确保模块中的 `this` 指向全局对象
     output: {
       // ...
       generatedCode: {
         preset: 'es2015', // 使用 ES2015 代码风格
         symbols: true, // 保留模块的符号信息
       },
     },
    };

    注意: Scope Hoisting 可能会导致一些兼容性问题,需要谨慎使用。

第三步:实例演练,手把手教你配置

咱们来一个实际的例子,假设我们有一个基于 Vue 3 的项目,使用 TypeScript 编写,需要打包成一个库。

  1. 项目结构

    my-library/
    ├── src/
    │   ├── components/
    │   │   ├── MyComponent.vue
    │   │   └── index.ts
    │   ├── index.ts
    ├── vite.config.ts
    ├── rollup.config.js
    ├── package.json
    └── tsconfig.json
  2. src/components/MyComponent.vue

    <template>
     <div>
       <h1>{{ message }}</h1>
     </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from 'vue';
    
    const message = ref('Hello from MyComponent!');
    </script>
  3. src/components/index.ts

    import MyComponent from './MyComponent.vue';
    
    export { MyComponent };
  4. src/index.ts

    import { MyComponent } from './components';
    
    export { MyComponent };
  5. vite.config.ts

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import dts from 'vite-plugin-dts'; // 用于生成 .d.ts 文件
    
    export default defineConfig({
     plugins: [
       vue(),
       dts({
         insertTypesEntry: true, // 自动插入 types 入口
       }),
     ],
     build: {
       lib: {
         entry: './src/index.ts', // 入口文件
         name: 'MyLibrary', // 库的名称
         fileName: (format) => `my-library.${format}.js`, // 输出文件名
       },
       rollupOptions: {
         // 确保外部化处理那些你不想打包进库的依赖
         external: ['vue'],
         output: {
           globals: {
             vue: 'Vue', // 在 UMD 构建模式下,vue 将被视为全局变量 Vue
           },
         },
       },
     },
    });
  6. rollup.config.js

    import { nodeResolve } from '@rollup/plugin-node-resolve';
    import commonjs from '@rollup/plugin-commonjs';
    import typescript from '@rollup/plugin-typescript';
    import vue from 'rollup-plugin-vue';
    import { terser } from 'rollup-plugin-terser';
    import postcss from 'rollup-plugin-postcss';
    
    export default {
     input: 'dist/my-library.es.js', // Vite 构建的 ES 模块入口文件
     output: [
       {
         file: 'dist/my-library.cjs.js', // CommonJS 格式
         format: 'cjs',
         sourcemap: true,
       },
       {
         file: 'dist/my-library.umd.js', // UMD 格式
         format: 'umd',
         name: 'MyLibrary', // 库的名称
         sourcemap: true,
         globals: {
           vue: 'Vue', // 在 UMD 构建模式下,vue 将被视为全局变量 Vue
         },
       },
       {
         file: 'dist/my-library.es.js', // ES 模块格式
         format: 'es',
         sourcemap: true,
       },
     ],
     external: ['vue'], // 声明外部依赖,不打包进库
     plugins: [
       nodeResolve(), // 解析 Node.js 模块
       commonjs(), // 将 CommonJS 模块转换为 ES 模块
       typescript({
         tsconfig: './tsconfig.json', // 指定 tsconfig 文件
         declaration: false, // 不生成 .d.ts 文件,Vite 已经生成
       }),
       vue({
         preprocessStyles: true, // 预处理样式
       }),
       postcss({
         modules: true, // 启用 CSS Modules
         extract: true, // 提取 CSS 到单独的文件
       }),
       terser(), // 压缩代码
     ],
    };
  7. package.json

    {
     "name": "my-library",
     "version": "1.0.0",
     "main": "dist/my-library.cjs.js",
     "module": "dist/my-library.es.js",
     "unpkg": "dist/my-library.umd.js",
     "types": "dist/index.d.ts",
     "scripts": {
       "dev": "vite",
       "build:vite": "vite build",
       "build:rollup": "rollup -c rollup.config.js",
       "build": "npm run build:vite && npm run build:rollup"
     },
     "devDependencies": {
       "@rollup/plugin-commonjs": "^25.0.7",
       "@rollup/plugin-node-resolve": "^15.2.3",
       "@rollup/plugin-typescript": "^11.1.6",
       "@vitejs/plugin-vue": "^4.5.2",
       "postcss": "^8.4.32",
       "rollup": "^2.79.1",
       "rollup-plugin-postcss": "^4.0.2",
       "rollup-plugin-terser": "^7.0.2",
       "rollup-plugin-vue": "^6.0.0",
       "typescript": "^5.3.3",
       "vite": "^5.0.10",
       "vite-plugin-dts": "^3.7.0",
       "vue": "^3.3.11"
     }
    }
  8. 运行构建命令

    运行 npm run buildyarn build,Vite 会先构建出 ES 模块格式的库,然后 Rollup 会对这个 ES 模块进行进一步的优化和打包,生成 CommonJS、UMD 和 ES 模块格式的库。

第四步:注意事项,避开那些坑

  • 外部依赖 (External Dependencies): 使用 external 配置项声明你的库的外部依赖,例如 vue。这可以避免将这些依赖打包进你的库,减小库的体积,并允许用户使用他们自己的版本的依赖。
  • 输出格式 (Output Format): 根据你的库的使用场景,选择合适的输出格式。常用的输出格式有 CommonJS (cjs)、UMD (umd) 和 ES 模块 (es)。
  • Source Map: 生成 Source Map 可以方便用户调试你的库的代码。建议在生产环境也生成 Source Map,但不要将 Source Map 文件发布到生产环境,而是将它们存储在私有服务器上。
  • 错误处理: 在 Rollup 的配置中添加错误处理机制,可以帮助你及时发现和解决构建问题。

总结:Vite + Rollup,打造你的专属优化方案

通过 Vite 和 Rollup 的巧妙结合,我们可以打造一个高度优化的生产环境打包方案,让你的代码体积更小,性能更强,用户体验更好。 记住,没有一成不变的配置,你需要根据你的项目的具体情况,不断调整和优化你的配置,才能达到最佳效果。

希望今天的分享对你有所帮助,祝你打包愉快!下次再见!

发表回复

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