Appearance
Rollup 源代码导览
项目结构
rollup-course/
├── 04-core-feature/ # 核心功能实现
│ ├── src/
│ │ ├── parser.js # 解析器
│ │ ├── transform.js # 转换器
│ │ └── generator.js # 生成器
│ ├── test/
│ │ ├── parser.test.js
│ │ ├── transform.test.js
│ │ └── generator.test.js
│ ├── package.json
│ └── README.md
├── 05-lesson-plan.md # 课程计划
├── 01-intro.md # 背景研究
├── 02-arch.md # 架构分析
└── 03-code-walkthrough.md # 源代码导览核心文件解析
1. parser.js - 解析器
文件路径: src/parser.js
核心功能:
- 解析模块
- 生成 AST
- 解析依赖
关键代码:
javascript
import acorn from 'acorn'
import { walk } from 'estree-walker'
// 解析器
export class Parser {
constructor(options = {}) {
this.options = options
this.acornOptions = {
sourceType: 'module',
ecmaVersion: 'latest',
...options.acorn
}
}
// 解析模块
parse(source, id) {
const ast = acorn.parse(source, this.acornOptions)
const dependencies = this.extractDependencies(ast)
return {
id,
ast,
dependencies,
source
}
}
// 提取依赖
extractDependencies(ast) {
const dependencies = []
walk(ast, {
enter(node) {
if (node.type === 'ImportDeclaration') {
dependencies.push(node.source.value)
} else if (node.type === 'ExportNamedDeclaration' && node.source) {
dependencies.push(node.source.value)
} else if (node.type === 'ExportAllDeclaration') {
dependencies.push(node.source.value)
}
}
})
return dependencies
}
}设计要点:
- 使用 Acorn 解析器
- 使用 estree-walker 遍历 AST
- 提取模块依赖
2. transform.js - 转换器
文件路径: src/transform.js
核心功能:
- 应用插件
- Tree-shaking
- 代码分割
关键代码:
javascript
import MagicString from 'magic-string'
import { walk } from 'estree-walker'
// 转换器
export class Transform {
constructor(options = {}) {
this.options = options
this.plugins = options.plugins || []
}
// 转换模块
async transform(module) {
const context = {
id: module.id,
code: module.source,
ast: module.ast,
magicString: new MagicString(module.source)
}
// 应用插件
for (const plugin of this.plugins) {
if (plugin.transform) {
const result = await plugin.transform.call(context, context.code, context.id)
if (result) {
context.code = result.code || context.code
context.magicString = new MagicString(context.code)
}
}
}
// Tree-shaking
const usedExports = this.analyzeUsedExports(context.ast)
context.magicString = this.pruneUnusedExports(context.ast, context.magicString, usedExports)
return context
}
// 分析使用的导出
analyzeUsedExports(ast) {
const usedExports = new Set()
walk(ast, {
enter(node) {
if (node.type === 'Identifier') {
// 检查是否是引用的标识符
if (this.isReferencedIdentifier(node)) {
usedExports.add(node.name)
}
}
}
})
return usedExports
}
// 移除未使用的导出
pruneUnusedExports(ast, magicString, usedExports) {
walk(ast, {
enter(node) {
if (node.type === 'ExportNamedDeclaration') {
// 检查导出是否被使用
const isUsed = node.declaration.declarations.some(decl =>
usedExports.has(decl.id.name)
)
if (!isUsed) {
magicString.remove(node.start, node.end)
}
} else if (node.type === 'ExportDefaultDeclaration') {
// 检查默认导出是否被使用
if (!usedExports.has('default')) {
magicString.remove(node.start, node.end)
}
}
}
})
return magicString
}
}设计要点:
- 使用 MagicString 进行字符串操作
- 使用 estree-walker 遍历 AST
- 实现 Tree-shaking
3. generator.js - 生成器
文件路径: src/generator.js
核心功能:
- 生成代码
- 生成 Source Map
- 支持多种输出格式
关键代码:
javascript
import { walk } from 'estree-walker'
// 生成器
export class Generator {
constructor(options = {}) {
this.options = options
this.format = options.format || 'es'
}
// 生成代码
generate(modules) {
const chunks = this.createChunks(modules)
const output = []
for (const chunk of chunks) {
const code = this.generateChunk(chunk)
output.push({
code,
fileName: chunk.fileName
})
}
return output
}
// 创建代码块
createChunks(modules) {
const entryPoints = modules.filter(m => m.isEntry)
const chunks = []
for (const entry of entryPoints) {
const chunk = {
id: entry.id,
modules: [entry],
fileName: this.getFileName(entry.id)
}
// 添加依赖模块
this.addDependencies(chunk, entry, modules)
chunks.push(chunk)
}
return chunks
}
// 添加依赖
addDependencies(chunk, module, modules) {
for (const depId of module.dependencies) {
const dep = modules.find(m => m.id === depId)
if (dep && !chunk.modules.includes(dep)) {
chunk.modules.push(dep)
this.addDependencies(chunk, dep, modules)
}
}
}
// 生成代码块
generateChunk(chunk) {
let code = ''
for (const module of chunk.modules) {
code += this.generateModule(module)
}
return code
}
// 生成模块
generateModule(module) {
const magicString = module.magicString || new MagicString(module.source)
// 移除导入导出语句
this.removeImports(module.ast, magicString)
this.removeExports(module.ast, magicString)
return magicString.toString()
}
// 移除导入
removeImports(ast, magicString) {
walk(ast, {
enter(node) {
if (node.type === 'ImportDeclaration') {
magicString.remove(node.start, node.end)
}
}
})
}
// 移除导出
removeExports(ast, magicString) {
walk(ast, {
enter(node) {
if (node.type === 'ExportNamedDeclaration') {
// 保留声明,移除 export 关键字
if (node.declaration) {
magicString.remove(node.start, node.declaration.start)
} else {
magicString.remove(node.start, node.end)
}
} else if (node.type === 'ExportDefaultDeclaration') {
// 保留声明,移除 export default 关键字
magicString.remove(node.start, node.declaration.start)
} else if (node.type === 'ExportAllDeclaration') {
magicString.remove(node.start, node.end)
}
}
})
}
// 获取文件名
getFileName(id) {
const base = id.replace(/\.[^/.]+$/, '')
return `${base}.js`
}
}设计要点:
- 使用 MagicString 进行字符串操作
- 支持多种输出格式
- 生成纯净的代码
关键设计决策
1. 使用 Magic String
原因:
- 高效的字符串操作
- 保持 Source Map
- 易于修改代码
实现:
javascript
const magicString = new MagicString(source)
magicString.remove(start, end)
magicString.overwrite(start, end, replacement)2. 使用 estree-walker
原因:
- 轻量级 AST 遍历器
- 高效的遍历算法
- 易于使用
实现:
javascript
walk(ast, {
enter(node) {
// 处理节点
}
})3. Tree-shaking
原因:
- 移除未使用的代码
- 减小输出体积
- 提升性能
实现:
javascript
const usedExports = analyzeUsedExports(ast)
pruneUnusedExports(ast, magicString, usedExports)测试策略
单元测试
javascript
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { Parser } from '../src/parser.js'
describe('Parser 测试', () => {
it('应该解析模块', () => {
const parser = new Parser()
const source = 'export const x = 1'
const result = parser.parse(source, 'test.js')
assert.ok(result)
assert.ok(result.ast)
})
it('应该提取依赖', () => {
const parser = new Parser()
const source = 'import { foo } from "./foo"'
const result = parser.parse(source, 'test.js')
assert.ok(result.dependencies.includes('./foo'))
})
})性能优化
1. 增量构建
javascript
function buildIncremental(changedFiles) {
const affected = getAffectedModules(changedFiles)
for (const module of affected) {
rebuildModule(module)
}
}2. 并行处理
javascript
async function buildParallel(modules) {
const chunks = chunkArray(modules, os.cpus().length)
const promises = chunks.map(chunk =>
Promise.all(chunk.map(m => buildModule(m)))
)
return Promise.all(promises)
}3. 缓存
javascript
const cache = new Map()
function build(module) {
if (cache.has(module.id)) {
return cache.get(module.id)
}
const result = buildInternal(module)
cache.set(module.id, result)
return result
}总结
Rollup 的源代码体现了以下设计原则:
- ES6 优先:专注于 ES6 模块
- Tree-shaking:移除未使用的代码
- 高效:使用 Magic String 和 estree-walker
- 插件化:支持插件扩展
理解源代码有助于更好地使用和优化 Rollup。
参考资源

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态
