Babel preset-env 未加载节点目标的顶级等待语法

Babel preset-env not loading top-level await syntax for node target

我正在尝试使用新的顶级 await 语法来导入模块,但 Babel 无法识别语法,尽管使用了 preset-env,除非我明确设置插件 @babel/plugin-syntax-top-level-await。为什么我必须手动指定这个插件?我的印象是 preset-env 会自动处理这些事情?

上下文,我的设置如下:

    presets: [
      [
        '@babel/preset-env',
        {
          debug: true,
          modules: false,
          useBuiltIns: 'usage',
          corejs: 3,
        },
      ],
      '@babel/preset-typescript',
    ],
    // plugins: ['@babel/plugin-syntax-top-level-await'], // Commented b/c I was experimenting
  };

当运行yarn run babel myFile.ts时,输出和抛出的错误是:

@babel/preset-env: `DEBUG` option
Using targets:
{
  "node": "13.12"
}
Using modules transform: false
Using plugins:
  proposal-nullish-coalescing-operator { "node":"13.12" }
  proposal-optional-chaining { "node":"13.12" }
  syntax-json-strings { "node":"13.12" }
  syntax-optional-catch-binding { "node":"13.12" }
  syntax-async-generators { "node":"13.12" }
  syntax-object-rest-spread { "node":"13.12" }
  syntax-dynamic-import { "node":"13.12" }
Using polyfills with `usage` option:
SyntaxError: /path/to/project/src/storage/index.ts: Unexpected token, expected ";" (4:6)
  2 | import { AppError } from '../errors';
  3 | 
> 4 | await import('./db/dbDiskMethods');
    |       ^
  5 | 
  6 | const getDbDiskMethods = async () => {
  7 |   return await import('./db/dbDiskMethods');

作为附带问题,为什么 preset-env 会加载调试输出中显示的 5 个语法插件,但会跳过顶级 await 语法插件?

发生这种情况是因为 preset-env 仅加载 accepted proposals. Currently, top level await has not yet been accepted into the language, it's at stage 3

一般来说,preset-env 可用的插件列表来自 compat-data package。 babel 为 运行 时 preset-env 实际使用的插件列表取决于

OP 错误被抛出,因为 Babel 是来自命令行的 运行。如果它是 babel-loader 运行,"missing" 插件会自动添加,如上面第二点所述。

为之前的答案添加一些细节:

TLDR

为了让顶级 awaitNode.js 中工作:将您的文件名更改为 *.mjs 或将 "type": "module" 添加到您的 package.json .

深入讲解

首先,top-level-await has moved to "Stage 4" (meaning: "Finished, Shipping"), and thus is now part of the ES standard. Node v14.18 开箱即用(但下面解释了注意事项)。

遗憾的是,top-level-await 调用方语义相当复杂。这意味着它不“只是工作”,而是取决于“load/require/import 逻辑”:

  1. CommonJS 不支持顶级 await。这有几个含义;最重要的是:顶级 await 仅适用于 ESMs (ECMAScript Modules);或者换句话说:它仅在您使用 import 时有效,而在您使用 require 时无效。更多的影响将在下面讨论。
  2. 更麻烦的是,顶级 await 语义完全由模块“调用者”或“加载者”决定。没有办法只获取单个顶级 await 文件的代码并使其“与 es5 兼容”而不更改 load/require/import 此代码的文件。正如你在 its few lines of source code 中看到的:没有 bundler,顶级 await babel 插件实际上根本没有改变代码,它只设置了一些选项,这些选项用于稍后确定代码的正确性下线。
  3. 从好的方面来说,如果您将顶级 await 与捆绑器(例如 Webpack、Rollup 等)一起使用,它应该可以正常工作,如果您 babel 您的代码并包含preset-env or the top-level-await plugin. This will be your go-to choice most of the time, especially when you want to run things in the browser. NOTE that many frontend framework "wrappers" (such as create-react-app, @vue/cli or storybook) use webpack under the hood, and thus should work out of the box. preset-env automatically adds the plugin if the code caller (e.g. babel-loader for webpack) allows it.

Node.js

中的顶级 await

由于此语法的复杂性,如上所述,Node.js 默认不支持顶级 await。如果您尝试 运行 node myscript.js,您将收到错误消息:await is only valid in async functions and the top level bodies of modules。这个错误信息其实很清楚,但对于不熟悉Node.js复杂模块系统的人来说是难以理解的。

问题是,除非您采取非常具体的步骤告诉 Node 它将您的文件视为“模块”(或“ES 模块”或 ESM),否则它不会。这是因为 ESMs 是更新的功能,并不隐式兼容旧的 CommonJS 模块,当人们尝试混合使用两者时会导致很多挫折。

The node documentation states:

Node.js will treat the following as ES modules when passed to node as the initial input, or when referenced by import statements within ES module code:

  • Files ending in .mjs.
  • Files ending in .js when the nearest parent package.json file contains a top-level "type" field with a value of "module".
  • Strings passed in as an argument to --eval, or piped to node via STDIN, with the flag --input-type=module.

因此,让您的顶级 await 进入 Node 的方法是使用 .mjs 文件扩展名,或在您的 [=14] 中设置 type 字段=].然后一切都会顺利进行,直到您陷入任何其他涉及 ESM 的陷阱。