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/2020/proposal-import-meta.md.
  • 简体中文
  • import.meta S4

    提案概览
    提案速览

    该提案引入了一个 import.meta 元属性,用于提供当前模块的主机特定元数据,例如 URL、脚本元素或主模块状态。它是一个由 ECMAScript 实现创建的 null 原型对象,可被主机扩展且可变。

    Note

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

    import.meta

    本仓库包含为 JavaScript 添加 import.meta 元属性的提案,用于持有当前模块的主机特定元数据。目前处于 TC39 流程 的阶段 4。

    此前,该问题领域曾与模块加载社区在 whatwg/html#1013 中进行过讨论。结果是 2017 年 5 月 TC39 会议上的 这些幻灯片 演示。委员会对其中提出选项的反馈促成了本提案中选择的特定形式;这些讨论在本文档的其余部分进行了总结。

    您可以查看进行中的 规范草案,并参与 问题跟踪器 上的讨论。

    动机和使用场景

    主机环境通常可以向模块内执行的代码提供有用的模块特定信息。以下是一些示例:

    模块的 URL 或文件名

    在 Node.js 的 CommonJS 模块系统中,通过作用域内的 __filename 变量(及其对应物 __dirname)提供。这允许通过如下代码轻松解析相对模块文件的资源:

    const fs = require("fs");
    const path = require("path");
    const bytes = fs.readFileSync(path.resolve(__dirname, "data.bin"));

    如果没有 __dirname,类似 fs.readFileSync("data.bin") 的代码将相对于当前工作目录解析 data.bin。这对库作者通常没有用处,他们将其资源与模块打包在一起,这些模块可能位于相对 CWD 的任何位置。

    在浏览器中也存在非常类似的使用场景,其中 URL 将代替文件名。

    初始化的 <script>

    在浏览器中,对于经典脚本(即非模块脚本),通过全局 document.currentScript 属性提供。通常用于配置包含的库。页面编写如下代码:

    <script data-option="value" src="library.js"></script>

    然后库通过如下代码查找 data-option 的值:

    const theOption = document.currentScript.dataset.option;

    顺便说一句,使用(有效的)全局变量而不是词法作用域值的机制是有问题的,因为这意味着该值仅在顶层设置,如果要在任何异步代码中使用,必须将其保存在那里。

    我是“主”模块吗?

    在 Node.js 中,通常通过如下代码分支判断您是否是程序的“主”或“入口”模块:

    if (module === process.mainModule) {
      // 运行此库的测试,或提供 CLI 接口,或类似操作
    }

    也就是说,它允许单个文件在导入时作为库使用,或在直接运行时作为有副作用的程序使用。

    请注意,这种特定表述依赖于将主机提供的作用域值 module 与主机提供的全局值 process.mainModule 进行比较。

    其他杂项使用场景

    可以预见的其他情况包括:

    • 其他 Node.js 实用程序,例如 module.childrenrequire.resolve()
    • 关于模块所属“包”的信息,无论是通过 Node.js 的 package.json 还是 网络包
    • 指向 HTML 模块 中嵌入的 JavaScript 模块的包含 DocumentFragment 的指针。

    约束

    鉴于这些信息通常是主机特定的,我们希望 JavaScript 提供一种通用的可扩展机制,供主机使用,而不是要求为每条元数据在 JavaScript 中进行标准化。

    此外,如上所述,最好以词法方式提供这些信息,而不是通过例如临时设置的全局变量。

    提议的解决方案

    本提案添加了一个 import.meta 元属性,它本身是一个对象。它由 ECMAScript 实现创建,具有 null 原型。主机环境可以返回一组将添加到该对象的属性(作为键/值对)。最后,作为逃生舱口,主机可以在必要时任意修改该对象。

    import.meta 元属性在语法上仅在模块中有效,因为它旨在提供有关当前正在运行的模块的元信息,不应重新用于有关当前运行的经典脚本的信息。

    示例

    以下代码使用了我们预期在浏览器中添加到 import.meta 的几个属性:

    (async () => {
      const response = await fetch(new URL("../hamsters.jpg", import.meta.url));
      const blob = await response.blob();
    
      const size = import.meta.scriptElement.dataset.size || 300;
    
      const image = new Image();
      image.src = URL.createObjectURL(blob);
      image.width = image.height = size;
    
      document.body.appendChild(image);
    })();

    当此模块加载时,无论其位置如何,它都会加载一个同级文件 hamsters.jpg,并显示该图像。图像的大小可以使用用于导入它的脚本元素进行配置,例如:

    <script type="module" src="path/to/hamster-displayer.mjs" data-size="500"></script>

    探索过的替代方案

    还有其他多种可能提供主机特定元数据的方式。探索的主要其他途径是:

    使用特定的模块说明符

    这里的想法是,我们已经有一种从模块系统获取上下文特定值的机制:导入它们!在这种情况下,获取这些属性的语法将通过顶级导入某个特定模块说明符来实现,例如:

    import { url, scriptElement } from "js:context";

    这与现有框架很契合,并且可以完全在主机端处理,不需要新的 ECMAScript 提案(因为主机控制模块说明符的解释方式)。然而,在网络方面,它遭到了 WebKit 的反对,鉴于 TC39 并未就此想法达成一致,认为不值得尝试克服 WebKit 的反对。Node.js 仍可能实现这一点,特别是如果 import.meta 不能很快满足他们的需求。

    以其他方式引入词法变量

    这种可能性会以某种方式将适当的信息注入到模块作用域中,类似于 Node.js 目前注入 __dirname__filenamemodule 的方式。然后它们就像普通作用域变量一样使用:

    console.log(moduleURL);
    console.log(moduleScriptElement);

    从实现和规范的角度来看,这可以在不修改 ECMAScript 规范的情况下完成,例如,主机在每个模块源文本交给 ECMAScript 规范机制之前,在每个模块源文本前添加适当的变量声明。或者,主机可以引入一种新型模块记录,其 [[Environment]] 字段具有略微不同的作用域。(或者可以修改 ECMAScript 以允许主机干预设置源文本模块记录的 [[Environment]]。)

    委员会普遍反对在 ECMAScript 中处理这个想法,因为几位成员不喜欢用新变量“污染”模块作用域。如果 import.meta 未能足够快地推进,它仍然是网络主机的后备选项。

    常见问题解答

    这个对象会被以任何方式锁定吗?

    委员会的初步决定是否定的:默认情况下 import.meta 对象将是可扩展的,其属性将是可写的、可配置的和可枚举的。

    锁定该对象没有真正的好处,因为它对于模块是局部的,并且只能通过传递来显式共享。它不代表“全局状态”或任何类似的东西。

    此外,保持其可变性允许模块到模块的转译器通过在每个模块顶部插入几行额外代码来“填充”未来特性,添加或修改 import.meta 属性。例如,如果几年后 HTML 添加了一个额外属性,编写一个在针对旧浏览器时添加该属性的转译器是可能的。

    最后,HostFinalizeImportMeta 提供的逃生舱口允许偏好更锁定 import.meta 对象的主机采取该路线。