For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/pending/proposal-esm-phase-imports.md.
  • 简体中文
  • ESM Phase Imports S2.7

    中文标题:ESM 阶段导入

    提案概览
    提案速览

    该提案为 ECMAScript 模块定义了源码阶段导入,允许静态或动态地获取模块源码对象的句柄。主要目标是通过允许将模块句柄直接传递给 new Worker(module) 来改进 worker 分析,同时为未来的模块和谐提案奠定基础。7,提出了扩展 AbstractModuleSourceModuleSource 类,并考虑了与 CSP、结构化克隆及其他提案的集成。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ECMAScript 模块阶段导入

    状态

    提案发起人:Luca Casonato, Guy Bedford

    阶段 2.7

    提案的规范文本目前基于 ECMA-262 的源码阶段导入 PR,位于 https://github.com/tc39/ecma262/pull/3492。

    问题陈述

    本提案旨在通过为源文本模块定义源码阶段导入,解决 JavaScript 的 静态 worker 模块分析问题。

    背景

    随着最近引入的源码阶段导入,现在可以在模块图的完整链接和执行之前定义导入阶段。

    虽然支持 WebAssembly,但 ECMAScript 模块本身的源码阶段的确切语义尚未规定。

    这些对象的一个关键规范约束是它们在 worker 和其他代理中的行为方式,我们认为这构成了这些功能的关键设计约束。这就是为什么本提案的问题陈述被视为更大的模块和谐分层工作中最合适的“下一步”,此处指定的阶段对象将支持未来提案的分层,包括模块表达式、模块声明和通过隔离舱加载器的虚拟化。

    动机

    改进对 worker 的运行时和工具支持将使 JavaScript 应用更快。

    当前用于这些工作流的 new Worker() 构造函数模式存在许多分析问题:

    1. 它总是接受任意表达式来定位 worker 的 URL。worker 不仅仅是像普通 ESM 导入那样的 静态导入
    2. 传递给 new Worker(url) 的字符串不支持模块解析规则。由于相对 URL 是基于 baseURL 解析的,用户通常需要先依赖解析函数,例如带外配置或涉及 import.meta.resolve()import.meta.url 的表达式。这里有许多不同的模式,开发人员没有采用任何单一的标准方法,进一步加剧了问题 (1) 中的分析困难。

    使用示例:

    // 常见情况,但需要非常不平凡的静态分析
    const url = new URL("./my_worker.js", import.meta.url);
    const worker = new Worker(url, { type: "module" });
    
    const url = import.meta.resolve("./my_worker.js");
    const worker = new Worker(url, { type: "module" });
    
    // 这很快就会变成对大多数工具几乎不可能的静态分析
    function createWorker(url) {
      return new Worker(url, { type: "module" });
    }
    
    const url = new URL("./my_worker.js", import.meta.url);
    const processor = createWorker(url);

    结果是,很难可靠地静态分析哪些模块在 worker 中加载,导致运行时和工具出现问题:

    • new Worker 对开发人员在编写应用程序尤其是编写库时不够友好。
    • 工具难以分析和打包使用 worker 的应用程序,导致使用减少,共享库对 worker 的支持有限。

    更好的语言级 worker 加载原语可以改善用户使用 worker 的体验,以及它们在分析和构建工具中的支持。

    提案

    通过为 ECMAScript 模块记录定义源码阶段,可以静态导入模块的句柄,并直接用于初始化 worker:

    import source myModule from "./my-module.js";
    
    // 由于 myModule 是模块对象,可以推断出 `{ type: 'module' }`
    const worker = new Worker(myModule);

    该技术解决了 worker 导入的分析问题 (1) 和 (2),改善了运行时 worker 的可用性——支持静态 worker 引用,同时通过正常的模块解析规则以模块相对方式解析,并支持所有解析特性。

    改进的静态分析使得工具能够更容易地分析 worker 引用,确定静态的 myModule 句柄被直接传递给 new Worker。打包可以通过将 ./my-module.js 阶段导入替换为对完全优化的 worker 块的阶段导入来进行。

    定义源码阶段也为未来需要源码表示形式的模块和谐提案奠定了基础。

    由于阶段也支持动态导入形式,我们还定义了动态变体:

    const workerModule = await import.source('./worker.js');
    new Worker(workerModule);

    当前提议的 API 是扩展 AbstractModuleSourceModuleSource 类实例。

    这是一个新的非全局内在对象,通过任何 JS 模块的源码阶段导入,具有与 AbstractModuleSource 相同的可达性属性。

    动态导入

    由于模块源码是从模块注册表中获取的,它们在其注册表键处被缓存,就像模块实例一样。动态导入模块源码,意味着为同一注册表键动态导入该模块源码的“规范实例”。

    在当前规范文本中,这是通过将 HostGetModuleSourceName 钩子转换为 HostGetModuleSourceModuleRecord 钩子来处理的,该钩子获取给定模块源码的 Source Text Module Record。然后可以直接驱动该记录直至完成。

    Worker 调用

    对于 HTML 集成,期望是 new Worker(module)AbstractModuleSource 的任何具体实例,其行为如同该模块首先被克隆到 worker 中,然后通过动态 import() 导入。

    这里还有一个额外目标,即创建的 worker 继承创建模块源码的父环境的相同解析规则,以提供一致的模块解析。

    与其他规范的集成

    CSP 集成

    对于 WebAssembly,CSP 集成是在编译时检查,发生在构造与 WebAssembly 模块对应的 AbstractModuleSource 之前。也就是说,当人们拥有 AbstractModuleSource 时,已经通过了 CSP 策略检查。

    对于 JavaScript,CSP 集成同样在执行之前静态发生,其中具有指向 JS ModuleSourcesource 阶段导入句柄意味着执行该源码的 CSP 许可。

    对于动态导入,将 AbstractModuleSource 传递给动态导入不需要通过 CSP 检查,因为所获得的对象已经通过 CSP 审查。

    对于 new Worker(module),可能存在比 script-src 策略更严格的 worker-src 策略,需要针对模块的 src 进行 CSP 策略验证。在这种情况下,应该可以从 [[HostDefined]] 数据重新创建原始的 CSP src,而不需要任何显式的 ECMA-262 集成。

    结构化克隆

    ModuleSource 实例可以在结构化克隆中支持,因为底层源码数据没有 realm 依赖性。存储在 [[HostDefined]] 中的任何数据需要被定义为自身可结构化克隆,才能被重新创建。

    分层

    源码阶段导入

    本提案旨在与源码阶段导入协同工作,无论它是否直接为 ECMAScript 模块指定源码阶段。

    导入属性

    导入属性提案提供了一种将属性传递给模块加载器的方式。这些属性在源码加载和解析期间使用。由于模块源码已经经历了这个过程,当它们获得句柄时,它们已经受到了属性的影响。

    当将模块源码对象传递给动态 import()new Worker 时,任何额外的 with 属性将不受支持——设置属性将抛出错误。

    延迟导入

    延迟导入提案提供了一种将模块的同步求值延迟到需要时的方法,但不会延迟模块的链接。这是“模块上下文附加”阶段和“求值”阶段之间的一个阶段。

    作为完全独立的阶段,本提案不与其他方面与延迟导入提案交互。

    模块表达式和模块声明

    模块表达式模块声明提案定义的模块对象,应与本提案中指定的任何 SourceTextModule 阶段对象基础一致。

    模块声明导入和导出的分析元数据可以通过现有源码分析的扩展来暴露。这些可能的分析扩展在 https://github.com/tc39/proposal-esm-phase-imports/issues/19 中讨论。

    隔离舱加载器

    隔离舱提案提供了一种从模块源码对象动态创建模块实例的方式,可选地提供自定义加载器。

    这里的模块源码定义正与本提案及其他提案中使用的定义保持一致。在本提案或其他提案中定义的那些地方,隔离舱加载器提案未来可以通过向这些现有对象添加新方法等方式进一步扩展其功能。

    问答

    本提案在进入阶段 2 和阶段 2.7 时有哪些考虑?

    请参阅以前的状态更新,网址为 https://github.com/tc39/proposal-esm-phase-imports/issues/50。

    为什么从本提案中移除了源码分析?

    本提案最初还包括向模块源码添加源码分析属性,形式如下:

    • AbstractModuleSource.prototype.imports: () => Import[]
    • AbstractModuleSource.prototype.exports: () => Export[]
    • AbstractModuleSource.prototype.hasImportMeta: boolean
    • AbstractModuleSource.prototype.hasTopLevelAwait: boolean

    其中 Import 定义如下:

    interface Import {
      specifier: string,
      phase: null | 'source'
    }

    ExportDirectExport | Reexport | ReexportAll 定义:

    interface DirectExport {
      type: 'direct',
      names: string[]
    }
    
    interface Reexport {
      type: 'reexport',
      name: string,
      import: string | null, // null 用于指示命名空间再导出
      from: string,
    }
    
    interface ReexportAll {
      type: 'reexport-all',
      from: string,
    }

    这个特性被移除了,因为它导致本提案有双重动机。

    JS 模块源码分析将来可能会在 JS 虚拟化工作(如隔离舱)中重新被提上议程。

    在这个初始提案中移除这个特性绝不意味着这种源码分析在未来不可行,只是本提案需要更专注于用例。

    提交问题