webpack 核心编译器 十六 节

getPath(filename, data = {})

总结:根据 filename 模板和 hash 插值,生成资源路径字符串。

js 复制代码
/**
 * 获取插值后的资源路径字符串。
 * 如果 data 中没有提供 hash,则补上 this.hash。
 * 然后调用 getAssetPath 生成最终的路径。
 */
getPath(filename, data = {}) {
	if (!data.hash) {
		data = {
			hash: this.hash,
			...data
		};
	}
	return this.getAssetPath(filename, data);
}

getPathWithInfo(filename, data = {})

总结:返回带资源信息(assetInfo)的插值路径。

js 复制代码
/**
 * 获取带 assetInfo 的插值路径。
 * 逻辑与 getPath 类似,但调用 getAssetPathWithInfo,
 * 返回的结果包括 path 和 assetInfo 两部分。
 */
getPathWithInfo(filename, data = {}) {
	if (!data.hash) {
		data = {
			hash: this.hash,
			...data
		};
	}
	return this.getAssetPathWithInfo(filename, data);
}

getAssetPath(filename, data)

总结:通过调用 assetPath 钩子插值资源路径字符串。

js 复制代码
/**
 * 插值资源路径。
 * 如果 filename 是函数,先执行它。
 * 然后将其传给 assetPath 钩子处理。
 */
getAssetPath(filename, data) {
	return this.hooks.assetPath.call(
		typeof filename === "function" ? filename(data) : filename,
		data,
		undefined
	);
}

getAssetPathWithInfo(filename, data)

总结:获取插值后的路径和可被插件使用的资源信息对象。

js 复制代码
/**
 * 获取插值路径和附带的资源信息。
 * 创建 assetInfo 空对象,传给钩子,供插件修改。
 * 返回 path 和 info。
 */
getAssetPathWithInfo(filename, data) {
	const assetInfo = {};
	const newPath = this.hooks.assetPath.call(
		typeof filename === "function" ? filename(data, assetInfo) : filename,
		data,
		assetInfo
	);
	return { path: newPath, info: assetInfo };
}

getWarnings()

总结:通过钩子处理构建过程中收集的 warnings。

js 复制代码
/**
 * 调用 processWarnings 钩子处理警告。
 */
getWarnings() {
	return this.hooks.processWarnings.call(this.warnings);
}

getErrors()

总结:通过钩子处理构建过程中收集的 errors。

js 复制代码
/**
 * 调用 processErrors 钩子处理错误。
 */
getErrors() {
	return this.hooks.processErrors.call(this.errors);
}

createChildCompiler(name, outputOptions, plugins)

总结:创建一个子编译器实例,用于插件中执行嵌套编译任务。

js 复制代码
/**
 * 创建一个子编译器。
 * 可用于插件中运行嵌套的 webpack 构建(如 HtmlWebpackPlugin)。
 * 子编译器会复制父编译器的 hook 和 plugin,但允许使用不同配置。
 */
createChildCompiler(name, outputOptions, plugins) {
	const idx = this.childrenCounters[name] || 0;
	this.childrenCounters[name] = idx + 1;
	return this.compiler.createChildCompiler(
		this,
		name,
		idx,
		outputOptions,
		plugins
	);
}

executeModule 是 Webpack 中用于"构建时执行模块"的方法,关键用于某些插件(如 ModuleFederationPlugin、MiniCssExtractPlugin)需要在构建阶段获取模块执行结果的场景。

核心步骤包括:

  1. 收集模块及其依赖:递归收集所有相关模块,等待其构建与依赖处理完成。
  2. 创建 chunk 与 entrypoint:模拟一个构建环境,为模块分配 chunkGraph、chunk。
  3. 生成模块 hash 和代码:计算模块 hash,进行代码生成。
  4. 准备执行上下文:为每个模块准备执行上下文、缓存信息和资源依赖。
  5. 执行模块 :通过自定义的 __webpack_require__ 执行入口模块,并收集其 exports
  6. 返回结果:将模块导出值与构建资源等信息回传。
js 复制代码
/**
 * 执行一个模块,并在构建时收集其依赖、生成代码、执行并获取其导出内容
 *
 * @param {Module} module - 需要执行的入口模块
 * @param {ExecuteModuleOptions} options - 执行模块的附加选项,如 entry 相关配置
 * @param {ExecuteModuleCallback} callback - 执行完成后的回调函数
 */
executeModule(module, options, callback) {
  // 初始化模块集合,包含初始模块
  const modules = new Set([module]);

  // 递归收集所有依赖模块,确保所有模块都完成构建和依赖处理
  processAsyncTree(
    modules,
    10, // 并发限制
    (module, push, callback) => {
      // 等待模块构建完成
      this.buildQueue.waitFor(module, err => {
        if (err) return callback(err);
        // 等待依赖处理完成
        this.processDependenciesQueue.waitFor(module, err => {
          if (err) return callback(err);
          // 获取当前模块的所有依赖模块(出边模块)
          for (const { module: m } of this.moduleGraph.getOutgoingConnections(module)) {
            const size = modules.size;
            modules.add(m); // 添加依赖模块
            if (modules.size !== size) push(m); // 如果是新增模块,则继续递归处理
          }
          callback();
        });
      });
    },
    err => {
      if (err) return callback(/** @type {WebpackError} */(err));

      // 构建运行时信息,包括 chunkGraph、chunk、entrypoint
      const chunkGraph = new ChunkGraph(this.moduleGraph, this.outputOptions.hashFunction);
      const runtime = "build time"; // 运行时名
      const { hashFunction, hashDigest, hashDigestLength } = this.outputOptions;
      const runtimeTemplate = this.runtimeTemplate;

      // 创建一个虚拟的 chunk 和 entrypoint 用于该模块的执行
      const chunk = new Chunk("build time chunk", this._backCompat);
      chunk.id = /** @type {ChunkId} */ (chunk.name);
      chunk.ids = [chunk.id];
      chunk.runtime = runtime;

      const entrypoint = new Entrypoint({
        runtime,
        chunkLoading: false,
        ...options.entryOptions
      });

      // 建立 chunk、entrypoint 与模块之间的关系
      chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
      connectChunkGroupAndChunk(entrypoint, chunk);
      entrypoint.setRuntimeChunk(chunk);
      entrypoint.setEntrypointChunk(chunk);

      const chunks = new Set([chunk]);

      // 为模块分配 ID 并连接 chunk 与模块
      for (const module of modules) {
        const id = module.identifier();
        chunkGraph.setModuleId(module, id);
        chunkGraph.connectChunkAndModule(chunk, module);
      }

      /** @type {WebpackError[]} */
      const errors = [];

      // 为所有模块生成哈希值
      for (const module of modules) {
        this._createModuleHash(
          module,
          chunkGraph,
          runtime,
          hashFunction,
          runtimeTemplate,
          hashDigest,
          hashDigestLength,
          errors
        );
      }

      const codeGenerationResults = new CodeGenerationResults(this.outputOptions.hashFunction);

      // 生成模块代码的函数
      const codeGen = (module, callback) => {
        this._codeGenerationModule(
          module,
          runtime,
          [runtime],
          chunkGraph.getModuleHash(module, runtime),
          this.dependencyTemplates,
          chunkGraph,
          this.moduleGraph,
          runtimeTemplate,
          errors,
          codeGenerationResults,
          (err, codeGenerated) => {
            callback(err);
          }
        );
      };

      // 将错误写入 this.errors 中
      const reportErrors = () => {
        if (errors.length > 0) {
          errors.sort(compareSelect(err => err.module, compareModulesByIdentifier));
          for (const error of errors) {
            this.errors.push(error);
          }
          errors.length = 0;
        }
      };

      // 为每个模块生成代码
      asyncLib.eachLimit(modules, 10, codeGen, err => {
        if (err) return callback(err);
        reportErrors();

        // 设置临时 chunkGraph(用于兼容)
        const old = this.chunkGraph;
        this.chunkGraph = chunkGraph;

        // 处理运行时依赖
        this.processRuntimeRequirements({
          chunkGraph,
          modules,
          chunks,
          codeGenerationResults,
          chunkGraphEntries: chunks
        });

        this.chunkGraph = old;

        const runtimeModules = chunkGraph.getChunkRuntimeModulesIterable(chunk);

        // 为运行时代码模块生成哈希
        for (const module of runtimeModules) {
          modules.add(module);
          this._createModuleHash(
            module,
            chunkGraph,
            runtime,
            hashFunction,
            runtimeTemplate,
            hashDigest,
            hashDigestLength,
            errors
          );
        }

        // 为运行时模块生成代码
        asyncLib.eachLimit(runtimeModules, 10, codeGen, err => {
          if (err) return callback(err);
          reportErrors();

          /** @type {Map<Module, ExecuteModuleArgument>} */
          const moduleArgumentsMap = new Map();
          /** @type {Map<string, ExecuteModuleArgument>} */
          const moduleArgumentsById = new Map();

          // 初始化模块依赖集合
          const fileDependencies = new LazySet();
          const contextDependencies = new LazySet();
          const missingDependencies = new LazySet();
          const buildDependencies = new LazySet();

          // 初始化生成的资源集合
          const assets = new Map();

          let cacheable = true;

          // 创建模块执行上下文
          const context = {
            assets,
            __webpack_require__: undefined,
            chunk,
            chunkGraph
          };

          // 准备每个模块的执行
          asyncLib.eachLimit(modules, 10, (module, callback) => {
            const codeGenerationResult = codeGenerationResults.get(module, runtime);
            const moduleArgument = {
              module,
              codeGenerationResult,
              preparedInfo: undefined,
              moduleObject: undefined
            };

            moduleArgumentsMap.set(module, moduleArgument);
            moduleArgumentsById.set(module.identifier(), moduleArgument);

            // 添加模块的依赖
            module.addCacheDependencies(
              fileDependencies,
              contextDependencies,
              missingDependencies,
              buildDependencies
            );

            // 判断是否可缓存
            if (module.buildInfo?.cacheable === false) {
              cacheable = false;
            }

            // 将模块的 asset 信息放入资源集合
            if (module.buildInfo && module.buildInfo.assets) {
              const { assets: moduleAssets, assetsInfo } = module.buildInfo;
              for (const assetName of Object.keys(moduleAssets)) {
                assets.set(assetName, {
                  source: moduleAssets[assetName],
                  info: assetsInfo ? assetsInfo.get(assetName) : undefined
                });
              }
            }

            // 执行 prepareModuleExecution 钩子
            this.hooks.prepareModuleExecution.callAsync(
              moduleArgument,
              context,
              callback
            );
          }, err => {
            if (err) return callback(err);

            let exports;
            try {
              const {
                strictModuleErrorHandling,
                strictModuleExceptionHandling
              } = this.outputOptions;

              // 自定义的 __webpack_require__ 实现
              const __webpack_require__ = id => {
                const cached = moduleCache[id];
                if (cached !== undefined) {
                  if (cached.error) throw cached.error;
                  return cached.exports;
                }
                const moduleArgument = moduleArgumentsById.get(id);
                return __webpack_require_module__(moduleArgument, id);
              };

              // 初始化模块缓存和拦截器
              const interceptModuleExecution = (__webpack_require__[RuntimeGlobals.interceptModuleExecution.replace(`${RuntimeGlobals.require}.`, "")] = []);
              const moduleCache = (__webpack_require__[RuntimeGlobals.moduleCache.replace(`${RuntimeGlobals.require}.`, "")] = {});

              context.__webpack_require__ = __webpack_require__;

              // 内部模块执行函数
              const __webpack_require_module__ = (moduleArgument, id) => {
                const execOptions = {
                  id,
                  module: {
                    id,
                    exports: {},
                    loaded: false,
                    error: undefined
                  },
                  require: __webpack_require__
                };

                // 执行所有模块拦截器
                for (const handler of interceptModuleExecution) {
                  handler(execOptions);
                }

                const module = moduleArgument.module;
                this.buildTimeExecutedModules.add(module);

                const moduleObject = execOptions.module;
                moduleArgument.moduleObject = moduleObject;

                try {
                  if (id) moduleCache[id] = moduleObject;

                  // 执行 executeModule 钩子
                  tryRunOrWebpackError(() => {
                    this.hooks.executeModule.call(moduleArgument, context);
                  }, "Compilation.hooks.executeModule");

                  moduleObject.loaded = true;
                  return moduleObject.exports;
                } catch (execErr) {
                  if (strictModuleExceptionHandling) {
                    if (id) delete moduleCache[id];
                  } else if (strictModuleErrorHandling) {
                    moduleObject.error = execErr;
                  }
                  if (!execErr.module) execErr.module = module;
                  throw execErr;
                }
              };

              // 执行所有 runtime 模块
              for (const runtimeModule of chunkGraph.getChunkRuntimeModulesInOrder(chunk)) {
                __webpack_require_module__(moduleArgumentsMap.get(runtimeModule));
              }

              // 执行入口模块
              exports = __webpack_require__(module.identifier());

            } catch (execErr) {
              const err = new WebpackError(
                `Execution of module code from module graph (${module.readableIdentifier(this.requestShortener)}) failed: ${execErr.message}`
              );
              err.stack = execErr.stack;
              err.module = execErr.module;
              return callback(err);
            }

            // 返回最终执行结果
            callback(null, {
              exports,
              assets,
              cacheable,
              fileDependencies,
              contextDependencies,
              missingDependencies,
              buildDependencies
            });
          });
        });
      });
    }
  );
}

checkConstraints()

总结:检查模块 ID 是否唯一,以及 chunkGraph 与 module 集合的一致性。

js 复制代码
/**
 * 检查 chunkGraph 与模块集合的完整性:
 * - 没有重复 moduleId
 * - chunk 中所有模块都存在于 this.modules
 * - chunkGroup 的约束也会检查
 */
checkConstraints() {
	const chunkGraph = this.chunkGraph;
	const usedIds = new Set();

	for (const module of this.modules) {
		if (module.type === WEBPACK_MODULE_TYPE_RUNTIME) continue;
		const moduleId = chunkGraph.getModuleId(module);
		if (moduleId === null) continue;
		if (usedIds.has(moduleId)) {
			throw new Error(`checkConstraints: duplicate module id ${moduleId}`);
		}
		usedIds.add(moduleId);
	}

	for (const chunk of this.chunks) {
		for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
			if (!this.modules.has(module)) {
				throw new Error(`checkConstraints: module in chunk but not in compilation ${chunk.debugId} ${module.debugId}`);
			}
		}
		for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
			if (!this.modules.has(module)) {
				throw new Error(`checkConstraints: entry module in chunk but not in compilation ${chunk.debugId} ${module.debugId}`);
			}
		}
	}

	for (const chunkGroup of this.chunkGroups) {
		chunkGroup.checkConstraints();
	}
}
相关推荐
木木黄木木2 小时前
html5炫酷3D文字效果项目开发实践
前端·3d·html5
Li_Ning212 小时前
【接口重复请求】axios通过AbortController解决页面切换过快,接口重复请求问题
前端
胡八一3 小时前
Window调试 ios 的 Safari 浏览器
前端·ios·safari
Dontla3 小时前
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)
前端·javascript
再学一点就睡3 小时前
深拷贝与浅拷贝:代码世界里的永恒与瞬间
前端·javascript
CrimsonHu3 小时前
B站首页的 Banner 这么好看,我用原生 JS + 三大框架统统给你复刻一遍!
前端·javascript·css
Enti7c3 小时前
前端表单输入框验证
前端·javascript·jquery
拉不动的猪3 小时前
几种比较实用的指令举例
前端·javascript·面试
麻芝汤圆3 小时前
MapReduce 的广泛应用:从数据处理到智能决策
java·开发语言·前端·hadoop·后端·servlet·mapreduce
与妖为邻4 小时前
自动获取屏幕尺寸信息的html文件
前端·javascript·html