rollup技术揭秘系列十五 renderModules(可能是全网最系统性的rollup源码分析文章)

138 阅读3分钟

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 内部主要逻辑有:

  1. 更新 ImportExpression 的属性。例如inlineNamespace,resolution,assertions 等等
  2. 设置 importMeta 属性和 accessedGlobalsByScope
  3. 执行 this.setIdentifierRenderResolutions() 解决变量命名冲突
  4. 生成 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 揭秘相关文章