`@babel/preset-env` + `useBuiltIns` + `@babel/runtime` + `browserslistrc` 的最佳实践是什么?

20

当我在使用@babel/preset-envuseBuiltIns@babel/transform-runtime结合使用时,会得到不同的输出结果。我已经阅读了文档,但还没有找出最佳实践应该是什么。

例如,当我的目标浏览器列表包括Edge 18时,@babel/preset-envuseBuiltIns会为string.replace添加一个polyfill。

但是,当我改用@babel/transform-runtime时,这个polyfill就不会被添加。


因此,从这个问题开始:

Does `string.replace` need to be polyfilled for Edge 18?

caniusemdncompat-table是很好的教育资源,但并不是真正用于开发者工具的数据源: 只有compat-table包含了一组良好的与ES相关的数据,它被@babel/preset-env所使用,但它也有一些限制。

并且进一步说:

出于这个原因,我创建了core-js-compat软件包: 它提供 有关不同目标引擎需要core-js模块的数据。当使用core-js@3时,@babel/preset-env将使用该新包 而不是compat-table

因此,我将我的目标浏览器传递给core-js-compat,并输出了所有所需的polyfill。如下图所示,需要为许多字符串方法提供polyfill,大多数是为了支持Edge 18。

enter image description here

到目前为止,一切都好。看起来对于Edge 18,确实需要对string.replace进行polyfill。


Babel配置

第一种方法:@babel/preset-envuseBuiltIns: 'usage'

当我使用useBuiltIns: 'usage'core-js中带入每个文件的polyfills时:

// babel.config.js

  presets: [
    [
      '@babel/preset-env',
      {
        debug: false,
        bugfixes: true,
        useBuiltIns: 'usage',
        corejs: { version: "3.6", proposals: true }
      }
    ],
    '@babel/preset-flow',
    '@babel/preset-react'
  ],

debug: true 时,Babel会向我的 PriceColumn.js 文件中添加以下 polyfills:

// Console output

[/price-column/PriceColumn.js] Added following core-js polyfills:

  es.string.replace { "edge":"17", "firefox":"71", "ios":"12", "safari":"12" }
  es.string.split { "edge":"17" }
  web.dom-collections.iterator { "edge":"17", "ios":"12", "safari":"12" }

有一个区别是它说 es.string.replace 是针对 edge: 17 而不是我们在上面从 core-js-compat 中看到的 edge: 18 - 可能是我做的什么,但现在没关系。

Babel 添加到转译后的 PriceColumn.js 文件顶部的附加内容:

// PriceColumn.js

"use strict";

require("core-js/modules/es.string.replace");

require("core-js/modules/es.string.split");

require("core-js/modules/web.dom-collections.iterator");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

再次,目前为止一切都很好。


第二种方法:@babel/runtime@babel/transform-runtime

根据core-js的文档:

@babel/runtime选项使用corejs: 3可以简化与core-js-pure的工作。它会自动将 JS 标准库中的现代特性替换为来自未污染全局命名空间的 core-js 版本的导入。

听起来不错-让我们试试吧!

注释掉useBuiltIns,并添加@babel/transform-runtime插件配置:

// babel.config.js

  presets: [
    [
      '@babel/preset-env',
      {
        debug: true,
        // bugfixes: true,
        // useBuiltIns: 'usage',
        // corejs: { version: '3.6', proposals: true }
      }
    ],
    '@babel/preset-flow',
    '@babel/preset-react'
  ],
  plugins: [
    [
      '@babel/transform-runtime',
      {
        corejs: { version: 3, proposals: true },
        version: '^7.8.3'
      }
    ]
  ],

在控制台输出中,我看到:

Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.

检查文件顶部添加了什么内容:

// PriceColumn.js

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");

var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");

_Object$defineProperty(exports, "__esModule", {
  value: true
});

exports.default = void 0;

var _objectSpread2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/objectSpread2"));

var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/map"));

因此,新增了不同的helpers - 但没有es.string.*的拼写补丁。它们不再需要了吗?它们已经被“helpers”带来了吗?看起来对象扩展和数组映射与多填充字符串实例方法没有任何关系,所以我认为不是。


最后

我的最后尝试是结合这两种方法,并遵循建议:

enter image description here

a) 为@babel/preset-env设置corejs:

// babel.config.js

  presets: [
    [
      '@babel/preset-env',
      {
        debug: true,
        // bugfixes: true,
        useBuiltIns: 'usage',
        corejs: { version: '3.6', proposals: true }
      }
    ],
    '@babel/preset-flow',
    '@babel/preset-react'
  ],
  
  plugins: [
    [
      '@babel/transform-runtime',
      {
        // corejs: { version: 3, proposals: true },
        version: '^7.8.3'
      }
    ]
  ]

这是输出结果:

// PriceColumn.js

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

require("core-js/modules/es.string.replace");

require("core-js/modules/es.string.split");

require("core-js/modules/web.dom-collections.iterator");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));

The recommended approach is to use the @babel/preset-env with useBuiltIns option to bring in the required polyfills while minimizing global namespace pollution. Polluting the global namespace can cause issues for libraries, but may not be a problem for standalone applications. The combination of useBuiltIns and @babel/transform-runtime also provides a polyfill for object spread, but if the Array#map polyfill is needed will depend on the specific requirements of the application.
2个回答

12

根据https://www.jmarkoski.com/understanding-babel-preset-env-and-transform-runtime的建议:

App应用程序:如果您正在编写一个应用程序,请在应用程序顶部使用import 'core-js,并将useBuiltIns设置为entry,仅对helpers使用@babel/transform-runtime(作为依赖项的@babel/runtime)。这样,您会污染全局环境,但您不必担心,因为这是您的应用程序。您将获得别名为@babel/runtime的helpers以及包含在应用程序顶部的polyfills的好处。这样,您也不需要处理node_modules(除非某个依赖项使用必须转换的语法),因为如果某个依赖项使用需要polyfill的功能,您已经在应用程序顶部包含了该polyfill。

:如果你正在编写一个库,只使用带有corejs选项的@babel/transform-runtime和作为依赖项的@babel/runtime-corejs3,并使用useBuiltIns: false的@babel/preset-env进行语法转换。同时,我会将从node_modules中使用的包也进行转换。为此,您需要设置absoluteRuntime选项(https://babeljs.io/docs/en/babel-plugin-transform-runtime#absoluteruntime)来解决运行时依赖项从单个位置解析的问题,因为@babel/transform-runtime直接从@babel/runtime-corejs3导入,但这仅适用于@babel/runtime-corejs3位于正在编译的文件的node_modules中。

更多信息:


你能解释一下为什么你建议在一个库中使用 absoluteRuntime 吗?如果你把一个有着绝对路径的库发布到 npm 上,那么它将无法正常运行。你的意思是针对那些在发布之前被打包的库吗? - loganfsmyth
@loganfsmyth:我只在构建和打包部署之前的 Web 应用程序中使用过这个(但为了完整性而引用了两种情况)。如果您想深入了解,我认为您需要与原始作者讨论:jmarkoski.com/contact。 - Per Quested Aronsson
说得好,我主要是想提到这一点,因为有人从另一个问题链接到这里,而那部分内容让他们感到困惑。只有在捆绑时才会使用absoluteRuntime,所以似乎库点需要一些注意事项。 - loganfsmyth

0

接受答案中的链接文章已经失效。根据内容检查,我相信它来自于JMarkoski在这个问题https://github.com/babel/babel/issues/9853中发布的评论,请查看他的详细解释以了解更多信息。

但我认为这个答案中有一个错误(或者是过时的观点),我认为我们应该使用useBuiltIns: 'usage'而不是useBuiltIns: 'entry',因为usage更简单,打包后的大小也要小得多,来自Babel维护者的答案我仍然不明白@babel/preset-env.useBuiltIns: entry和useBuiltIns: usage之间的区别证实了这一点。

我发现在使用useBuiltIns: 'usage'时,通常都会配合corejs: { version: "3.8", proposals: true }一起使用,毕竟https://babeljs.io/docs/babel-preset-env#corejs上面是这么说的,而且我看到大多数在SO上的答案都使用了proposals: true
但是我认为当你的目标环境是生产环境时,最好将proposals: false(默认值)设置为更安全的选择,因为proposals: true是用于实验的。
此外,我认为大多数情况下,我们只想为目标浏览器缺少的功能提供polyfill,而不需要为提案阶段的功能提供polyfill,除非我们知道我们使用了该功能。
这里有一个示例来说明我的意思,我的.babelrc文件。
   "presets": [
     [
         "@babel/preset-env",
         {
             "targets": {
                 "chrome": "84"
             },
             "modules": "commonjs",
             "debug": true,
             "useBuiltIns": "usage",
             "corejs": {
                 "version": 3.8
                 // "proposals": true
             }
         }
     ]
  ]

从日志中看,要关闭“proposals”,我需要babel polyfill(参考在任何promise已完成时是否可能中断await Promise.all(Chrome 80))。
[/Users/langqiu/xxx/getLoginInfo.js]
The corejs3 polyfill added the following polyfills:
  es.aggregate-error { "chrome":"84" }
  es.promise.any { "chrome":"84" }

但是使用"proposals": true的babel polyfill比我需要的要多。我确定我不需要那些esnext的东西,我对Chrome已经很满意了。
[/Users/langqiu/xxx/getLoginInfo.js]
The corejs3 polyfill added the following polyfills:
  es.aggregate-error { "chrome":"84" }
  es.promise.any { "chrome":"84" }
  esnext.async-iterator.map { "chrome":"84" }
  esnext.iterator.map { "chrome":"84" }
  esnext.async-iterator.filter { "chrome":"84" }
  esnext.iterator.constructor { "chrome":"84" }
  esnext.iterator.filter { "chrome":"84" }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接