Rollup 最佳实践 #
库开发最佳实践 #
目录结构 #
text
my-library/
├── src/
│ ├── index.ts # 入口文件
│ ├── components/ # 组件目录
│ │ ├── index.ts
│ │ ├── Button.ts
│ │ └── Input.ts
│ ├── utils/ # 工具函数
│ │ ├── index.ts
│ │ └── helpers.ts
│ └── types/ # 类型定义
│ └── index.ts
├── dist/ # 输出目录
│ ├── my-lib.cjs.js # CommonJS
│ ├── my-lib.esm.js # ES Module
│ ├── my-lib.umd.js # UMD
│ └── my-lib.d.ts # 类型声明
├── tests/ # 测试文件
├── rollup.config.js # Rollup 配置
├── tsconfig.json # TypeScript 配置
└── package.json # 包配置
package.json 配置 #
json
{
"name": "my-library",
"version": "1.0.0",
"description": "A modern JavaScript library",
"type": "module",
"main": "dist/my-lib.cjs.js",
"module": "dist/my-lib.esm.js",
"browser": "dist/my-lib.umd.js",
"types": "dist/my-lib.d.ts",
"exports": {
".": {
"import": "./dist/my-lib.esm.js",
"require": "./dist/my-lib.cjs.js",
"browser": "./dist/my-lib.umd.js",
"types": "./dist/my-lib.d.ts"
},
"./style.css": "./dist/style.css"
},
"files": [
"dist",
"README.md"
],
"sideEffects": [
"*.css",
"*.scss"
],
"keywords": ["javascript", "library"],
"author": "Your Name",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/user/my-library"
},
"scripts": {
"dev": "rollup -c -w",
"build": "rollup -c",
"test": "vitest",
"lint": "eslint src",
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"devDependencies": {
"rollup": "^4.0.0",
"@rollup/plugin-node-resolve": "^15.0.0",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-terser": "^0.4.0",
"typescript": "^5.0.0"
}
}
完整库配置 #
javascript
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
import dts from 'rollup-plugin-dts';
const isProduction = process.env.NODE_ENV === 'production';
const config = [
{
input: 'src/index.ts',
output: [
{
file: 'dist/my-lib.cjs.js',
format: 'cjs',
sourcemap: true,
exports: 'named'
},
{
file: 'dist/my-lib.esm.js',
format: 'es',
sourcemap: true
}
],
external: ['react', 'react-dom'],
plugins: [
resolve({
extensions: ['.ts', '.tsx', '.js', '.jsx']
}),
commonjs(),
typescript({
tsconfig: './tsconfig.json',
declaration: true,
declarationDir: 'dist/types'
}),
isProduction && terser()
].filter(Boolean)
},
{
input: 'dist/types/index.d.ts',
output: {
file: 'dist/my-lib.d.ts',
format: 'es'
},
plugins: [dts()]
}
];
if (isProduction) {
config.push({
input: 'src/index.ts',
output: {
file: 'dist/my-lib.umd.js',
format: 'umd',
name: 'MyLib',
sourcemap: true,
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
},
external: ['react', 'react-dom'],
plugins: [
resolve({
extensions: ['.ts', '.tsx', '.js', '.jsx']
}),
commonjs(),
typescript({
tsconfig: './tsconfig.json'
}),
terser()
]
});
}
export default config;
入口文件设计 #
typescript
// src/index.ts
export { Button } from './components/Button';
export { Input } from './components/Input';
export { formatDate, debounce } from './utils/helpers';
export type { ButtonProps } from './components/Button';
export type { InputProps } from './components/Input';
应用开发最佳实践 #
目录结构 #
text
my-app/
├── src/
│ ├── main.js # 入口文件
│ ├── App.js # 主应用
│ ├── components/ # 组件
│ ├── pages/ # 页面
│ ├── utils/ # 工具
│ ├── styles/ # 样式
│ └── assets/ # 静态资源
├── public/
│ └── index.html
├── dist/
├── rollup.config.js
└── package.json
开发环境配置 #
javascript
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import postcss from 'rollup-plugin-postcss';
import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';
export default {
input: 'src/main.js',
output: {
file: 'dist/bundle.js',
format: 'iife',
name: 'App',
sourcemap: true
},
plugins: [
resolve({
browser: true
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**'
}),
postcss({
extract: true,
minimize: false,
sourceMap: true
}),
serve({
contentBase: ['dist', 'public'],
port: 3000,
open: true
}),
livereload({
watch: 'dist'
})
],
watch: {
clearScreen: false,
include: 'src/**'
}
};
生产环境配置 #
javascript
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import postcss from 'rollup-plugin-postcss';
import terser from '@rollup/plugin-terser';
import { visualizer } from 'rollup-plugin-visualizer';
import copy from 'rollup-plugin-copy';
import del from 'rollup-plugin-delete';
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
entryFileNames: '[name].[hash].js',
chunkFileNames: 'chunks/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]',
sourcemap: true,
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom']
}
},
plugins: [
del({ targets: 'dist/*' }),
copy({
targets: [
{ src: 'public/index.html', dest: 'dist' }
]
}),
resolve({
browser: true
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**'
}),
postcss({
extract: 'style.css',
minimize: true
}),
terser({
compress: {
drop_console: true,
drop_debugger: true
}
}),
visualizer({
filename: 'dist/stats.html',
gzipSize: true
})
]
};
性能优化最佳实践 #
1. 合理使用 external #
javascript
export default {
external: (id) => {
// 排除所有 node_modules
if (id.includes('node_modules')) {
return true;
}
// 排除特定大型库
const largeLibs = ['lodash', 'moment', 'axios'];
return largeLibs.some(lib => id.startsWith(lib));
}
};
2. 优化代码分割 #
javascript
export default {
output: {
manualChunks(id) {
// 第三方库分割
if (id.includes('node_modules')) {
// React 生态
if (id.includes('react') || id.includes('react-dom')) {
return 'vendor-react';
}
// 工具库
if (id.includes('lodash')) {
return 'vendor-lodash';
}
// 其他第三方库
return 'vendor';
}
// 业务代码分割
if (id.includes('src/features/')) {
const match = id.match(/src\/features\/([^/]+)/);
if (match) {
return `feature-${match[1]}`;
}
}
}
}
};
3. Tree-shaking 优化 #
javascript
// 使用 ES 模块导入
import { debounce } from 'lodash-es'; // ✅ 支持 Tree-shaking
// 避免 CommonJS 导入
const _ = require('lodash'); // ❌ 不支持 Tree-shaking
// 使用具名导入
import { Button } from './components'; // ✅
// 避免命名空间导入
import * as Components from './components'; // ❌ 所有都会被打包
4. 压缩优化 #
javascript
import terser from '@rollup/plugin-terser';
export default {
plugins: [
terser({
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info'],
passes: 3 // 多次压缩
},
mangle: {
properties: {
regex: /^_/, // 混淆以 _ 开头的属性
reserved: ['_id'] // 保留特定属性
}
},
format: {
comments: false // 移除注释
}
})
]
};
常见问题解决方案 #
问题一:CommonJS 模块导入问题 #
javascript
// 问题
import lodash from 'lodash'; // 可能无法正确解析
// 解决方案
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
plugins: [
resolve({
preferBuiltins: true
}),
commonjs({
transformMixedEsModules: true
})
]
};
问题二:动态导入路径问题 #
javascript
// 问题
const module = await import(`./modules/${name}.js`); // 可能无法解析
// 解决方案
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
export default {
plugins: [
dynamicImportVars({
include: ['src/**'],
exclude: ['node_modules/**']
})
]
};
问题三:CSS 样式问题 #
javascript
// 问题:CSS 未被提取
// 解决方案
import postcss from 'rollup-plugin-postcss';
export default {
plugins: [
postcss({
extract: true, // 提取到单独文件
minimize: true,
sourceMap: true,
extensions: ['.css', '.scss', '.sass', '.less']
})
]
};
问题四:图片资源问题 #
javascript
import url from '@rollup/plugin-url';
import image from '@rollup/plugin-image';
export default {
plugins: [
url({
include: ['**/*.svg', '**/*.png', '**/*.jpg', '**/*.gif'],
limit: 8192, // 小于 8KB 转 Base64
emitFiles: true,
fileName: 'assets/[name]-[hash][extname]'
}),
image({
exclude: ['node_modules/**']
})
]
};
问题五:Node.js 内置模块问题 #
javascript
import { builtinModules } from 'module';
import resolve from '@rollup/plugin-node-resolve';
export default {
external: [
...builtinModules,
...builtinModules.map(m => `node:${m}`)
],
plugins: [
resolve({
preferBuiltins: true
})
]
};
TypeScript 最佳实践 #
tsconfig.json #
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationDir": "dist/types",
"outDir": "dist",
"rootDir": "src",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": false
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
类型声明最佳实践 #
typescript
// src/types/index.ts
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
onClick?: (event: MouseEvent) => void;
children: React.ReactNode;
}
export interface InputProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
error?: string;
}
测试最佳实践 #
使用 Vitest #
javascript
// vitest.config.js
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html']
}
}
});
测试示例 #
javascript
// tests/utils.test.js
import { describe, it, expect } from 'vitest';
import { formatDate, debounce } from '../src/utils';
describe('formatDate', () => {
it('should format date correctly', () => {
const date = new Date('2024-01-01');
expect(formatDate(date)).toBe('2024-01-01');
});
});
describe('debounce', () => {
it('should debounce function calls', async () => {
let count = 0;
const fn = debounce(() => count++, 100);
fn();
fn();
fn();
expect(count).toBe(0);
await new Promise(resolve => setTimeout(resolve, 150));
expect(count).toBe(1);
});
});
发布最佳实践 #
版本管理 #
json
// package.json
{
"scripts": {
"release": "standard-version",
"release:minor": "standard-version --release-as minor",
"release:major": "standard-version --release-as major"
}
}
发布前检查 #
json
// package.json
{
"scripts": {
"prepublishOnly": "npm run lint && npm test && npm run build",
"prepack": "npm run build"
}
}
CI/CD 配置 #
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Upload coverage
uses: codecov/codecov-action@v3
文档最佳实践 #
README 模板 #
markdown
# My Library
A modern JavaScript library for building awesome applications.
## Installation
```bash
npm install my-library
Quick Start #
javascript
import { Button } from 'my-library';
function App() {
return <Button>Click me</Button>;
}
Documentation #
License #
MIT
text
### API 文档
```typescript
/**
* Formats a date to a string
* @param date - The date to format
* @param format - The format string (default: 'YYYY-MM-DD')
* @returns The formatted date string
* @example
* ```ts
* formatDate(new Date(), 'YYYY-MM-DD')
* // => '2024-01-01'
* ```
*/
export function formatDate(date: Date, format?: string): string;
总结 #
核心原则 #
- 简洁优先:保持配置简单,避免过度复杂
- 性能至上:合理使用 Tree-shaking 和代码分割
- 类型安全:使用 TypeScript 提供类型支持
- 测试覆盖:编写充分的单元测试
- 文档完善:提供清晰的使用文档
检查清单 #
- [ ] 配置文件结构清晰
- [ ] 正确设置 external 依赖
- [ ] 启用 Tree-shaking
- [ ] 配置代码分割
- [ ] 生成 Source Map
- [ ] 压缩生产代码
- [ ] 编写单元测试
- [ ] 完善文档
- [ ] 配置 CI/CD
- [ ] 版本管理规范
下一步 #
恭喜你完成了 Rollup 的学习!现在你可以:
- 开始构建你的第一个库
- 将现有项目迁移到 Rollup
- 探索更多 Rollup 插件
- 参与社区贡献
祝你使用 Rollup 愉快!
最后更新:2026-03-28