Appearance
Rollup.js 源码摘要
调试命令:
bash
# 先构建
pnpm run build
# 进入调试流程
./dist/bin/rollup -c zhi.config.ts --configPlugin typescript看源码技能 get ✅
- 1、注意 class 类,尤其是属性;
- 2、注意方法名称,代表功能;
- 3、看函数返回了什么,返回值才是目的嘛;
rollup(命令) 源码摘要
- 获取参数。
- 解析配置。
- 唤起打包核心函数。
ts
// 获取参数 cli/cli.ts
const command = argParser(process.argv.slice(2), {
alias: commandAliases,
configuration: { "camel-case-expansion": false },
});
if (command.help || (process.argv.length <= 2 && process.stdin.isTTY)) {
console.log(`\n${help.replace("__VERSION__", version)}\n`);
} else if (command.version) {
console.log(`rollup v${version}`);
} else {
run(command);
}
// 即 run()
export default async function runRollup(
command: Record<string, any>
): Promise<void> {
try {
// 解析配置
const { options, warnings } = await getConfigs(command);
try {
for (const inputOptions of options) {
// 开启打包流程
await build(inputOptions, warnings, command.silent);
}
} catch (error: any) {
warnings.flush();
handleError(error);
}
} catch (error: any) {
handleError(error);
}
}
export default async function build(
inputOptions: MergedRollupOptions,
warnings: BatchWarnings,
silent = false
): Promise<unknown> {
// 唤起打包核心函数
const bundle = await rollup(inputOptions as any);
// 输出打包产物、写入目标文件
await Promise.all(outputOptions.map(bundle.write));
// 执行 closeBundle 钩子
await bundle.close();
}
export default function rollup(
rawInputOptions: RollupOptions
): Promise<RollupBuild> {
return rollupInternal(rawInputOptions, null);
}打包流程简介
主要分为 2 大阶段:
- 1、构建(
build) 阶段。 - 2、输出(
write/generate)阶段。generate意思是,只输出在内存中,比如ts配置文件,会被先打包成.mjs文件,再读取配置内容,随后立即删除这个临时文件。write就是真正写入磁盘,变成看得见的实体文件。所有目标模块,都写入成实体文件。
流程函数集锦
build谱系函数集锦
ts
runRollup(command)
getConfigs(command)
const configFile = await getConfigPath(command.config)
// 加载配置文件
const { options, warnings } = await loadConfigFile(configFile, command)
getConfigList(fileName, commandOptions)
getConfigFileExport(fileName, commandOptions)
loadTranspiledConfigFile(fileName, commandOptions)
const inputOptions = {..., plugins: [] }
addPluginsFromCommandOption(configPlugin, inputOptions)
// 解析配置文件
const bundle = await rollup.rollup(inputOptions) {
rollupInternal(rawInputOptions, null) {
// 获取配置文件入口配置
const { options: inputOptions } = await getInputOptions(rawInputOptions) {
const rawPlugins = getSortedValidatedPlugins('options')
const { options } = await normalizeInputOptions(
rawPlugins.reduce(..., rawInputOptions)) {
const options = {..., input: getInput(config)}
return { options }
}
return { options }
}
// 创建图谱实例
const graph = new Graph(inputOptions, watcher) {
readonly modulesById = new Map()
this.pluginDriver = new PluginDriver(this, options, options.plugins)
this.moduleLoader = new ModuleLoader(this, this.modulesById)
}
// 进入核心构建流程
await catchUnfinishedHookActions() {
try {
await graph.pluginDriver.hookParallel('buildStart')
// 构建
await graph.build() {
// 深度优先递归解析模块内容、依赖等信息,生成关系图谱
await this.generateModuleGraph() {
await this.moduleLoader.addEntryModules(
normalizeEntryModules(this.options.input),
true
) {
const newEntryModules = await this.extendLoadModulesPromise() {
Promise.all(
unresolvedEntryModules.map(({ id, importer }) => {
this.loadEntryModule(id) {
const resolveIdResult = await resolveId(unresolvedId) {
const pluginResult = await resolveIdViaPlugins(source) {
// return pluginDriver.hookFirstAndGetPlugin('resolveId')
}
return addJsExtensionIfNecessary(source)
}
return this.fetchModule(this.getResolvedIdWithDefaults(id)) {
// 创建模块实例
const module = new Module(id, ...)
this.modulesById.set(id, module)
// 添加模块源信息
const loadPromise = this.addModuleSource(id, module) {
try {
source = await this.pluginDriver.hookFirst('load', [id])
}
// 更新模块相关信息
module.updateOptions(sourceDescription)
// 设置模块相关信息
module.setSource(transform(module) {
code = await pluginDriver.hookReduceArg0('transform', module.id)
return { code, ... }
}) {
this.info.code = code
const moduleAst = ast ?? this.tryParse()
this.astContext = {code, ...}
this.scope = new ModuleScope()
this.namespace = new NamespaceVariable()
this.ast = new Program()
this.info.ast = moduleAst;
}
}.then(() => {
this.getResolveStaticDependencyPromises(module),
this.getResolveDynamicImportPromises(module),
loadAndResolveDependenciesPromise
})
this.pluginDriver.hookParallel('moduleParsed')
const resolveDependencyPromises = await loadPromise;
await this.fetchModuleDependencies(module, ...resolveDependencyPromises)
return module;
}
}
}
).then(entryModules => {
for (const [index, entryModule] of entryModules.entries()) {
addChunkNamesToModule(entryModule)
this.indexedEntryModules.push({index: xxx, module: entryModule})
this.indexedEntryModules.sort(({index: indexA}, {index: indexB}) => {
indexA > indexB ? 1 : -1
})
}
return entryModules;
})
}
await this.awaitLoadModulesPromise()
return {
entryModules: this.indexedEntryModules.map(({ module }) => module),
newEntryModules
}
}
// 标记内部、外部模块
for (const module of this.modulesById.values()) {
if (module instanceof Module) {
this.modules.push(module);
} else {
this.externalModules.push(module);
}
}
}
// 按模块及依赖执行先后排序,ast按照各自类型绑定引用
this.sortModules() {
// 排序:递归分析模块,并标记执行顺序,返回排序后的模块数组
const { orderedModules, cyclePaths } = analyseModuleExecution(this.entryModules) {
const analyseModule = (module) => {
if (module instanceof Module) {
for (const dependency of module.dependencies) {
analyseModule(dependency);
}
}
module.execIndex = nextExecIndex++;
analysedModules.add(module);
}
return { cyclePaths, orderedModules };
}
this.modules = orderedModules;
// ast绑定引用
for (const module of this.modules) {
module.bindReferences() {
this.ast!.bind() {
for (const key of this.keys) {
child?.bind()
}
}
}
}
}
// 运行node.hasEffects(),标记ast属性included是否含true,决定该节点是否入包
this.includeStatements() {
for (const module of entryModules) {
markModuleAndImpureDependenciesAsExecuted(module) {
baseModule.isExecuted = true;
}
}
for (const module of this.modules) {
if (module.info.moduleSideEffects === 'no-treeshake') {
module.includeAllInBundle() {
this.ast!.include(createInclusionContext(), true) {
// Program.ts
include() {
this.included = true
for (const node of this.body) {
if(...) node.include(context, includeChildrenRecursively)
}
}
}
this.includeAllExports(false)
} else {
module.include() {
const context = createInclusionContext()
if (this.ast!.shouldBeIncluded(context) {
return this.included || (!context.brokenFlow && this.hasEffects(createHasEffectsContext()))
}) {
this.ast!.include(context, false)
}
}
}
}
}
}
}
// 执行 buildEnd 钩子
await graph.pluginDriver.hookParallel('buildEnd')
}
}
const result = {
// 只生成。不写入
async generate() {
return handleGenerateWrite(false, ...)
}
}
return result;
}
}write/generate谱系函数集锦
ts
runRollup() {
const { options, warnings } = await getConfigs(command)
try {
for (const inputOptions of options) {
await build(inputOptions, warnings, command.silent) {
const outputOptions = inputOptions.output
// 声明 build 结果
const bundle = await rollup(inputOptions as any) {
return rollupInternal(rawInputOptions, null) {
const { options: inputOptions } = await getInputOptions(rawInputOptions)
const graph = new Graph(inputOptions, watcher)
await catchUnfinishedHookActions(graph.pluginDriver, async () => {
await graph.build(); // build细节参见 build谱系函数集锦
});
const result = {
// 写入、生成文件
async write() {
return handleGenerateWrite(true, inputOptions, unsetInputOptions, rawOutputOptions, graph) {
const { outputOptions, outputPluginDriver } = await getOutputOptionsAndPluginDriver(rawOutputOptions) {
const rawPlugins = await normalizePluginOption(rawOutputOptions.plugins)
const outputPluginDriver = inputPluginDriver.createOutputPluginDriver(rawPlugins)
return {
...(await getOutputOptions(rawOutputOptions) {
return normalizeOutputOptions(outputPluginDriver.hookReduceArg0Sync('outputOptions', [rawOutputOptions], ...))
}),
outputPluginDriver
}
}
return catchUnfinishedHookActions() {
const bundle = new Bundle(outputOptions, outputPluginDriver, graph) {
constructor() {
private readonly outputOptions
}
async generate() {
// 声明返回数据
const outputBundleBase: OutputBundle = Object.create(null)
// 创建返回数据转发代理
const outputBundle = getOutputBundle(outputBundleBase)
this.pluginDriver.setOutputBundle(outputBundle, this.outputOptions)
try {
await this.pluginDriver.hookParallel('renderStart')
// 生成chunks
const chunks = await this.generateChunks(outputBundle) {
const snippets = getGenerateCodeSnippets(this.outputOptions)
const chunks: Chunk[] = []
for(const {alias, modules} of getChunkAssignments(this.graph.entryModules)) {
const chunk = new Chunk(modules, this.inputOptions, this.outputOptions) {
constructor(
// 即第一个参数: modules
private readonly orderedModules: readonly Module[]
) {}
}
chunks.push(chunk)
}
for (const chunk of chunks) {
// 设置chunk依赖、导入、导出等
chunk.link() {
this.dependencies = getStaticDependencies(this)
for (const module of this.orderedModules) {
this.addImplicitlyLoadedBeforeFromModule(module)
this.setUpChunkImportsAndExportsForModule(module)
}
}
}
return [...chunks, ...facades]
}
// 生成chunk导出变量、模式等
for (const chunk of chunks) {
chunk.generateExports() {
const exportNamesByVariable = this.facadeModule.getExportNamesByVariable()
this.exportMode = getExportMode(...)
}
}
// 渲染chunk
await renderChunks(chunks, outputBundle = bundle) {
const renderedChunks = await Promise.all(chunks.map(chunk => chunk.render()) {
// 预备文件名
const preliminaryFileName = this.getPreliminaryFileName()
const { magicString, usedModules } = this.renderModules(preliminaryFileName.fileName) {
const { orderedModules } = this
const magicString = new MagicStringBundle({ separator: `${n}${n}` })
const usedModules: Module[] = []
const renderOptions = {...}
for (const module of orderedModules) {
const rendered = module.render(renderOptions) {
const source = this.magicString.clone()
this.ast!.render(source = code, options) {
if (code.original.startsWith('#!')) code.remove(0, start)
if (this.body.length > 0) {
renderStatementList(code, ..., options)
} else {
super.render(code, options)
}
}
return { source, ... }
}
({ source } = rendered)
magicString.addSource(source)
usedModules.push(module)
}
return { magicString, renderedSource, usedModules, ... }
}
const { intro, outro, banner, footer } = await createAddons()
return { chunk: this, magicString, preliminaryFileName, usedModules }
})
// 创建 chunk 图谱
const chunkGraph = getChunkGraph(chunks) {
return Object.fromEntries(
chunks.map(chunk => {
const renderedChunkInfo = chunk.getRenderedChunkInfo()
return [renderedChunkInfo.fileName, renderedChunkInfo]
}
)
}
// 生成chunk哈希
const { nonHashedChunksWithPlaceholders } = await transformChunksAndGenerateContentHashes(renderedChunks, chunkGraph)
const hashesByPlaceholder = generateFinalHashes(..., bundle, )
// 整合chunk
addChunksToBundle(..., bundle, nonHashedChunksWithPlaceholders) {
for (const { chunk, code, fileName, map } of nonHashedChunksWithPlaceholders) {
bundle[fileName] = chunk.finalizeChunk(...)
}
}
}
// 移除未引用的物料
removeUnreferencedAssets(outputBundle) {
for (const file of unreferencedAssets) {
delete outputBundle[file]
}
}
await this.pluginDriver.hookSeq('generateBundle')
// 返回数据
return outputBundleBase;
}
}
}
const generated = await bundle.generate(isWrite)
if (isWrite) {
await Promise.all(
Object.values(generated).map(chunk => {
graph.fileOperationQueue.run(() => writeOutputFile(chunk = outputFile, outputOptions) {
const fileName = resolve(outputOptions.dir || dirname(outputOptions.file!), outputFile.fileName)
await mkdir(dirname(fileName), { recursive: true })
return writeFile(fileName, outputFile.type === 'asset' ? outputFile.source : outputFile.code)
})
})
)
await outputPluginDriver.hookParallel('writeBundle')
}
return createOutput(generated) {
return { output: xxx }
}
}
}
}
}
return result
}
}
// 唤起写入功能
await Promise.all(outputOptions.map(bundle.write))
await bundle.close()
}
}
}
}