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
。 - 这样可以更细粒度地控制代码分割,提高缓存利用率。
- 我们优先将 React 相关的代码打包到
-
动态导入 (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 应用。 希望今天的讲座对大家有所帮助! 记住,没有银弹,只有不断学习,不断实践! 感谢大家!