@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
方法,其在解析代码中使用到了Promise
和allSettled
的api,如下图:
babel会根据代码用到的api,最终解析出为这些api注入的polyfill,如下图:
从图可以看出最终需要为Promise
和allSettled
注入的依赖polyfill;但是注入的polyfill存在问题,即es.promise
与es.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天前进行了修复:
进一步查看发现,之前已经有人提出过类似的bug:The order of promise and promise.finally after compilation seems to be wrong,于是做了修复。
总结
困扰我一天的问题算是解决了,分享给大家希望大家避坑。
不过话说回来,开始遇到这个问题时,换成@babel/preset-env
的entry
模式的polyfill模式不会发生任何问题,但是心中过不去这个坎为啥usage
模式不能用,明明后者有一定的体积优势,最终得到答案;这一过程虽然耗费一定的时间,但是有收获,值!
@babel/preset-env使用polyfill遇到的坑的更多相关文章
- Error: Couldn't find preset "env" relative to directory "/Users/user/ethereumjs-vm"
运行npm run build时遇见这个问题,解决办法是安装: npm install --save-dev babel-preset-env 就解决了
- vue客户端渲染首屏优化之道
提取第三方库,缓存,减少打包体积 1. dll动态链接库, 使用DllPlugin DllReferencePlugin,将第三方库提取出来另外打包出来,然后动态引入html.可以提高打包速度和缓存第 ...
- babel-preset-env: a preset that configures Babel for you
转载 babel-preset-env is a new preset that lets you specify an environment and automatically enables t ...
- babel更新之后的 一些坑
最近在使用babel-loader的时候,发生了一些错误,现在的babel-loader版本已经是8.0.0,更新到这个版本之后,如果还按照以前的安装依赖的方法: cnpm install --sav ...
- 《前端之路》之 Babel 下一代 JavaScript 语法编译器
写本章的内容的出发点主要是 为了对于之前关于 JS 版本的一个总结,在之前的开发中,我们始终对于 ECMAScript 的版本的更新不够重视,以至于在后面的 开发过程中,我们始终会被各种新奇的语法打断 ...
- babel 7 简单升级指南
babel 7 babel 7 发布两天了,试着对当前项目更新了下,仅此记录分享 主要改动参考 官方博客 官方升级指南 主要升级内容 不再支持放弃维护的 node 版本 0.10.0.12.4.5 使 ...
- Babel总结
什么是babel? babel是一个JavaScript编译器. Babel是一个工具链,主要用于将ECMAScript 2015+代码转换为向后兼容的旧浏览器或环境中JavaScript版本. 注解 ...
- 前端利器躬行记(2)——Babel
Babel是一个JavaScript编译器,不仅能将当前运行环境不支持的JavaScript语法(例如ES6.ES7等)编译成向下兼容的可用语法(例如ES3或ES5),这其中会涉及新语法的转换和缺失特 ...
- 前端自动化之babel本地化安装
npm添加package.json cd到项目根目录直接调用npm init 会创建package.json文件 本地安装bebel(并非全局安装,这种情况下cmd命令中babel命令不识别): 步骤 ...
随机推荐
- NGK和USDN的应用
一.NGK和USDN的发展方向 目前区块链将会朝着两个方向去发展,第一种是金融经济的衍生品,第二种是商业应用,快速支付的货币体系,NGK.IO公链是基于分布式应用设计的商用金融区块链操作系统,通过数字 ...
- BGV再度爆发,流通市值破500万美金!
BGV似乎以超乎寻常的姿态,开启了爆发的模式.这两天,BGV一路上涨,日内最高涨至548.78美金,24小时成交额达到了98.07万美金,24小时成交量达到1844.93枚BGV,流通市值更是突破了5 ...
- 按键显示器(判断键盘监听器获得的值为普通Key还中modifiers)
1 import sys 2 from PyQt5 import QtWidgets,QtCore 3 from PyQt5.QtCore import Qt 4 from PyQt5.uic.pro ...
- jdbc连接数据库(oracle、mysql)
很简单,直接贴代码吧!代码注释自认为足够理解! 第一步创建数据库连接类,数据库连接地址.数据库驱动.用户名.密码建议创建为公共变量,方便修改,一目了然. package db; import java ...
- Linux-两种磁盘分区方式
Linux文件设备 要理解Linux,首先要理解Linux文件结构 在Linux操作系统中,几乎所有的设备都位于/dev目录中 名称 作用 位置 SATA接口 电脑硬盘接口 /dev/sd[a-p] ...
- 第50天学习打卡(JavaScript)
前端三要素 HTML(结构):超文本标记语言(Hyper Text Markup Language),决定网页的结构和内容. CSS(表现):层叠样式表(Cascading Style Sheets) ...
- vue关于导航守卫的几种应用场景
beforeEach 该钩子函数主要用来做权限的管理认证 router.beforeEach((to, from, next) => { if (to.matched.some(record = ...
- 基于OpenSSL的PKI的PKI数字证书系统实现
本篇主要介绍了基于OpenSSL的PKI的PKI数字证书系统实现,利用OpenSSL建立一个CA中心的详细解决方案和建立的具体步骤. 1.PKI数字证书系统设计 PKI数字证书系统主要包括证书颁发机构 ...
- 007-变量的作用域和LED点阵
变量 一.局部变量和全局变量 局部变量:函数内申明的变量,只在函数内有效. 全局变量:函数外部申明的变量.一个源程序文件有一个或者多个函数,全局变量对他们都起作用. 备注:全局变量有副作用,降低了函数 ...
- java基础知识 + 常见面试题
准备校招面试之Java篇 一. Java SE 部分 1.1 Java基础 1. 请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的? Object 的 hash ...