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。
参考资源
Rollup 源码,Rollup 官方文档,Magic String 文档。

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