webpack 核心编译器 八 节

  • unseal() 方法的主要目的是重置 Webpack 编译过程中的各种内部状态和缓存。这可能是一个清理过程,用于为新的编译或构建阶段做好准备。

  • 它会清除所有的 chunks、chunk 组、资源和模块属性,并重置相关的缓存,基本上清除了所有积累的数据,以确保没有旧的数据干扰下一次的构建过程。

  • 这个操作对于确保 Webpack 不会保留过时的数据非常重要,有助于避免冲突并保持构建过程的准确性。

js 复制代码
nseal() {
    // 调用 'unseal' 钩子,可能表示开始解除封印过程。
    this.hooks.unseal.call();

    // 清空 'chunks' 集合,它存储了编译过程中的所有 chunk。
    this.chunks.clear();

    // 重置 'chunkGroups' 数组,它用于跟踪 chunk 组。
    this.chunkGroups.length = 0;

    // 清空 'namedChunks' 集合,它存储了命名的 chunks(例如入口点)。
    this.namedChunks.clear();

    // 清空 'namedChunkGroups' 集合,它可能存储了命名的 chunk 组。
    this.namedChunkGroups.clear();

    // 清空 'entrypoints' 映射,它存储了应用的入口点。
    this.entrypoints.clear();

    // 重置 'additionalChunkAssets' 数组,它存储与 chunk 相关的额外资源。
    this.additionalChunkAssets.length = 0;

    // 重置 'assets' 对象,清除所有的资源。
    this.assets = {};

    // 清空 'assetsInfo' 映射,它可能存储了资源的元数据。
    this.assetsInfo.clear();

    // 从 'moduleGraph' 中移除所有模块属性,可能是重置模块的状态。
    this.moduleGraph.removeAllModuleAttributes();

    // 解冻 'moduleGraph',允许对其进行进一步修改。
    this.moduleGraph.unfreeze();

    // 清除 'moduleMemCaches2',这似乎是一个用于模块的内存缓存。
    this.moduleMemCaches2 = undefined;
}

seal 方法是 Webpack 编译过程中非常重要的一步,它处理了许多关键任务,包括优化、生成 Chunk 图、计算哈希值、生成代码等。以下是对 seal 方法流程的总结:、

  • 初始化 ChunkGraph :创建并初始化 ChunkGraph,用于跟踪模块与 Chunk 之间的关系。

  • 处理依赖关系:触发优化依赖钩子,优化并处理模块依赖关系。

  • 创建 Chunks :为每个入口点创建 Chunk,并处理 dependOnruntime 选项,管理入口点之间的关系。

  • 优化阶段 :触发多个优化钩子,如 optimizeModulesoptimizeChunksoptimizeTree,并进行模块和 Chunk 的优化。

  • 模块哈希和代码生成:计算模块的哈希值,并执行代码生成任务,包括生成代码、处理资源和计算最终的 Hash 值。

  • 处理资源:生成和处理模块及 Chunk 的资源文件,并执行相关钩子进行后处理。

  • 最终回调 :根据是否需要额外的 seal 过程,最终完成所有工作,并调用回调函数通知编译过程的完成。

js 复制代码
/**
 * 对编译进行封装,执行优化、生成 Chunk 图、计算哈希值等过程,最终生成可输出的资源。
 *
 * @param {Callback} callback - 结束时的回调函数
 * @returns {void}
 */
seal(callback) {
	/**
	 * 最终的回调函数,在整个 `seal` 过程结束后清理队列并执行用户提供的回调。
	 *
	 * @param {WebpackError=} err - 可能的错误信息
	 * @returns {void}
	 */
	const finalCallback = err => {
		this.factorizeQueue.clear();
		this.buildQueue.clear();
		this.rebuildQueue.clear();
		this.processDependenciesQueue.clear();
		this.addModuleQueue.clear();
		return callback(err);
	};

	// 创建 ChunkGraph(Chunk 依赖图),用于管理模块与 Chunk 之间的关系
	const chunkGraph = new ChunkGraph(
		this.moduleGraph,
		this.outputOptions.hashFunction
	);
	this.chunkGraph = chunkGraph;

	// 兼容旧版 Webpack,确保所有模块都设置了 chunkGraph
	if (this._backCompat) {
		for (const module of this.modules) {
			ChunkGraph.setChunkGraphForModule(module, chunkGraph);
		}
	}

	// 触发 `seal` 钩子,通知所有插件封装过程开始
	this.hooks.seal.call();

	// 优化依赖
	this.logger.time("optimize dependencies");
	while (this.hooks.optimizeDependencies.call(this.modules)) { /* 可能多次优化 */ }
	this.hooks.afterOptimizeDependencies.call(this.modules);
	this.logger.timeEnd("optimize dependencies");

	// 创建 Chunk(代码块)
	this.logger.time("create chunks");
	this.hooks.beforeChunks.call();
	this.moduleGraph.freeze("seal"); // 冻结模块图,防止后续修改

	/** @type {Map<Entrypoint, Module[]>} */
	const chunkGraphInit = new Map();

	// 遍历所有入口点,创建 Chunk 和 Entrypoint
	for (const [name, { dependencies, includeDependencies, options }] of this.entries) {
		const chunk = this.addChunk(name);
		if (options.filename) {
			chunk.filenameTemplate = options.filename;
		}
		const entrypoint = new Entrypoint(options);
		if (!options.dependOn && !options.runtime) {
			entrypoint.setRuntimeChunk(chunk);
		}
		entrypoint.setEntrypointChunk(chunk);
		this.namedChunkGroups.set(name, entrypoint);
		this.entrypoints.set(name, entrypoint);
		this.chunkGroups.push(entrypoint);
		connectChunkGroupAndChunk(entrypoint, chunk);

		const entryModules = new Set();

		// 处理入口点的依赖模块
		for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {
			entrypoint.addOrigin(null, { name }, dep.request);
			const module = this.moduleGraph.getModule(dep);
			if (module) {
				chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
				entryModules.add(module);
				const modulesList = chunkGraphInit.get(entrypoint);
				if (modulesList === undefined) {
					chunkGraphInit.set(entrypoint, [module]);
				} else {
					modulesList.push(module);
				}
			}
		}

		// 计算模块的深度信息
		this.assignDepths(entryModules);

		// 处理 includeDependencies(额外包含的依赖模块)
		const mapAndSort = deps => 
			deps.map(dep => this.moduleGraph.getModule(dep)).filter(Boolean).sort(compareModulesByIdentifier);

		const includedModules = [
			...mapAndSort(this.globalEntry.includeDependencies),
			...mapAndSort(includeDependencies)
		];

		let modulesList = chunkGraphInit.get(entrypoint);
		if (modulesList === undefined) {
			chunkGraphInit.set(entrypoint, (modulesList = []));
		}
		for (const module of includedModules) {
			this.assignDepth(module);
			modulesList.push(module);
		}
	}

	// 处理 runtime 和 dependOn 选项
	const runtimeChunks = new Set();
	outer: for (const [name, { options: { dependOn, runtime } }] of this.entries) {
		if (dependOn && runtime) {
			const err = new WebpackError(
				`Entrypoint '${name}' 不能同时有 'dependOn' 和 'runtime' 选项。`
			);
			err.chunk = this.entrypoints.get(name).getEntrypointChunk();
			this.errors.push(err);
		}

		if (dependOn) {
			const entry = this.entrypoints.get(name);
			const referencedChunks = entry.getEntrypointChunk().getAllReferencedChunks();
			const dependOnEntries = [];
			for (const dep of dependOn) {
				const dependency = this.entrypoints.get(dep);
				if (!dependency) {
					throw new Error(`Entrypoint '${name}' 依赖 '${dep}',但未找到该入口`);
				}
				if (referencedChunks.has(dependency.getEntrypointChunk())) {
					const err = new WebpackError(
						`Entrypoints '${name}' 和 '${dep}' 存在循环依赖`
					);
					err.chunk = entry.getEntrypointChunk();
					this.errors.push(err);
					entry.setRuntimeChunk(entry.getEntrypointChunk());
					continue outer;
				}
				dependOnEntries.push(dependency);
			}
			for (const dependency of dependOnEntries) {
				connectChunkGroupParentAndChild(dependency, entry);
			}
		} else if (runtime) {
			// 处理 runtime 选项
			const entry = this.entrypoints.get(name);
			let chunk = this.namedChunks.get(runtime);
			if (chunk) {
				if (!runtimeChunks.has(chunk)) {
					const err = new WebpackError(`Entrypoint '${name}' 的 'runtime' 选项指向另一个入口点 '${runtime}',不合法`);
					err.chunk = entry.getEntrypointChunk();
					this.errors.push(err);
					entry.setRuntimeChunk(entry.getEntrypointChunk());
					continue;
				}
			} else {
				chunk = this.addChunk(runtime);
				chunk.preventIntegration = true;
				runtimeChunks.add(chunk);
			}
			entry.unshiftChunk(chunk);
			chunk.addGroup(entry);
			entry.setRuntimeChunk(chunk);
		}
	}

	// 生成 Chunk 依赖图
	buildChunkGraph(this, chunkGraphInit);
	this.hooks.afterChunks.call(this.chunks);
	this.logger.timeEnd("create chunks");

	// 触发优化阶段的钩子
	this.logger.time("optimize");
	this.hooks.optimize.call();

	while (this.hooks.optimizeModules.call(this.modules)) { /* 可能多次优化 */ }
	this.hooks.afterOptimizeModules.call(this.modules);

	while (this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups)) { /* 可能多次优化 */ }
	this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);

	// 进一步优化 Chunk 和模块的关系
	this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
		if (err) return finalCallback(err);

		this.hooks.afterOptimizeTree.call(this.chunks, this.modules);
		this.hooks.optimizeChunkModules.callAsync(this.chunks, this.modules, err => {
			if (err) return finalCallback(err);
			
			// 计算模块和 Chunk 的哈希值
			this.createModuleHashes();
			this.createHash();

			// 生成最终的资源
			this.hooks.processAssets.callAsync(this.assets, err => {
				if (err) return finalCallback(err);
				this.hooks.afterProcessAssets.call(this.assets);

				// 检查是否需要额外的 seal 过程
				if (this.hooks.needAdditionalSeal.call()) {
					this.unseal();
					return this.seal(callback);
				}

				// 结束 seal 过程
				this.hooks.afterSeal.callAsync(err => {
					if (err) return finalCallback(err);
					this.fileSystemInfo.logStatistics();
					finalCallback();
				});
			});
		});
	});
}
相关推荐
IT瘾君2 小时前
JavaWeb:Html&Css
前端·html
264玫瑰资源库2 小时前
问道数码兽 怀旧剧情回合手游源码搭建教程(反查重优化版)
java·开发语言·前端·游戏
喝拿铁写前端2 小时前
从圣经Babel到现代编译器:没开玩笑,普通程序员也能写出自己的编译器!
前端·架构·前端框架
HED2 小时前
VUE项目发版后用户访问的仍然是旧页面?原因和解决方案都在这啦!
前端·vue.js
拉不动的猪3 小时前
前端自做埋点,我们应该要注意的几个问题
前端·javascript·面试
王景程3 小时前
如何测试短信接口
java·服务器·前端
安冬的码畜日常3 小时前
【AI 加持下的 Python 编程实战 2_10】DIY 拓展:从扫雷小游戏开发再探问题分解与 AI 代码调试能力(中)
开发语言·前端·人工智能·ai·扫雷游戏·ai辅助编程·辅助编程
小杨升级打怪中3 小时前
前端面经-JS篇(三)--事件、性能优化、防抖与节流
前端·javascript·xss
清风细雨_林木木3 小时前
Vue开发网站会有“#”原因是前端路由使用了 Hash 模式
前端·vue.js·哈希算法
鸿蒙布道师4 小时前
OpenAI为何觊觎Chrome?AI时代浏览器争夺战背后的深层逻辑
前端·人工智能·chrome·深度学习·opencv·自然语言处理·chatgpt