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();
				});
			});
		});
	});
}
相关推荐
番茄比较犟10 分钟前
Combine知识点switchToLatest
前端
北京_宏哥11 分钟前
🔥《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(15)-Charles如何配置反向代理
前端·面试·charles
Process14 分钟前
前端图片技术深度解析:格式选择、渲染原理与性能优化
前端·面试·性能优化
大松鼠君15 分钟前
轿车3D展示
前端·webgl·three.js
却尘15 分钟前
URL参数传递的两种方式:查询参数与路径参数详解
前端
下辈子再也不写代码了17 分钟前
分片下载、断点续传与实时速度显示的实现方法
前端·后端·github
婷婷婷婷18 分钟前
AntV X6 常用方法
前端
LucianaiB27 分钟前
拿到Offer,租房怎么办?看我用高德MCP+腾讯云MCP,帮你分分钟搞定!
前端·后端·cursor
用户175923421502833 分钟前
D3.js - 基本用法
前端·d3.js
Mr.Liu61 小时前
小程序30-wxml语法-声明和绑定数据
前端·微信小程序·小程序