creeperyang / blog

前端博客,关注基础知识和性能优化。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

了解 Babel 6 & 7 生态

creeperyang opened this issue · comments

了解Babel 6生态

现在写一个babel的简介好像已经不太必要了(太晚了😄 )。但大多数情况下,会配置babel来编译代码,不代表我们清楚babel的概念,而且Babel 6相关的多个babel-xx包还是容易让人混淆的。所以这里还是希望帮助理清整个Babel 6生态。

参考:

Babel 6的核心特性(相比5的巨大变化)

我刚开始用babel的时候,版本是5,一个月后Babel 6发布——变化简直天翻地覆。

相比前一版本,Babel 6最大的变化是更模块化,各种内置库都被分散到独立的模块;其次,让所有插件可选,这意味着Babel默认不会编译ES2015代码,并且所有的transformer完全独立;同时,为了减少配置的复杂性,引入了preset;最后,提升了性能。

下面列出一些Babel 6的核心模块/变化:

  1. babel package被弃用。

    我们可以看babel@6.x的源码,两个提示很明显:

    1. babel/index.js::node API 被转移到 babel-core
    2. babel/src/cli.js: cli 被转移到babel-cli
  2. babel-core是babel的core compiler,主要用来对你的源码跑一系列变换(transform)。但默认情况下,不会应用任何变换——你必须自己安装和注册这些变换。

  3. babel-cli是babel的command line,有babel/babel-external-helpers/babel-node3个命令。babel-doctor已被移除,见babel/babel#4678

    1. babel即用于编译代码。
    2. babel-external-helpers用于生成一段js代码(里面是一些helper函数)。这些helper如果被用到,一般被置于生成代码顶部(公用),所以生成的代码不会有内连这些helper好几遍。但是如果你有多个文件的话,你可能又要重复这些helper好几遍了。所以你可以生成这样一份代码,然后在每个文件中直接引入(node通过require,browser通过<script>)。详情见external-helpers
    3. babel-node是方便开发的node二进制(非生产使用),内置了babel-polyfill,并用babel-register来编译被require的模块。
  4. babel-registerrequire hook,替换了node的require

    The require hook will bind itself to node's require and automatically compile files on the fly.

    1. 如果模块是内置模块或者是node_modules内的模块,则使用node的require
    2. 否则使用babel的require,自动编译模块。

Babel 6plugins

详情见https://babeljs.io/docs/plugins/

这里不多说,只简单说两点:

  1. Babel引入了preset的概念,preset其实是一组plugins
  2. 我们常用的babel-preset-es2015包括了完整的ES2015特性,引入它即可编译ES2015代码到ES5

用transform还是polyfill实现?

babel-core仅仅聚焦于code transform,所以不是什么事都可以用babel来转换的。

比如,检索上面的plugins列表,你会发现没有一个plugin用来转换Promise;事实上,如果环境不支持Promise,你应该自己引入相应polyfill。

那么什么时候应该用tranform,什么时候该用polyfill呢?如果一个新特性你可以用ES5实现,那么,你应该用polyfill,比如Array.from。否则,你应该用transform,比如箭头函数。

babel-polyfill vs babel-runtime

这可能是babel中最让人误解的一组概念:当你需要支持ES2015的所有特性时,究竟用babel-polyfill 还是 babel-runtime

babel-polyfillbabel-runtime是达成同一种功能(模拟ES2015环境,包括global keywordsprototype methods,都基于core-js提供的一组polyfill和一个generator runtime)的两种方式:

  1. babel-polyfill通过向全局对象和内置对象的prototype上添加方法来达成目的。这意味着你一旦引入babel-polyfill,像MapArray.prototype.find这些就已经存在了——全局空间被污染。

  2. babel-runtime不会污染全局空间和内置对象原型。事实上babel-runtime是一个模块,你可以把它作为依赖来达成ES2015的支持。

    比如当前环境不支持Promise,你可以通过require(‘babel-runtime/core-js/promise’)来获取Promise。这很有用但不方便。幸运的是,babel-runtime并不是设计来直接使用的——它是和babel-plugin-transform-runtime一起使用的。babel-plugin-transform-runtime会自动重写你使用Promise的代码,转换为使用babel-runtime导出(export)的Promise-like对象。

    注意: 所以plugin-transform-runtime一般用于开发(devDependencies),而runtime自身用于部署的代码(dependencies),两者配合来一起工作。

那么我们什么时候用babel-polyfill,什么时候用babel-runtime

  1. babel-polyfill会污染全局空间,并可能导致不同版本间的冲突,而babel-runtime不会。从这点看应该用babel-runtime
  2. 但记住,babel-runtime有个缺点,它不模拟实例方法,即内置对象原型上的方法,所以类似Array.prototype.find,你通过babel-runtime是无法使用的。
  3. 最后,请不要一次引入全部的polyfills(如require('babel-polyfill')),这会导致代码量很大。请按需引用最好。

额,好像现在是习惯性的引入babel-polyfill; 其实关于babel-core这个库,如果使用webpack的话,自然会有babel-loader,但是现在loader又不依赖babel-core,是不是一般都不用加入项目的依赖了

@mengxingshike2012 babel-loaderpeerDependencies

"peerDependencies": {
    "babel-core": "^6.0.0",
    "webpack": "1 || 2 || ^2.1.0-beta || ^2.2.0-rc"
  },

我只用了这三个 babel-preset-env + babel-polyfill + whatwg-fetch 。
babel-preset-env很智能的。各种transform傻瓜化配置了。useBuiltIns选项对polyfill也做了筛选,不用引入全部的polyfill

@lgh06 才知道babel-preset-env 👍

是个不错的选择,不过看起来它是依赖指定node版本或者browser版本来决定引入哪些polyfill的;如果不用内置对象原型上的方法,babel-runtime可能更好一点。

太喜欢Array.from、forEach、Promise,没办法不污染原型了…… 新浏览器里面都内置了,也不能算污染……

有个小问题。比如你引用了一些第三方库,直接require是使用的编译后的文件。但是如果第三方库作者使用了es6并且没有使用相应的polyfill来转换类似于for(item of list) 这种语法转换的时候会变成Symbol。造成某一些环境下对其不支持的错误。这应该是开发者本人来引入polyfill解决还是第三方作者在转换的时候就应该引入polyfill转换?还是说是babel本身的一些问题

@For-me 我觉得是库作者的责任,他应该提供转换后的可用库文件,或者明确告知使用者必须自己转换。

为什么vue-cli生成的项目里,同时使用了transform-runtimebabel-preset-env

.babelrc:

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": ["transform-vue-jsx", "transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}

@xwenliang 很好的问题。

我仔细查阅了文档,答案是 babel-preset-env@1.x 没法很好地消除未使用的polyfill(就是说有未使用的代码被引入进来了)。如果希望避免这一点,那么就会禁用useBuiltIns: true,而用更好的 transform-runtime 代替。

babel-preset-env@2.x 中可以用 useBuiltIns: 'usage' 达到按需引入的目的。

详情可见:

babel/babel-preset-env#84

babel/babel-preset-env#241


可以看到 vuejs-templates/webpack/ 引入的是 1.3babel-preset-env

@creeperyang 专业👍

react不需要同时 用transform-runtime和babel-preset-env ,感觉和vue有关系,或者react里有的工作被babel-preset-react做了

请教作者vue-cli 使用的babel-preset-env 是@1.x ,有不能很好地消除未使用的polyfill的缺点,那么为什么不直接使用transform-runtime而是即使用env又使用runtime,这样重复不会造成打包体积变大吗。并且我没发现vue-cli中有useBuiltIns: true。

@lawpachi 这个可能是应为transform-runtime对实例方法的支持不是很好,所以只只能结合polyfill来进行转码

Babel 7 及新用法

Babel 7 快正式发布了(2018-06-20是@babel/core@^7.0.0-beta.51),相比6有非常多的更新,这里例举一些主要的,详情可以阅读官方博文。

  • deprecated Yearly Presets (如 babel-preset-es20xx),preset-env 代替
  • 最新更新的 TC39 Proposals
  • 重命名,@babel scope (防止被占用)
  • ...

presets 详解

babel 的工作流是

输入字符串 -> @babel/parser parser -> AST -> transformer[s] -> AST -> @babel/generator -> 输出字符串

其中在 transformer[s] 阶段,就是使用plugins来转换代码的阶段。不同的plugin转换特定的代码,而preset是一组完成特定转换的plugins,比如babel-preset-es2017即包含syntax-trailing-function-commas | transform-async-to-generator两个plugin,用于支持ES2017的新特性。

plugin 分为 transform plugin(实际转换代码) 和 syntax-plugin(语法支持,即parse阶段),一般transform plugin包含对应的syntax plugin。

以前常用的yearly presets(preset-es2015 | preset-es2016 | preset-es2017)在 Babel 7 中已经不推荐使用了,建议用 preset-env 代替。我们先跳过 preset-env (下一节重点讲),来讲一下常用的preset-state-x presets。

对JS语言的更新需要一个过程(即0-4的stage,过程如下),用于支持这些尚未被加入到标准(如ES5/2016)的更新的presets即preset-state-x

The TC39 categorizes proposals into the following stages:

  • Stage 0 - Strawman: just an idea, possible Babel plugin.
  • Stage 1 - Proposal: this is worth working on.
  • Stage 2 - Draft: initial spec.
  • Stage 3 - Candidate: complete spec and initial browser implementations.
  • Stage 4 - Finished: will be added to the next yearly release.

其中,preset-stage-3 用于支持 stage 3/4,其余一一对应。另外需要注意,preset-stage-0~3是一个包含关系,即preset-stage-0包含preset-stage-1所有的plugins和 stage 0 对应的plugins。

preset-env 详解

preset-env 是 JS 中的 autoprefixer,可以根据具体环境(浏览器/node版本)来应用不同的plugins,用于替代原来的babel-preset-es20xx/latestbabel-preset-es2015/babel-preset-es2016/babel-preset-es2017)。

严格来说,preset-env支持的plugins超过babel-preset-latest(2015-2017),包括部分stage-3支持的plugins及其它plugin——多出的plugins可以某种程度理解为preset-es2018(不会出现了),并且随着落地标准越来越多(es2018/es2019/...),包括的plugins也会越来越多。

多出的plugins :
@babel/plugin-proposal-async-generator-functions
@babel/plugin-proposal-object-rest-spread
@babel/plugin-proposal-optional-catch-binding
@babel/plugin-proposal-unicode-property-regex
@babel/plugin-syntax-async-generators
@babel/plugin-syntax-object-rest-spread
@babel/plugin-syntax-optional-catch-binding
@babel/plugin-transform-dotall-regex
@babel/plugin-transform-new-target

下面来讲讲preset-env的用法:

{
  "presets": [
    [
      "env",
      {
        "targets": { // 目标环境
          "browsers": [ // 浏览器
            "last 2 versions",
            "ie >= 10"
          ],
          "node": "current" // node
        },
        "modules": true,  // 是否转译module syntax,默认是 commonjs
        "debug": true, // 是否输出启用的plugins列表
        "spec": false, // 是否允许more spec compliant,但可能转译出的代码更慢
        "loose": false, // 是否允许生成更简单es5的代码,但可能不那么完全符合ES6语义
        "useBuiltIns": false, // 怎么运用 polyfill
        "include": [], // 总是启用的 plugins
        "exclude": [],  // 强制不启用的 plugins
        "forceAllTransforms": false, // 强制使用所有的plugins,用于只能支持ES5的uglify可以正确压缩代码
      }
    ]
  ],
}

preset-env的配置如上,大部分都很简单,重点讲一下useBuiltIns

useBuiltIns用于指定怎么处理polyfill(通过@babel/polyfill),可选的值为"usage" | "entry" | false,默认是false

  • useBuiltIns: 'usage':当每个文件里用到(需要polyfill的特性)时,在文件中添加特定的import语句。这可以保证每个polyfill的特性仅load一次。

    /// input
    var a = new Promise(); // a.js
    var b = new Map(); // b.js
    /// output
    // a.js
    import "core-js/modules/es6.promise";
    var a = new Promise();
    // b.js
    import "core-js/modules/es6.map";
    var b = new Map();
  • useBuiltIns: 'entry':替换import "@babel/polyfill" / require("@babel/polyfill")语句为独立的(根据环境)需要引入的polyfill特性的import语句。

    // input
    import "@babel/polyfill";
    // output
    import "core-js/modules/es7.string.pad-start";
    import "core-js/modules/es7.string.pad-end";

    需要注意,在整个项目中,"@babel/polyfill"只能require一次,否则报错。建议用独立的entry文件引入。

  • useBuiltIns: false,禁止使用上面两种转换,可以理解为不使用polyfill,此时需要使用者自己手动处理。

ReferenceError: regeneratorRuntime is not defined 错误:

需要注意,当你使用async/await并被preset-env转译后,运行时可能会出现以上错误,这是因为:

plugin-transform-regenerator 使用regenerator来转译 async/generator 函数,但是它本身不包括regeneratorRuntime,你需要使用babel-polyfill/regenerator runtime来使regeneratorRuntime 存在

通常情况下,加上transform-runtime plugin即可。

@creeperyang babel7 这里这一段讲的应该是有问题的:

  1. useBuiltIns 使用的前提就是页面入口引入了 babel-polyfill. 哪怕把useBuiltIns 设置为false, 也不代表 不适用polyfill,只是没有按环境需要引入而已

plugin-transform-regenerator 使用regenerator来转译 async/generator 函数,但是他本身不包含regeneratorRuntime。

是的, 但是babel-polyfill里面包含了regeneratorRuntim,只要使用了babel-polyfill,就可以支持这个generator。否则就要使用transform-runtime+babel-runtime来支持了

@cuiyongjian 赞同第一点,实际操作了一下,entry应该是按照目标浏览器的兼容性把所有需要的polyfill都放上去,
而usage就是上面提到的按照目标兼容性和按需原则引入,而当值为false的时候,就是不管三七二十一,把polyfill里面所有的内容都引入。
第二点的话,依我的理解一个完整和高兼容性的项目是需要babel-runtime和babel-polyfill一起支持的,babel-runtime提供runtimeHelper,babel-polyfill提供instance method。

包括拆分后的runtime-corejs2也不支持添加instance method。

@cuiyongjian @YardWill

其实按你们的说法,既然polyfill全引入了,怎么还会有 ReferenceError: regeneratorRuntime is not defined 的错误?

对于 useBuiltIns: false,babel 将不会加入任何 polyfill。

可以打开debug看到调试信息:

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

我自己的使用結果是,給您們做參考。

useBuiltIns: false 是會按照目前兼容性把需要的 polyfill 放在當前編譯的檔案
useBuiltIns: entry 是會把兼容性需要的 polyfill 放在唯一 import '@babel/polyfill' 的檔案裡面(我沒用過)

ReferenceError: regeneratorRuntime is not defined 不是 babel 的問題,是 regenerator-runtime 的問題,已經被修了,但還沒 publish https://github.com/facebook/regenerator/pull/353/files.

如果有用 useBuiltIns 是可以不用在使用 babel-polyfill/regeneratoruseBuiltIns 加入 polyfill的方式是 require('core-js/lib/<modules>'),把需要 polyfillfunctionvariable 以全域的方式補齊。 babel-polyfill/regenerator 則是以 const module = require('core-js/lib/<modules>') 的方式,僅當前編譯當案有 polyfill

比較起來 useBuiltIns: falsebabel-polyfill/regenerator 基本上是一樣的,只是一個是用全域的方式,一個是本地的方式。

const presets = [
  [
    '@babel/env',
    {
      targets: '> 0.25%, not dead',
      useBuiltIns: false,
      debug: true,
      modules: 'commonjs',
    },
  ],
];

我尝试了一下仅用这个preset,然后在index内加上import '@babel/polyfill';使用async await 并没有报ReferenceError: regeneratorRuntime is not defined
我也看过polyfill的代码,transform-regenerator库是被require了。
我感觉没什么问题,不知道你是怎么出现这种情况的。

@creeperyang 你也可以看一看最后打包出来的文件,看一下有没有这句代码。我按照上面的配置是能搜索到的。

__webpack_require__(/*! regenerator-runtime/runtime */ "./node_modules/regenerator-runtime/runtime.js");

@YardWill 理解你的意思了。你手动引入了@babel/polyfill,再设置useBuiltIns: false,这就是告诉preset-env不要处理任何有关polyfill的事(你自己会手动处理,即你手动引入了@babel/polyfill)。

所以我的意思是对的:设置useBuiltIns: false时,babel不会帮你处理任何polyfill的事,你必须手动处理。

@creeperyang 多谢指教,不过我还有一个问题https://github.com/babel/babel/blob/master/packages/babel-preset-env/src/index.js#L286 为什么entry还需要自己引入polyfill?
以下是debug信息

  1. 没有手动引入polyfill。
Using polyfills with `entry` option:
`import '@babel/polyfill'` was not found.
  1. 手动引入polyfill之后
Using polyfills with `entry` option:
Replaced `@babel/polyfill` with the following polyfills:
  es6.array.copy-within { "android":"4.4", "chrome":"29", "ie":"11" }
  es6.array.fill { "android":"4.4", "chrome":"29", "ie":"11" }
  es6.array.find { "android":"4.4", "chrome":"29", "ie":"11" }// 太长就不贴全了,几乎所有polyfill和target有关。

@YardWill @HsuTing

以一个简单示例来说明。有文件 test.js 如下:

async function hello(a) {
    console.log(a);
}

hello(Promise.resolve(1));

我们用不同的babel配置来验证一些东西。

1. 如果只设置useBuiltIns: false

可以看到,由于transform-async-to-generator plugin起作用,async/wait被翻译到了regeneratorRuntime
但是regeneratorRuntimePromise都没有相应的polyfill被引入——即polyfill完全被忽略(不处理)。

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

function hello(_x) {
  return _hello.apply(this, arguments);
}

function _hello() {
  _hello = _asyncToGenerator(
  /*#__PURE__*/
  regeneratorRuntime.mark(function _callee(a) {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            console.log(a);

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _hello.apply(this, arguments);
}

hello(Promise.resolve(1));

2. 如果只设置useBuiltIns: 'usage'

可以看到文件开头引入了需要的promise/regeneratorRuntime polyfill,但是由于我们未手动依赖(手动安装)regenerator-runtime 库,会报错 ReferenceError: regeneratorRuntime is not defined

同时(require("core-js/modules/es6.promise"))同样会报错,因为我们也没有安装这个依赖。

"use strict";

require("regenerator-runtime/runtime"); // 污染全局,提供 regeneratorRuntime (代码`runtime = global.regeneratorRuntime = inModule ? module.exports : {};`)

require("core-js/modules/es6.promise"); // 污染全局,提供 Promise

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

function hello(_x) {
  return _hello.apply(this, arguments);
}

function _hello() {
  _hello = _asyncToGenerator(
  /*#__PURE__*/
  regeneratorRuntime.mark(function _callee(a) {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            console.log(a);

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _hello.apply(this, arguments);
}

hello(Promise.resolve(1));

3. 设置useBuiltIns: 'usage' 且同时使用plugin-transform-runtime

可以看到,上面的inline代码(如_asyncToGenerator等)被替换成@babel/runtime中相应的引用了。
同时通过require("@babel/runtime/regenerator")@babel/runtime为我们提供了缺失的regenerator-runtime 库。

但(require("core-js/modules/es6.promise"))还是会报错,因为我们没有安装这个依赖。

"use strict";

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

// @babel/runtime/regenerator 依赖 regenerator-runtime/runtime,但以不污染全局的方式引入。
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));

// 然后还是会引入regenerator-runtime/runtime;有重复引入和污染全局的问题;
// TODO:这里需要注意一下!问题待跟进
require("regenerator-runtime/runtime");

// 其实可以看到,core-js 我们并没有作为依赖,但这里却引入了。
// 这一块详见 <https://babeljs.io/docs/en/v7-migration#babel-runtime-babel-plugin-transform-runtime>
require("core-js/modules/es6.promise");

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

function hello(_x) {
  return _hello.apply(this, arguments);
}

function _hello() {
  _hello = (0, _asyncToGenerator2.default)(
  /*#__PURE__*/
  _regenerator.default.mark(function _callee(a) {
    return _regenerator.default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            console.log(a);

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _hello.apply(this, arguments);
}

hello(Promise.resolve(1));

4. 设置useBuiltIns: 'usage' 且同时使用plugin-transform-runtime(且配置core-js)

对比下上面,可以发现Promise被正确修复——代码已经可以正常运行。

"use strict";

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

var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));

require("regenerator-runtime/runtime");

// promise 指向 @babel/runtime-corejs2/core-js/promise,被修复正确
var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));

var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));

function hello(_x) {
  return _hello.apply(this, arguments);
}

function _hello() {
  _hello = (0, _asyncToGenerator2.default)(
  /*#__PURE__*/
  _regenerator.default.mark(function _callee(a) {
    return _regenerator.default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            console.log(a);

          case 1:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _hello.apply(this, arguments);
}

hello(_promise.default.resolve(1));

@creeperyang 我想了解您說的未手動安裝是指 install ? 如果只是未手動安裝,不是只要在個別 install 就好了?我猜 @babel/env 本身因為還有處理其他東西,所以 babel 團隊不希望在把整個 @babel/polyfill 加入到 dependencies 裡面,才需要自行安裝,但不需要自己在 code 裡面一一引入所需要的 core-js

另外

regeneratorRuntime (代码runtime = global.regeneratorRuntime = inModule ? module.exports : {};)

這段我也有找到,但是我自己測試的情況是,在 只設置useBuiltIns: 'usage' 的情況下,就算裝了 core-js@babel/polyfill 一樣會出現 ReferenceError: regeneratorRuntime is not defined,但我手動把 require("regenerator-runtime/runtime"); 改成 var regeneratorRuntime = require("regenerator-runtime/runtime"); 是可以的,所以我猜他是沒有被加到全域變數裡面。

我有試過用 patch-package 方式把 https://github.com/facebook/regenerator/pull/353/files 這段 PR 覆蓋掉 node_modulesregenerator-runtime,則確實是有把 regeneratorRuntime 加到全域變數,則不用另外在手動修正成 var regeneratorRuntime = require("regenerator-runtime/runtime");

@HsuTing
https://github.com/facebook/regenerator/pull/353/files 里已经不在注册全局的regeneratorRuntime了,但是@babel/runtime@7.0.0或者说regenerator-runtime <= 0.13.0里的regenerator-runtime/runtime都是会直接提供全局的regeneratorRuntime的(挂载到global上)。

我说的 手動安裝 更多的意思是必须自己指定 @babel/polyfill 或者 @babel/runtime 等包作为依赖并 install,而不是 babel 自动添加/管理。

现在对generator这里更明白一点了:

  1. async编译后的代码需要 辅助函数+polyfill 才能在目标浏览器正常运行,polyfill包括regenerator-runtime,甚至是promise
  2. 因此,问题就变成了如何引入这两个 polyfill。 这里方法有很多:例如全局全部引入babel-polyfill, 或全局useBuiltIns配置为"usage","entry" 等方式的按需引入; 又或者transform-runtime的局部引入

写了一篇长文,啰嗦了这些实验过程..

useBuiltIns

有项目地址吗