• @babel/preset-env使用polyfill遇到的坑


    场景还原

    最近将一个项目由babel@6升级到babel@7,升级后最重要的两个包:

    • @babel/preset-env: 提供代码的转换和API的polyfill的能力
    • @babel/plugin-transform-runtime: 复用babel注入的helper代码以及提供无污染全局环境的polyfill功能

    基于此,对项目中js语法的transform和API的polyfill进行了调整:

    • 关闭@babel/plugin-transform-runtime的polyfill功能
    • 开启@babel/preset-env的polyfill和transform功能

    其中,@babel/preset-env的polyfill使用usage形式(不了解的可以查看官方文档),意思是以项目设置的target环境为前提,根据项目中使用到的api功能进行polyfill;具体babel配置片段如下:

    {
      "plugins": [
        [
          "@babel/plugin-transform-runtime",
          {
            "corejs": false,
            "regenerator": false
          }
        ]
      ],
      "sourceType": "unambiguous",
      "presets": [
        [
          "@babel/preset-env",
          {
            "modules": false,
            "shippedProposals": true,
            "useBuiltIns": "usage",
            "corejs": {
              "version": "3.10",
              "proposals": true
            },
            "targets": {
                ...
            }
          }
        ]
      ]
    }
    

    然后项目中使用到了Promise.allSettled静态方法:

    Promise.allSettled([p1, p2, p3]).then(res => console.log(res));
    

    通过webpack打包后运行,js会报错:

    TypeError: Promise.allSettled is not a function

    不对呀,按照官网就是这么配置的,一度对babel的配置产生怀疑,折腾半天最后都排除掉;没招了,那就试试断点调试,别说还真发现问题,直接上图:

    相信大家能够看出问题所在,Promise.allSettled的polyfill之后重新引入Promise的polyfill,后面的Promise的polyfill覆盖了Promise.allSettled的polyfill,导致调用该方法时报错。

    那会不会是babel的bug导致的呢,于是开起查找问题之旅了。。。

    问题追踪

    首先,简要说明下@babel/preset-env实现polyfill的思路:babel会生成代码的ast,并对其traverse过程中,根据代码使用的新API来确定需要填充的polyfill。

    遇到这种问题,首先想到会不会是@babel/preset-env的bug,google半天也没有找到类似问题,于是就开启debug调试模式。在调试追踪到babel-plugin-polyfill-corejs3/lib/index.js中的usageGlobal方法,其在解析代码中使用到了PromiseallSettled的api,如下图:

    babel会根据代码用到的api,最终解析出为这些api注入的polyfill,如下图:

    从图可以看出最终需要为PromiseallSettled注入的依赖polyfill;但是注入的polyfill存在问题,即es.promisees.promise.all-settled顺序反了,后者依赖前者;由此可见是babel的bug已确定无疑了。

    接着进如resolve方法,发现其在确定代码的相关polyfill依赖后,对与依赖的先后顺序存在bug;因为代码调用Promise.allSettled会依赖:

    • 全局global的Promise api
    • Promise的静态方法allSettled api

    所以babel在获取二者对应的polyfill在合并时产生了问题,这可以在babel-plugin-polyfill-corejs/lib/built-in-definitions.js文件中:

    // 所有静态方法的polyfill
    const StaticProperties = {
        ...
        Promise: {
            all: define(null, PromiseDependenciesWithIterators),
            allSettled: define(null, ["es.promise.all-settled", ...PromiseDependenciesWithIterators]),
            any: define(null, ["esnext.promise.any", ...PromiseDependenciesWithIterators]),
            race: define(null, PromiseDependenciesWithIterators),
            try: define(null, ["esnext.promise.try", ...PromiseDependenciesWithIterators])
          },
      ...
    }
    

    可以看出Promise的相关静态方法的polyfill都放置到第一位,而define为对该数值进行任何排序:

    const define = (pure, global, name = global[0], exclude) => {
      return {
        name,
        pure,
        global,
        exclude
      };
    };
    

    查到这里可以猜测这个babel-plugin-polyfill-corejs3@0.1.7有bug,查看最新版本0.2.0的代码发现对这个方法进行了修复:

    var _data = _interopRequireDefault(require("../core-js-compat/data.js"));
    
    const polyfillsOrder = {};
    Object.keys(_data.default).forEach((name, index) => {
      polyfillsOrder[name] = index;
    });
    
    const define = (pure, global, name = global[0], exclude) => {
      return {
        name,
        pure,
        global: global.sort((a, b) => polyfillsOrder[a] - polyfillsOrder[b]),
        exclude
      };
    };
    

    可以看出该方法对注入的polyfill做了排序,进过排序得到正确的依赖顺序,于是果断升级@babel/preset-env@7.13.15,因为之前@babel/preset-env@7.13.10依赖的是babel-plugin-polyfill-corejs3@0.1.7,至此一直困扰我的这个大坑给堵上了。

    出于好奇心,对babel-plugin-polyfill-corejs3代码进行blame,果然发现这个问题在24天前进行了修复:

    blame.png

    进一步查看发现,之前已经有人提出过类似的bug:The order of promise and promise.finally after compilation seems to be wrong,于是做了修复。

    总结

    困扰我一天的问题算是解决了,分享给大家希望大家避坑。

    不过话说回来,开始遇到这个问题时,换成@babel/preset-enventry模式的polyfill模式不会发生任何问题,但是心中过不去这个坎为啥usage模式不能用,明明后者有一定的体积优势,最终得到答案;这一过程虽然耗费一定的时间,但是有收获,值!

  • 相关阅读:
    指针与数组
    深入函数
    到底是使用指针还是引用 ,混合使用以及易错点
    返回值作为标志
    c++的引用(二)
    内联函数
    c++的引用
    指针总结以及常量指针与指向常量的指针与指向常量的常指针
    c++中的 堆和栈
    Java Messages Synchronous and Asynchronous
  • 原文地址:https://www.cnblogs.com/wonyun/p/14667430.html
Copyright © 2020-2023  润新知