前些日子虽然写了 最近折腾 @babel/preset-env 的一些小心得,但其实没有正确的理解和配置 browserslist,所以今天问题又来了。
同事问我,在项目 A 中使用内部库 B,A 里面需要继承和扩写 B,结果运行时报错:
TypeError: Class constructor c cannot be invoked without 'new'
看起来应该跟 ES6 Class 有关,报错的位置是构造函数,调用了 super()
,我初步猜测:B 在编译时配置不当,导致编译后的文件被转换成了 ES5,也就是基于原型链继承的结构,所以在子类 super()
的时候报错。
接下来验证,打开 node_modules/B/dist/index.js
,查找定义 class
的代码,出乎意料,B 的生产代码的确是 ES6 的,问题似乎不在这里。然后我把 import {ClassB} from 'B'
改成 import ClassB from 'B/src/ClassB'
,问题依旧,看来的确不是 B 错误编译造成的。
于是回到项目 A。项目 A 是使用 @vue/cli 创建的项目,里面 Babel 相关配置是默认的,预制组写在 babel.config.js
里,是 @vue/cli-plugin-babel/preset;目标浏览器写在 package.json 里,内容如下:
{
"browserslist": [
"> 1%",
"last 2 versions"
]
}
按照我之前的理解,这里的意思是:
- 市场占有率大于1%
- 浏览器最新的两个版本
- 二者需要同时满足,是且的关系
但实际上,在命令行里执行 npx browserslist
,可以看到当前要支持的浏览器列表(其实仔细一看,这个列表和 最近折腾 @babel/preset-env 的一些小心得 里的是一样的……):
and_chr 76
and_ff 68
and_qq 1.2
and_uc 12.12
android 76
baidu 7.12
bb 10
bb 7
chrome 77
chrome 76
chrome 75
edge 18
edge 17
firefox 69
firefox 68
ie 11
ie 10
ie_mob 11
ie_mob 10
ios_saf 12.2-12.3
ios_saf 12.0-12.1
kaios 2.5
op_mini all
op_mob 46
op_mob 12.1
opera 62
opera 60
safari 12.1
safari 12
samsung 9.2
samsung 8.2
今天再看这个列表,感觉有些奇怪。别的不说,bb 和 ie_mob 应该都是非常古老的浏览器,以我对身边人的观察,相关设备早就已经退出历史舞台了。结合今天 class 的诡异报错,我觉得问题就在这里。
一般这种时候先试一下极端手段。我把浏览器改成了 chrome >= 75
,指定只要最新浏览器,然后再次执行 npx browserslist
,输出的结果果然只有最新的 chrome:
chrome 77
chrome 76
chrome 75
然后在项目中尝试刚才的操作,果然 bug 消失了。
再把 last 2 version
加回来,所有浏览器都回来了,bug 也如期复现。
好吧,看来之前对 browserslist 的理解是错的,定义项的关系是“或”,也就是所有筛选出来的浏览器取合集,所以我们不知不觉间其实在支持“所有浏览器的最后两个版本”,所以为了兼容那些古老浏览器,会把 ES6 class 转译成 ES5 的原型链格式,于是无法正确继承。
请注意:query 里面的
not
系列是“且”的关系,也就是从大集合里剔除某些项目的意思,比如not ie <= 10
就是不考虑 IE 10-。
最后,我们把配置改成了兼容主流桌面浏览器,问题解决。
{
"browserslist": [
"last 5 chrome versions",
"last 5 safari versions",
"last 5 firefox versions",
"last 5 edge versions"
]
}
babel.config.js
我在做上述事情的同时,我的同事也在做类似的尝试,不过她的目标是 `babel.config.js`。很遗憾,她没有成功。从文档来看,其实除了 browserslist,其它配置都在这个文件里……
总结
相比于早些年,前端工具链大幅提升了我们的开发效率,也带来不少新知识需要学习。根据我日常学习经验来看,这方面的分享比较少,而且流于表面;另一方面,这些工具链的文档阅读量不小。所以建议大家坚持学习,每次解决一些问题,慢慢的,就能把整个前端工具链都摸清楚了。
不过我没想通的是,为啥 @vue/cli 默认的目标浏览器设置的这么保守……
欢迎吐槽,共同进步