renderModules
上一节内容有聊到执行 chunk.render() 方法内部的时候会执行到如下代码:
async render(): Promise<ChunkRenderResult> {
//...
const { accessedGlobals, indent, magicString, renderedSource, usedModules, usesTopLevelAwait } = this.renderModules(preliminaryFileName.fileName);
//...
return {
chunk: this,
magicString,
preliminaryFileName,
usedModules
};
}
this.renderModules(preliminaryFileName.fileName) 是 "generate" 阶段的关键方法,this.renderModules 会调用 module.render() 生成 source,然后执行 magicString.addSource(source) 将多个 source 拼接成 bundle。 this.renderModules 最终返回了如下对象:
{
accessedGlobals, // 访问过的全局变量
indent, // 缩进,默认’\t‘
magicString, // MagicStringBundle
renderedSource, // magicString.trim()
usedModules, // 被包含的模块(不含手动模块(manualChunks))
usesTopLevelAwait // 是否允许顶层的 “await”, 默认 false
}
chunk.renderModules 内部主要逻辑有:
- 更新 ImportExpression 的属性。例如inlineNamespace,resolution,assertions 等等
- 设置 importMeta 属性和 accessedGlobalsByScope
- 执行 this.setIdentifierRenderResolutions() 解决变量命名冲突
- 生成 MagicStringBundle,添加 source,设置 renderedModules[module.id]
更新 ImportExpression 的属性
private setDynamicImportResolutions(fileName: string) {
const { accessedGlobalsByScope, outputOptions, pluginDriver, snippets } = this;
/**
* this.getIncludedDynamicImports() 用于获取包含了 module.dynamicImports 的 chunks:[{chunk: Chunk, externalChunk: null, facadeChunk: undefined, node: ImportExpression, resolution: Module}]
*/
for (const resolvedDynamicImport of this.getIncludedDynamicImports()) {
if (resolvedDynamicImport.chunk) {
const { chunk, facadeChunk, node, resolution } = resolvedDynamicImport;
if (chunk === this) {
//node.inlineNamespace = resolution.namespace;
node.setInternalResolution(resolution.namespace);
} else {
node.setExternalResolution(
(facadeChunk || chunk).exportMode,
resolution,
outputOptions,
snippets,
pluginDriver,
accessedGlobalsByScope,
`'${(facadeChunk || chunk).getImportPath(fileName)}'`,
!facadeChunk?.strictFacade && chunk.exportNamesByVariable.get(resolution.namespace)![0],
null
);
}
} else {
const { node, resolution } = resolvedDynamicImport;
const [resolutionString, assertions] = this.getDynamicImportStringAndAssertions(
resolution,
fileName
);
node.setExternalResolution(
'external',
resolution,
outputOptions,
snippets,
pluginDriver,
accessedGlobalsByScope,
resolutionString,
false,
assertions
);
}
}
}
设置 importMeta 属性和 accessedGlobalsByScope
private setImportMetaResolutions(fileName: string) {
const {
accessedGlobalsByScope,
includedNamespaces,
orderedModules,
outputOptions: { format, preserveModules }
} = this;
for (const module of orderedModules) {
for (const importMeta of module.importMetas) {
//处理元数据
importMeta.setResolution(format, accessedGlobalsByScope, fileName);
}
if (includedNamespaces.has(module) && !preserveModules) {
// accessedGlobalsByScope.set(ChildScope, accessedGlobals);
module.namespace.prepare(accessedGlobalsByScope);
}
}
}
this.setIdentifierRenderResolutions
private setIdentifierRenderResolutions() {
const { format, interop, namespaceToStringTag, preserveModules, externalLiveBindings } =
this.outputOptions;
const syntheticExports = new Set<SyntheticNamedExportVariable>();
for (const exportName of this.getExportNames()) {
const exportVariable = this.exportsByName.get(exportName)!;
if (
format !== 'es' &&
format !== 'system' &&
exportVariable.isReassigned &&
!exportVariable.isId
) {
exportVariable.setRenderNames('exports', exportName);
} else if (exportVariable instanceof SyntheticNamedExportVariable) {
syntheticExports.add(exportVariable);
} else {
exportVariable.setRenderNames(null, null);
}
}
for (const module of this.orderedModules) {
if (module.needsExportShim) {
this.needsExportsShim = true;
break;
}
}
const usedNames = new Set(['Object', 'Promise']);
if (this.needsExportsShim) {
usedNames.add(MISSING_EXPORT_SHIM_VARIABLE);
}
if (namespaceToStringTag) {
usedNames.add('Symbol');
}
switch (format) {
case 'system': {
usedNames.add('module').add('exports');
break;
}
case 'es': {
break;
}
case 'cjs': {
usedNames.add('module').add('require').add('__filename').add('__dirname');
}
// fallthrough
default: {
usedNames.add('exports');
for (const helper of HELPER_NAMES) {
usedNames.add(helper);
}
}
}
deconflictChunk(
this.orderedModules,
this.getDependenciesToBeDeconflicted(
format !== 'es' && format !== 'system',
format === 'amd' || format === 'umd' || format === 'iife',
interop
),
this.imports,
usedNames,
format,
interop,
preserveModules,
externalLiveBindings,
this.chunkByModule,
this.externalChunkByModule,
syntheticExports,
this.exportNamesByVariable,
this.accessedGlobalsByScope,
this.includedNamespaces
);
}
生成 MagicStringBundle,添加 source。设置 renderedModules[module.id]
//...
// 调用 MagicStringBundle 方法生成 bundle
const magicString = new MagicStringBundle({ separator: `${n}${n}` });
// 循序排序模块执行 magicString.addSource(source); 添加source
for (const module of orderedModules) {
let renderedLength = 0;
let source: MagicString | undefined;
if (module.isIncluded() || includedNamespaces.has(module)) {
//module.render 实际上就是执行了 Program.render(source, options) 方法。
const rendered = module.render(renderOptions);
({ source } = rendered);
usesTopLevelAwait ||= rendered.usesTopLevelAwait;
renderedLength = source.length();
if (renderedLength) {
if (compact && source.lastLine().includes('//')) source.append('\n');
renderedModuleSources.set(module, source);
magicString.addSource(source);
usedModules.push(module);
}
const namespace = module.namespace;
if (includedNamespaces.has(module) && !preserveModules) {
const rendered = namespace.renderBlock(renderOptions);
if (namespace.renderFirst()) hoistedSource += n + rendered;
else magicString.addSource(new MagicString(rendered));
}
const accessedGlobalVariables = accessedGlobalsByScope.get(module.scope);
if (accessedGlobalVariables) {
for (const name of accessedGlobalVariables) {
accessedGlobals.add(name);
}
}
}
const { renderedExports, removedExports } = module.getRenderedExports();
//更新 chunk.renderedModules
renderedModules[module.id] = {
get code() {
return source?.toString() ?? null;
},
originalLength: module.originalCode.length,
removedExports,
renderedExports,
renderedLength
};
}
chunk.renderModules 方法定义:
class Chunk{
//...
private renderModules(fileName: string) {
const {
accessedGlobalsByScope,
dependencies,
exportNamesByVariable,
includedNamespaces,
inputOptions: { onwarn },
isEmpty,
orderedModules,
outputOptions,
pluginDriver,
renderedModules,
snippets
} = this;
const {
compact,
dynamicImportFunction,
format,
freeze,
namespaceToStringTag,
preserveModules
} = outputOptions;
const { _, cnst, n } = snippets;
//更新 ImportExpression 的属性。例如inlineNamespace,resolution,assertions 等等
this.setDynamicImportResolutions(fileName);
//设置 importMeta 属性和 accessedGlobalsByScope
this.setImportMetaResolutions(fileName);
//防止变量命名冲突
this.setIdentifierRenderResolutions();
const magicString = new MagicStringBundle({ separator: `${n}${n}` });
const indent = getIndentString(orderedModules, outputOptions);
const usedModules: Module[] = [];
let hoistedSource = '';
const accessedGlobals = new Set<string>();
const renderedModuleSources = new Map<Module, MagicString>();
const renderOptions: RenderOptions = {
dynamicImportFunction,
exportNamesByVariable,
format,
freeze,
indent,
namespaceToStringTag,
pluginDriver,
snippets
};
let usesTopLevelAwait = false;
for (const module of orderedModules) {
let renderedLength = 0;
let source: MagicString | undefined;
if (module.isIncluded() || includedNamespaces.has(module)) {
const rendered = module.render(renderOptions);
({ source } = rendered);
usesTopLevelAwait ||= rendered.usesTopLevelAwait;
renderedLength = source.length();
if (renderedLength) {
if (compact && source.lastLine().includes('//')) source.append('\n');
renderedModuleSources.set(module, source);
magicString.addSource(source);
usedModules.push(module);
}
const namespace = module.namespace;
if (includedNamespaces.has(module) && !preserveModules) {
const rendered = namespace.renderBlock(renderOptions);
if (namespace.renderFirst()) hoistedSource += n + rendered;
else magicString.addSource(new MagicString(rendered));
}
const accessedGlobalVariables = accessedGlobalsByScope.get(module.scope);
if (accessedGlobalVariables) {
for (const name of accessedGlobalVariables) {
accessedGlobals.add(name);
}
}
}
const { renderedExports, removedExports } = module.getRenderedExports();
renderedModules[module.id] = {
get code() {
return source?.toString() ?? null;
},
originalLength: module.originalCode.length,
removedExports,
renderedExports,
renderedLength
};
}
if (hoistedSource) magicString.prepend(hoistedSource + n + n);
// eslint-disable-next-line unicorn/consistent-destructuring
if (this.needsExportsShim) {
magicString.prepend(`${n}${cnst} ${MISSING_EXPORT_SHIM_VARIABLE}${_}=${_}void 0;${n}${n}`);
}
const renderedSource = compact ? magicString : magicString.trim();
if (isEmpty && this.getExportNames().length === 0 && dependencies.size === 0) {
onwarn(errorEmptyChunk(this.getChunkName()));
}
return { accessedGlobals, indent, magicString, renderedSource, usedModules, usesTopLevelAwait };
}
}
rollup 揭秘相关文章
- rollup 技术揭秘系列一 准备篇(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列二 源码目录结构及打包入口分析(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列三 rollup 函数(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列四 graph.build(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列五 构建依赖图谱(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列六 模块排序(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列七 includeStatements(可能是全网最系统性的 rollup 源码分析文章
- rollup 技术揭秘系列八 node.hasEffects(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列九 module.include()(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十 includeStatements 总结(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十一 rollup 打包配置选项整理(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十二 handleGenerateWrite(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十三 bundle.generate(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十四 renderChunks(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十五 renderModules(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十六 Rollup 插件开发指南(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十七 rollup-cli 的开发(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十八 Rollup 打包流程示意图(可能是全网最系统性的 rollup 源码分析文章)