各位观众老爷们,大家好!今天咱们来聊聊 Vite 在生产环境下,是怎么玩转 Rollup,实现代码分割、Tree Shaking 和打包优化的。保证让各位听得懂,记得住,用得上!
开场白:Vite 和 Rollup 的爱恨情仇
Vite,这小伙子,开发环境那是杠杠的,启动速度嗖嗖的。但到了生产环境,还得靠老大哥 Rollup 出马。Rollup 就像是经验丰富的老师傅,专门负责把咱们的代码打磨得漂漂亮亮的,打包得紧紧实实的,好让浏览器能更快更好地运行。
Vite 本身并不负责生产环境的打包,它只是个“调度员”,把任务交给 Rollup。所以,理解 Rollup 在 Vite 中的作用,就等于抓住了 Vite 生产环境优化的命脉。
第一幕:代码分割 (Code Splitting)
啥是代码分割?简单来说,就是把一个大文件拆成多个小文件。 这样做的好处是:
- 首次加载更快: 浏览器只需要下载当前页面需要的代码,不用一次性下载所有代码。
- 缓存更高效: 当我们更新代码时,只有修改过的文件会被重新下载,其他文件可以继续使用缓存。
Rollup 是如何实现代码分割的呢?主要靠以下几种策略:
-
入口点 (Entry Points): 每个入口点都会被打包成一个独立的 chunk。 常见的入口点就是
index.html
引用的main.js
或者app.js
。// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], build: { rollupOptions: { input: { main: 'src/main.js', nested: 'src/nested/nested.js' } } } })
在这个例子中,
main.js
和src/nested/nested.js
会被打包成两个独立的 chunk。 -
动态导入 (Dynamic Imports): 使用
import()
语法可以实现按需加载。 Rollup 会把动态导入的代码分割成一个单独的 chunk。// src/components/MyComponent.vue <template> <button @click="loadModule">Load Module</button> </template> <script setup> const loadModule = async () => { const module = await import('./MyModule.js'); module.default(); // 执行模块的默认导出 }; </script>
在这个例子中,
./MyModule.js
会被打包成一个独立的 chunk,只有在点击按钮时才会加载。 -
公共模块 (Common Modules): 如果多个 chunk 都依赖同一个模块,Rollup 会把这个模块提取出来,打包成一个公共的 chunk。 这样可以避免重复加载相同的代码。
// src/utils/helper.js export function sayHello(name) { console.log(`Hello, ${name}!`); } // src/main.js import { sayHello } from './utils/helper.js'; sayHello('World'); // src/nested/nested.js import { sayHello } from './utils/helper.js'; sayHello('Nested');
在这个例子中,
./utils/helper.js
会被提取成一个公共的 chunk,main.js
和nested.js
都会引用这个 chunk。
代码分割策略总结:
策略 | 描述 | 示例 |
---|---|---|
入口点 | 每个入口点都会被打包成一个独立的 chunk。 | vite.config.js 中配置 build.rollupOptions.input |
动态导入 | 使用 import() 语法可以实现按需加载。 Rollup 会把动态导入的代码分割成一个单独的 chunk。 |
import('./MyModule.js') |
公共模块 | 如果多个 chunk 都依赖同一个模块,Rollup 会把这个模块提取出来,打包成一个公共的 chunk。 | 多个模块都 import 同一个文件,例如 import { sayHello } from './utils/helper.js' |
手动控制 | 可以通过Rollup的manualChunks 选项进行更细粒度的控制,将特定的模块打包到特定的chunk中。这在需要精细控制chunk大小和依赖关系时非常有用。 |
rollupOptions: { output: { manualChunks: { vendor: ['vue', 'lodash'] } } } 。这会将vue 和lodash 打包到一个名为vendor 的chunk中。 |
第二幕:Tree Shaking (摇树优化)
Tree Shaking,顾名思义,就是把代码中没用的部分“摇”掉。 就像摇一棵树,把枯枝败叶都摇下来,只留下健康的枝干。 这样可以减少打包后的文件大小,提高运行效率。
Rollup 是如何实现 Tree Shaking 的呢?主要靠以下几点:
-
ES Module 静态分析: Rollup 只能对 ES Module 进行 Tree Shaking。 因为 ES Module 的导入导出关系是静态的,可以在编译时确定哪些代码会被用到。 CommonJS 是动态的,无法进行 Tree Shaking。
-
未使用的导出 (Unused Exports): Rollup 会分析代码,找出所有未被使用的导出,并将其从最终的 bundle 中移除。
// src/utils/math.js export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; // 未被使用 } // src/main.js import { add } from './utils/math.js'; console.log(add(1, 2));
在这个例子中,
subtract
函数未被使用,会被 Rollup 移除。 -
副作用 (Side Effects): 如果一个模块有副作用,Rollup 就不会对其进行 Tree Shaking。 副作用指的是模块在导入时会执行一些操作,例如修改全局变量、发送网络请求等。
// src/utils/logger.js console.log('Logger initialized'); // 副作用 export function log(message) { console.log(message); } // src/main.js import { log } from './utils/logger.js'; log('Hello');
在这个例子中,
src/utils/logger.js
有副作用,即使log
函数没有被使用,Rollup 也不会移除console.log('Logger initialized')
。如何处理副作用?
- 纯函数: 尽量使用纯函数,避免副作用。
-
sideEffects
标记: 在package.json
中使用sideEffects
标记,告诉 Rollup 哪些文件有副作用,哪些文件没有。// package.json { "name": "my-project", "version": "1.0.0", "sideEffects": [ "./src/utils/logger.js" // 标记该文件有副作用 ] }
如果
sideEffects
设置为false
,则表示所有文件都没有副作用,Rollup 可以放心地进行 Tree Shaking。也可以更精确地指定哪些文件有副作用,哪些文件没有。 例如:
{ "name": "my-project", "version": "1.0.0", "sideEffects": [ "./src/has-side-effect.js", "*.css" // 所有 CSS 文件都有副作用 ] }
Tree Shaking 注意事项:
- 使用 ES Module: 确保使用 ES Module 语法。
- 避免副作用: 尽量避免副作用,或者使用
sideEffects
标记。 - 检查打包结果: 使用 Rollup 的可视化分析工具,检查 Tree Shaking 的效果。
第三幕:打包优化 (Bundling Optimization)
除了代码分割和 Tree Shaking,Rollup 还提供了一些其他的打包优化策略:
-
代码压缩 (Code Minification): 使用 Terser 或 esbuild 等工具,可以压缩代码,删除不必要的空格、注释等,减小文件大小。
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import terser from '@rollup/plugin-terser' //引入terser插件 export default defineConfig({ plugins: [vue()], build: { minify: 'terser', // 使用 terser 进行压缩 rollupOptions: { plugins: [terser()] //rollup配置中添加terser插件 } } })
Vite 默认使用 esbuild 进行压缩,速度很快。 如果需要更高级的压缩选项,可以使用 Terser。
-
Brotli/Gzip 压缩: 在服务器端启用 Brotli 或 Gzip 压缩,可以进一步减小文件大小。
// 使用 nginx 启用 Brotli 压缩 gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/rss+xml application/atom+xml image/svg+xml; brotli on; brotli_static on; brotli_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/rss+xml application/atom+xml image/svg+xml;
-
HTTP/2: 使用 HTTP/2 协议可以并行加载多个文件,提高加载速度。
-
预加载 (Preload): 使用
<link rel="preload">
标签可以提前加载关键资源,例如字体、图片等。<link rel="preload" href="/fonts/my-font.woff2" as="font" type="font/woff2" crossorigin>
-
预取 (Prefetch): 使用
<link rel="prefetch">
标签可以提前加载将来可能需要的资源,例如下一个页面的代码。<link rel="prefetch" href="/next-page.js" as="script">
-
懒加载 (Lazy Loading): 对于非关键的资源,可以使用懒加载,只有在需要时才加载。 例如图片、视频等。
<img src="placeholder.png" data-src="real-image.jpg" alt="My Image" loading="lazy">
打包优化策略总结:
策略 | 描述 |
---|---|
代码压缩 | 使用 Terser 或 esbuild 等工具,可以压缩代码,删除不必要的空格、注释等,减小文件大小。 |
Brotli/Gzip | 在服务器端启用 Brotli 或 Gzip 压缩,可以进一步减小文件大小。 |
HTTP/2 | 使用 HTTP/2 协议可以并行加载多个文件,提高加载速度。 |
预加载 | 使用 <link rel="preload"> 标签可以提前加载关键资源,例如字体、图片等。 |
预取 | 使用 <link rel="prefetch"> 标签可以提前加载将来可能需要的资源,例如下一个页面的代码。 |
懒加载 | 对于非关键的资源,可以使用懒加载,只有在需要时才加载。 例如图片、视频等。 |
第四幕:Vite 配置 (Vite Configuration)
Vite 的配置文件 vite.config.js
允许我们自定义 Rollup 的行为。 例如,我们可以配置 Rollup 的插件、输出格式等。
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import terser from '@rollup/plugin-terser'
export default defineConfig({
plugins: [vue()],
build: {
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源目录
minify: 'terser', // 使用 terser 进行压缩
sourcemap: true, // 生成 sourcemap
rollupOptions: {
input: {
main: 'src/main.js',
nested: 'src/nested/nested.js'
},
output: {
format: 'es', // 输出格式
entryFileNames: 'js/[name]-[hash].js', // 入口文件名
chunkFileNames: 'js/[name]-[hash].js', // chunk 文件名
assetFileNames: 'assets/[name]-[hash][extname]', // 静态资源文件名
},
plugins: [terser()] // Rollup 插件
}
}
})
常用配置选项:
build.outDir
: 输出目录。build.assetsDir
: 静态资源目录。build.minify
: 压缩方式,可选terser
、esbuild
或false
。build.sourcemap
: 是否生成 sourcemap。build.rollupOptions.input
: 入口点。build.rollupOptions.output.format
: 输出格式,可选es
、cjs
、umd
、iife
。build.rollupOptions.output.entryFileNames
: 入口文件名。build.rollupOptions.output.chunkFileNames
: chunk 文件名。build.rollupOptions.output.assetFileNames
: 静态资源文件名。build.rollupOptions.plugins
: Rollup 插件。
Vite 插件 (Vite Plugins)
Vite 插件可以扩展 Vite 的功能,例如支持 TypeScript、CSS Modules、JSX 等。 Vite 插件本质上是 Rollup 插件的封装。
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import eslintPlugin from 'vite-plugin-eslint' // 引入eslint插件
export default defineConfig({
plugins: [
vue(),
eslintPlugin({
cache: false, // 禁用缓存
})
]
})
结尾:总结与展望
今天我们一起学习了 Vite 在生产环境下如何通过 Rollup 进行代码分割、Tree Shaking 和打包优化。 简单总结一下:
- 代码分割: 把大文件拆成小文件,提高加载速度和缓存效率。
- Tree Shaking: 移除未使用的代码,减小文件大小。
- 打包优化: 使用代码压缩、Brotli/Gzip 压缩、HTTP/2 等技术,进一步提高性能。
- Vite 配置: 通过
vite.config.js
自定义 Rollup 的行为。 - Vite 插件: 扩展 Vite 的功能。
希望今天的分享能帮助大家更好地理解 Vite 的生产环境优化策略。 当然,技术在不断发展,新的优化方法也在不断涌现。 希望大家保持学习的热情,不断探索,共同进步!
感谢各位的观看! 下次再见!