Rollup 高级特性 #
代码分割 #
什么是代码分割? #
代码分割是将代码拆分成多个文件的技术,可以实现:
- 按需加载
- 减小初始加载体积
- 共享公共依赖
- 优化缓存策略
text
┌─────────────────────────────────────────────────────────────┐
│ 代码分割效果 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 单一 Bundle 代码分割 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ │ │ main.js │ │
│ │ bundle.js │ │ (50KB) │ │
│ │ (500KB) │ ├──────────────┤ │
│ │ │ │ vendor.js │ │
│ │ │ │ (200KB) │ │
│ │ │ ├──────────────┤ │
│ │ │ │ lazy.js │ │
│ │ │ │ (100KB) │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 初始加载: 500KB 初始加载: 50KB │
│ │
└─────────────────────────────────────────────────────────────┘
自动代码分割 #
使用动态导入自动触发代码分割:
javascript
// src/main.js
import { add } from './math.js';
console.log(add(1, 2));
// 动态导入
async function loadHeavyModule() {
const { heavyFunction } = await import('./heavy.js');
heavyFunction();
}
document.getElementById('btn').addEventListener('click', loadHeavyModule);
javascript
// rollup.config.js
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
sourcemap: true
}
};
手动代码分割 #
多入口分割 #
javascript
export default {
input: {
main: 'src/main.js',
admin: 'src/admin.js',
user: 'src/user.js'
},
output: {
dir: 'dist',
format: 'es'
}
};
manualChunks 配置 #
javascript
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
manualChunks: {
vendor: ['lodash', 'axios'],
utils: ['src/utils/index.js']
}
}
};
函数式 manualChunks #
javascript
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
manualChunks(id) {
// node_modules 打包到 vendor
if (id.includes('node_modules')) {
return 'vendor';
}
// 共享组件打包到 components
if (id.includes('src/components')) {
return 'components';
}
// 工具函数打包到 utils
if (id.includes('src/utils')) {
return 'utils';
}
}
}
};
保留模块结构 #
javascript
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'es',
preserveModules: true,
preserveModulesRoot: 'src'
}
};
动态导入 #
基本用法 #
javascript
// 静态导入
import { add } from './math.js';
// 动态导入
const math = await import('./math.js');
console.log(math.add(1, 2));
条件加载 #
javascript
async function loadModule() {
if (condition) {
const module = await import('./module-a.js');
module.init();
} else {
const module = await import('./module-b.js');
module.init();
}
}
按需加载组件 #
javascript
// React 示例
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}
javascript
// Vue 示例
const AsyncComponent = defineAsyncComponent(() => import('./HeavyComponent.vue'));
动态导入配置 #
javascript
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
chunkFileNames: 'chunks/[name]-[hash].js',
dynamicImportVarsOptions: {
include: ['src/**'],
exclude: ['node_modules/**']
}
}
};
动态路径导入 #
javascript
// 动态路径
async function loadLocale(locale) {
const messages = await import(`./locales/${locale}.json`);
return messages.default;
}
// 配置
export default {
plugins: [
dynamicImportVars({
include: ['src/**'],
exclude: ['node_modules/**']
})
]
};
Tree-shaking 优化 #
Tree-shaking 原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ Tree-shaking 过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 源代码 打包后 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ export function │ │ export function │ │
│ │ add() { } │ │ add() { } │ │
│ │ │ │ │ │
│ │ export function │ └─────────────────┘ │
│ │ subtract() { }│ │
│ │ │ subtract 被移除 │
│ │ export function │ multiply 被移除 │
│ │ multiply() { }│ │
│ └─────────────────┘ │
│ │
│ import { add } from './math' │
│ │
└─────────────────────────────────────────────────────────────┘
配置 Tree-shaking #
javascript
export default {
input: 'src/main.js',
treeshake: {
annotations: true, // 使用注释标记
moduleSideEffects: true, // 模块副作用
propertyReadSideEffects: true, // 属性读取副作用
tryCatchDeoptimization: true, // try-catch 优化
unknownGlobalSideEffects: true // 未知全局副作用
},
output: {
file: 'dist/bundle.js',
format: 'es'
}
};
副作用标记 #
json
// package.json
{
"name": "my-library",
"sideEffects": false
}
json
// 或指定有副作用的文件
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js",
"./src/styles/**"
]
}
纯函数标记 #
javascript
// 使用 /* @__PURE__ */ 标记纯函数
export const config = /* @__PURE__ */ createConfig();
// 这段代码会被移除
export const unused = /* @__PURE__ */ (() => {
console.log('This will be removed');
return 'unused';
})();
避免 Tree-shaking #
javascript
// 方式一:保留导出
export function usedFunction() {}
export function unusedFunction() {} // 即使未使用也会保留
// 方式二:使用 namespace 导入
import * as utils from './utils';
// 所有导出都会被保留
// 方式三:动态属性访问
const name = 'functionName';
import { [name] as func } from './module';
缓存策略 #
启用缓存 #
javascript
import { rollup } from 'rollup';
async function build() {
const bundle = await rollup({
input: 'src/main.js',
cache: true // 启用缓存
});
await bundle.write({
file: 'dist/bundle.js',
format: 'es'
});
// 保存缓存供下次使用
return bundle.cache;
}
增量构建 #
javascript
import { rollup } from 'rollup';
let cache;
async function build() {
const bundle = await rollup({
input: 'src/main.js',
cache: cache // 使用上次构建的缓存
});
// 更新缓存
cache = bundle.cache;
await bundle.write({
file: 'dist/bundle.js',
format: 'es'
});
}
持久化缓存 #
javascript
import { rollup } from 'rollup';
import fs from 'fs';
const cacheFile = '.rollup-cache.json';
async function build() {
let cache;
// 读取缓存
if (fs.existsSync(cacheFile)) {
cache = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
}
const bundle = await rollup({
input: 'src/main.js',
cache: cache
});
await bundle.write({
file: 'dist/bundle.js',
format: 'es'
});
// 保存缓存
fs.writeFileSync(cacheFile, JSON.stringify(bundle.cache));
}
性能优化 #
并行处理 #
javascript
export default {
input: 'src/main.js',
maxParallelFileOps: 20, // 最大并行文件操作
output: {
file: 'dist/bundle.js',
format: 'es'
}
};
减少解析时间 #
javascript
import resolve from '@rollup/plugin-node-resolve';
export default {
plugins: [
resolve({
// 只解析需要的模块
only: ['lodash', 'axios']
})
]
};
外部化大型依赖 #
javascript
export default {
external: [
'react',
'react-dom',
'lodash',
'moment'
],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
lodash: '_',
moment: 'moment'
}
}
};
使用 preserveModules #
javascript
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'es',
preserveModules: true, // 保持模块结构
preserveModulesRoot: 'src'
}
};
压缩优化 #
javascript
import terser from '@rollup/plugin-terser';
export default {
plugins: [
terser({
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
},
mangle: {
properties: {
regex: /^_/
}
},
format: {
comments: false
}
})
]
};
高级输出配置 #
条目文件命名 #
javascript
export default {
input: {
main: 'src/main.js',
admin: 'src/admin.js'
},
output: {
dir: 'dist',
format: 'es',
entryFileNames: (chunkInfo) => {
// 根据入口名称命名
if (chunkInfo.name === 'main') {
return '[name].js';
}
return 'pages/[name].js';
}
}
};
代码块命名 #
javascript
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
chunkFileNames: (chunkInfo) => {
// 根据模块内容命名
if (chunkInfo.modules.some(m => m.includes('vendor'))) {
return 'vendor/[name]-[hash].js';
}
return 'chunks/[name]-[hash].js';
}
}
};
资源文件命名 #
javascript
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
assetFileNames: (assetInfo) => {
// 根据文件类型命名
if (assetInfo.name.endsWith('.css')) {
return 'css/[name]-[hash][extname]';
}
if (/\.(png|jpe?g|gif|svg)$/.test(assetInfo.name)) {
return 'images/[name]-[hash][extname]';
}
return 'assets/[name]-[hash][extname]';
}
}
};
高级插件技巧 #
插件钩子顺序 #
javascript
export default {
plugins: [
{
name: 'plugin-1',
buildStart: {
order: 'pre', // 最先执行
handler() {
console.log('Plugin 1 buildStart');
}
}
},
{
name: 'plugin-2',
buildStart: {
order: 'post', // 最后执行
handler() {
console.log('Plugin 2 buildStart');
}
}
}
]
};
插件通信 #
javascript
const sharedState = {};
export default {
plugins: [
{
name: 'plugin-1',
buildStart() {
sharedState.data = 'from plugin 1';
}
},
{
name: 'plugin-2',
buildEnd() {
console.log(sharedState.data); // 'from plugin 1'
}
}
]
};
使用 this.emitFile #
javascript
function myPlugin() {
return {
name: 'emit-file',
generateBundle(options, bundle) {
// 发射新文件
this.emitFile({
type: 'asset',
fileName: 'manifest.json',
source: JSON.stringify({
files: Object.keys(bundle)
})
});
}
};
}
模块联邦 #
基本概念 #
模块联邦允许多个独立构建的应用共享代码:
text
┌─────────────────────────────────────────────────────────────┐
│ 模块联邦架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ App A │ │ App B │ │
│ │ │ │ │ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │Component│◄─┼─────┼──│ Uses │ │ │
│ │ │ (exposed)│ │ │ │ │ │ │
│ │ └────────┘ │ │ └────────┘ │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 共享组件在运行时加载,无需重复打包 │
│ │
└─────────────────────────────────────────────────────────────┘
使用 @module-federation #
bash
npm install @module-federation/rollup --save-dev
javascript
import moduleFederation from '@module-federation/rollup';
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es'
},
plugins: [
moduleFederation({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./utils': './src/utils'
},
remotes: {
app2: 'app2@http://localhost:3001/remoteEntry.js'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
高级 Source Map #
内联 Source Map #
javascript
export default {
output: {
file: 'dist/bundle.js',
format: 'es',
sourcemap: 'inline'
}
};
隐藏 Source Map #
javascript
export default {
output: {
file: 'dist/bundle.js',
format: 'es',
sourcemap: 'hidden' // 生成但不链接
}
};
Source Map 路径转换 #
javascript
export default {
output: {
file: 'dist/bundle.js',
format: 'es',
sourcemap: true,
sourcemapPathTransform: (relativePath) => {
// 转换路径
return relativePath.replace('../src/', '');
},
sourcemapBaseUrl: 'https://example.com/maps/'
}
};
实战案例 #
案例一:按路由分割 #
javascript
// router.js
const routes = {
home: () => import('./pages/Home.js'),
about: () => import('./pages/About.js'),
contact: () => import('./pages/Contact.js')
};
async function navigate(page) {
const module = await routes[page]();
module.render();
}
案例二:按功能分割 #
javascript
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
manualChunks(id) {
if (id.includes('node_modules')) {
// 第三方库单独打包
if (id.includes('lodash')) return 'vendor-lodash';
if (id.includes('axios')) return 'vendor-axios';
return 'vendor';
}
// 按功能分割
if (id.includes('src/features/auth')) return 'feature-auth';
if (id.includes('src/features/dashboard')) return 'feature-dashboard';
if (id.includes('src/features/settings')) return 'feature-settings';
}
}
};
案例三:SSR 优化 #
javascript
// 服务端配置
export default {
input: 'src/server.js',
output: {
file: 'dist/server.js',
format: 'cjs'
},
external: ['express', 'fs', 'path'],
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
// 客户端配置
export default {
input: 'src/client.js',
output: {
dir: 'dist/client',
format: 'es',
manualChunks: {
vendor: ['react', 'react-dom']
}
}
};
下一步 #
现在你已经掌握了 Rollup 的高级特性,接下来学习 最佳实践 了解实际项目中的应用技巧!
最后更新:2026-03-28