@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模式不能用,明明后者有一定的體積優勢,最終得到答案;這一過程雖然耗費一定的時間,但是有收獲,值!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM