webpack 运行时模版 第 五 节 /lib/RuntimeTemplate.js

  • runtimeConditionExpression 方法

    • 生成运行时条件表达式。
    • 如果 runtimeCondition 未定义,返回 "true"
    • 如果 runtimeCondition 是布尔值,返回该值的字符串形式。
    • 处理复杂的 runtimeCondition,从中提取正向和反向的 runtimeId 集合。
    • 使用 compileBooleanMatcher.fromLists 生成最终的条件表达式。
  • importStatement 方法

    • 用于生成模块的导入语句。
    • 如果模块不存在,则返回缺失模块的错误语句。
    • 如果模块没有 ID,根据是否为弱依赖(weak)返回错误或警告。
    • 根据模块的导出类型(例如动态导入),决定是否添加兼容的默认导出语句。
    • 将导入语句添加到运行时要求中,并根据是否更新现有变量来决定是否使用 var 声明。
  • exportFromImport 方法

    • 用于从已导入的模块中导出指定的成员。
    • 处理默认导出时的特定情况,并生成适当的访问表达式。
    • 如果模块是动态的,处理默认导出的调用情况。
    • 根据导出的类型(例如 default-onlydefault-with-named)调整导出名称,生成适当的代码。
    • 如果模块不符合要求,返回未使用的导出警告或错误。
js 复制代码
/**
 * 生成一个判断 runtime 条件的表达式字符串。
 * 常用于确定某段 runtime-specific 的代码是否应在当前 runtime 下执行。
 *
 * @param {Object} options
 * @param {ChunkGraph} options.chunkGraph - ChunkGraph 实例,用于获取模块 runtimeId。
 * @param {RuntimeSpec | boolean | undefined} options.runtimeCondition - 条件 runtime。
 * @param {RuntimeSpec} options.runtime - 当前运行时上下文。
 * @param {Set<string>} options.runtimeRequirements - 收集 runtime 的依赖变量。
 * @returns {string} 表达式字符串,例如:"__webpack_require__.r === 'main'"
 */
runtimeConditionExpression({
  chunkGraph,
  runtimeCondition,
  runtime,
  runtimeRequirements
}) {
  // 如果没有设置 runtime 条件,默认始终为 true
  if (runtimeCondition === undefined) return "true";

  // 如果 runtime 条件是布尔值,直接转换为字符串返回
  if (typeof runtimeCondition === "boolean") return `${runtimeCondition}`;

  // 正向匹配的 runtimeId 集合
  const positiveRuntimeIds = new Set();
  forEachRuntime(runtimeCondition, runtime =>
    positiveRuntimeIds.add(`${chunkGraph.getRuntimeId(/** @type {string} */ (runtime))}`)
  );

  // 反向排除的 runtimeId 集合
  const negativeRuntimeIds = new Set();
  forEachRuntime(subtractRuntime(runtime, runtimeCondition), runtime =>
    negativeRuntimeIds.add(`${chunkGraph.getRuntimeId(/** @type {string} */ (runtime))}`)
  );

  // 注册对 __webpack_require__.r 的依赖
  runtimeRequirements.add(RuntimeGlobals.runtimeId);

  // 生成条件匹配表达式,例如 __webpack_require__.r === "main"
  return compileBooleanMatcher.fromLists(
    Array.from(positiveRuntimeIds),
    Array.from(negativeRuntimeIds)
  )(RuntimeGlobals.runtimeId);
},

/**
 * 构造模块导入语句,返回导入声明 + default 导出兼容语句。
 *
 * @param {Object} options
 * @param {boolean} options.update - 是否是更新变量(false 表示声明)。
 * @param {Module=} options.module - 被导入的模块。
 * @param {ChunkGraph} options.chunkGraph - ChunkGraph 实例。
 * @param {string} options.request - 请求路径。
 * @param {string} options.importVar - 被赋值的变量名。
 * @param {Module} options.originModule - 发起导入的模块。
 * @param {boolean=} options.weak - 是否为弱依赖。
 * @param {Set<string>} options.runtimeRequirements - runtime 依赖收集器。
 * @returns {[string, string]} 导入语句 + 兼容 default 变量定义。
 */
importStatement({
  update,
  module,
  chunkGraph,
  request,
  importVar,
  originModule,
  weak,
  runtimeRequirements
}) {
  // 如果没有 module,说明模块缺失,输出错误提示代码
  if (!module) {
    return [
      this.missingModuleStatement({ request }),
      ""
    ];
  }

  // module 没有 ID,说明未包含在 chunk 中
  if (chunkGraph.getModuleId(module) === null) {
    // 弱依赖返回 warning,否则抛错
    if (weak) {
      return [
        this.weakError({ module, chunkGraph, request, type: "statements" }),
        ""
      ];
    }
    throw new Error(
      `RuntimeTemplate.importStatement(): ${noModuleIdErrorMessage(module, chunkGraph)}`
    );
  }

  // 获取模块 ID(可能会添加到 runtimeRequirements)
  const moduleId = this.moduleId({
    module,
    chunkGraph,
    request,
    weak
  });

  // 如果不是 update,就声明变量
  const optDeclaration = update ? "" : "var ";

  // 判断被导入模块的导出类型
  const exportsType = module.getExportsType(
    chunkGraph.moduleGraph,
    /** @type {BuildMeta} */ (originModule.buildMeta).strictHarmonyModule
  );

  // 添加对 __webpack_require__ 的 runtime 依赖
  runtimeRequirements.add(RuntimeGlobals.require);

  // 正常的导入语句
  const importContent = `/* harmony import */ ${optDeclaration}${importVar} = ${RuntimeGlobals.require}(${moduleId});\n`;

  // 如果是 dynamic 类型(即同时支持 CommonJS 和 ESM),兼容 default
  if (exportsType === "dynamic") {
    runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport);
    return [
      importContent,
      `/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${importVar});\n`
    ];
  }

  // 非 dynamic 类型不需要处理 default
  return [importContent, ""];
},

/**
 * 从一个 import 的模块中导出指定成员。
 *
 * @param {Object} options
 * @param {ModuleGraph} options.moduleGraph - 模块依赖图。
 * @param {Module=} options.module - 被导出的模块。
 * @param {string} options.request - 请求路径。
 * @param {string|string[]=} options.exportName - 要导出的成员名称。
 * @param {Module} options.originModule - 发起导出的模块。
 * @param {boolean} options.asiSafe - 是否是 ASI 安全(自动分号插入)。
 * @param {boolean} options.isCall - 是否用于调用场景。
 * @param {boolean=} options.callContext - 是否保留 this 绑定。
 * @param {boolean} options.defaultInterop - 是否处理 default 兼容。
 * @param {string} options.importVar - 模块变量名。
 * @param {InitFragment[]} options.initFragments - 初始化片段收集器。
 * @param {RuntimeSpec} options.runtime - 当前运行时上下文。
 * @param {Set<string>} options.runtimeRequirements - runtime 依赖收集器。
 * @returns {string} 最终生成的访问语句。
 */
exportFromImport({
  moduleGraph,
  module,
  request,
  exportName,
  originModule,
  asiSafe,
  isCall,
  callContext,
  defaultInterop,
  importVar,
  initFragments,
  runtime,
  runtimeRequirements
}) {
  // 模块缺失,返回报错表达式
  if (!module) return this.missingModule({ request });

  // 标准化 exportName 为数组
  if (!Array.isArray(exportName)) {
    exportName = exportName ? [exportName] : [];
  }

  const exportsType = module.getExportsType(
    moduleGraph,
    /** @type {BuildMeta} */ (originModule.buildMeta).strictHarmonyModule
  );

  // 处理 defaultInterop 情况
  if (defaultInterop) {
    if (exportName.length > 0 && exportName[0] === "default") {
      switch (exportsType) {
        case "dynamic":
          // 动态模块:default 调用返回对象
          if (isCall) {
            return `${importVar}_default()${propertyAccess(exportName, 1)}`;
          }
          return asiSafe
            ? `(${importVar}_default()${propertyAccess(exportName, 1)})`
            : asiSafe === false
              ? `;(${importVar}_default()${propertyAccess(exportName, 1)})`
              : `${importVar}_default.a${propertyAccess(exportName, 1)}`;
        case "default-only":
        case "default-with-named":
          // 直接剥离 default,改为访问子字段
          exportName = exportName.slice(1);
          break;
      }
    } else if (exportName.length > 0) {
      if (exportsType === "default-only") {
        return `/* non-default import from non-esm module */undefined${propertyAccess(exportName, 1)}`;
      } else if (exportsType !== "namespace" && exportName[0] === "__esModule") {
        return "/* __esModule */true";
      }
    } else if (
      exportsType === "default-only" ||
      exportsType === "default-with-named"
    ) {
      // 使用 createFakeNamespaceObject 生成 fake namespace
      runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
      initFragments.push(
        new InitFragment(
          `var ${importVar}_namespace_cache;\n`,
          InitFragment.STAGE_CONSTANTS,
          -1,
          `${importVar}_namespace_cache`
        )
      );
      return `/*#__PURE__*/ ${
        asiSafe ? "" : asiSafe === false ? ";" : "Object"
      }(${importVar}_namespace_cache || (${importVar}_namespace_cache = ${
        RuntimeGlobals.createFakeNamespaceObject
      }(${importVar}${exportsType === "default-only" ? "" : ", 2"})))`;
    }
  }

  if (exportName.length > 0) {
    const exportsInfo = moduleGraph.getExportsInfo(module);
    const used = exportsInfo.getUsedName(exportName, runtime);

    // 没有使用的 export,输出 undefined + 注释
    if (!used) {
      const comment = Template.toNormalComment(`unused export ${propertyAccess(exportName)}`);
      return `${comment} undefined`;
    }

    const comment = equals(used, exportName)
      ? ""
      : `${Template.toNormalComment(propertyAccess(exportName))} `;
    const access = `${importVar}${comment}${propertyAccess(used)}`;

    if (isCall && callContext === false) {
      // 保留 this 上下文
      return asiSafe
        ? `(0,${access})`
        : asiSafe === false
          ? `;(0,${access})`
          : `/*#__PURE__*/Object(${access})`;
    }

    return access;
  }

  // 如果没导出任何字段,直接返回模块变量本身
  return importVar;
}
相关推荐
HtwHUAT1 分钟前
五、web自动化测试01
前端·css·chrome·python·功能测试·selenium·html
86Eric4 分钟前
Vue 中 使用 Mixins 解决 多页面共用相同组件的相关问题
前端·javascript·vue.js·mixins·公用组件
qq_252496399616 分钟前
react 子组件暴露,父组件接收
前端·javascript·react.js
fakaifa19 分钟前
【最新版】西陆健身系统源码全开源+uniapp前端
前端·小程序·uni-app·开源·php·约课小程序·健身小程序
南囝coding25 分钟前
关于我的第一个产品!
前端·后端·产品
iOS阿玮31 分钟前
别等了,今天是Xcode15时代的最后一天。
前端·app·apple
沙尘暴炒饭37 分钟前
vuex持久化vuex-persistedstate,存储的数据刷新页面后导致数据丢失
开发语言·前端·javascript
2401_8370885040 分钟前
CSS清楚默认样式
前端·javascript·css
zwjapple1 小时前
React 的 useEffect 清理函数详解
前端·react.js·前端框架
Jewel1051 小时前
如何配置Telegram Mini-App?
前端·vue.js·app