背景
目前babel
只对语法进行转译,而API
支持交给了core-js
,一直以来对于这块市面上的资料都说不清楚,最近笔者在公司进行前端工程化建设,于是踏上了babel的polyfill配置之旅
语法转译
语法转译的配置非常简单
安装依赖
yarn add babel-loader @babel/core @babel/preset-env
复制代码
配置webpack
module.exports = {
// 引入编译选项
presets: [
[
'@babel/preset-env'
],
],
};
复制代码
大功告成
API支持
API支持是目前最有疑惑的,目前API
支持分为两大类方向,一个是使用'@babel/preset-env'
配合usage
或者entry
,另一个就是@babel/plugin-transform-runtime
配置@babel/runtime-corejs3
和core-js@3
@babel/plugin-transform-runtime
翻译一下就是使用之前的@babel/polyfill
会改写全局属性,导致可能在多个第三库发生问题,@babel/plugin-transform-runtime
就是为了不污染全局环境,所以一般用于开发库
使用起来很简单
安装依赖
yarn add @babel/plugin-transform-runtime
yarn add core-js@"^3"
yarn add @babel/runtime-corejs3
复制代码
配置babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env'
],
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: 3,
}
]
]
};
复制代码
@babel/preset-env + usage or entry
usage
和entry
比较好理解,entry
默认会把整个core-js
打包进最终代码,而usage
会分析你使用到的代码
@babel/preset-env + entry
index.js
我们在打包入口引入core-js/stable
和regenerator-runtime/runtime
用来代替废弃的@babel/polyfill
import 'core-js/stable'
import "regenerator-runtime/runtime";
复制代码
配置useBuiltIns为entry
module.exports = {
presets: [
[
'@babel/preset-env',{
useBuiltIns: 'entry',
corejs: 3,
},
],
],
};
复制代码
打包结果
可以发现我们随意搜索一个ES6+
的API
都能在打包后的代码中找到,同时也注意到打包未压缩前的体积非常大,聪明的工程师们就在想,能否分析项目用到哪些API,然后按需加到最终的代码结果呢
@babel/preset-env + usage
usage
是为了提供一种动态分析项目使用到的API
选项,配置只是把entry
换成usage
,同时删除入口的polyfill
index.js
// import 'core-js/stable'
// import "regenerator-runtime/runtime";
Promise.resolve(1).then(console.log)
复制代码
module.exports = {
presets: [
[
'@babel/preset-env',{
useBuiltIns: 'usage',
corejs: 3,
},
],
],
};
复制代码
打包结果
重新打包后发现,我们使用的Promise
已经引入,而且没使用到的API
也没打包到里面,体积从400多KB降到了60KB,效果明显,usage按需打包根据源代码中出现的语言特性自动检测需要的 polyfill。这确保了最终包里 polyfill 数量的最小化。然而,这也意味着如果其中一个依赖需要特殊的 polyfill,默认情况下 Babel 无法将其检测出来。node_modules的依赖如果依赖了一个ES6的API,然而你不知道,就会导致生产环境出现兼容性问题,使用usage其实就是将减少的体积成本转移到了开发时要对npm包的代码是否使用了ES6+的API熟悉成本
处理babel内联函数
例子
演示代码
a.js
export default class B {
}
复制代码
b.js
export default class B {
}
复制代码
index.js
import A from './a.js'
import B from './b.js'
// 加一个副作用,不然会被tree-shaking A和B的代码
console.log(A,B);
复制代码
打包结果
每个文件class
都有一个_classCallCheck
,可以尝试增更多文件,可以验证助手函数在每个文件都会内联一次,增加了代码体积
@babel/plugin-transform-runtime提取内联函数
在使用@babel/preset-env + usage or entry
的模式下,我们的助手函数会被内联在每个文件,导致代码体积无意义的增加,我在官网找到一个描述
Babel
会使用助手函数,默认会添加到每一个文件,可以使用@babel/plugin-transform-runtime
提取内联函数
安装依赖
yarn add @babel/plugin-transform-runtime
yarn add core-js@"^3"
yarn add @babel/runtime-corejs3
复制代码
配置babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
},
],
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: 3,
}
]
]
};
复制代码
打包结果
打包之后我们发现助手函数从内联变成了从一个包引入,成功解决了内联函数在多个文件引入的问题
@babel/plugin-transform-runtime的疑问
@babel/preset-env
加上usage
配置@babel/plugin-transform-runtime
确实看起来完美,在某一个查看打包后的代码,我发现了端倪:
index.js
Promise.resolve(1).then(console.log)
复制代码
打包结果
我们找到刚才写的代码,发现Promise
被替换成了一个promise_default
,查找promise_default
发现来自runtime-corejs3
,同时发现全局也引入了一个Promise
@babel/plugin-transform-runtime 和 @babel/preset-env + usage都引入了Promise这个变量,造成了代码冗余
@babel/plugin-transform-runtime不能配合@babel/preset-env + usage?
因为发现输出后的代码出现Promise
这样的冗余,我一度放弃了提起内联函数,从配置从删除@babel/plugin-transform-runtime
,偶尔一天我去查阅vue-cli
的源码发现使用了@babel/plugin-transform-runtime
plugins.push([require('@babel/plugin-transform-runtime'), {
regenerator: true,
// polyfills are injected by preset-env & polyfillsPlugin, so no need to add them again
corejs: false,
// 这里VueCli只在useBuiltIns为usage提取内联函数,其实无论usage和entry都可以提取函数,CRA就是这样做的
helpers: useBuiltIns === 'usage',
// By default, babel assumes babel/runtime version 7.0.0-beta.0,
// explicitly resolving to match the provided helper functions.
// https://github.com/babel/babel/issues/10261
version: require('@babel/runtime-corejs3/package.json').version,
}])
复制代码
大家看到注释都应该明白了,我们只要把@babel/plugin-transform-runtime
的corejs
设置为false
就能解决API会冗余的问题了
最终的babel配置
安装依赖
yarn add babel-loader @babel/core @babel/preset-env
yarn add @babel/plugin-transform-runtime
yarn add core-js@"^3"
yarn add @babel/runtime-corejs3
复制代码
babel.config.js配置
const path = require('path')
module.exports = {
presets: [
[
'@babel/preset-env',{
// 可以entry或者usage
useBuiltIns: 'entry',
corejs: 3,
},
],
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: false,
version: require('@babel/runtime-corejs3/package.json').version,
absoluteRuntime: path.dirname(
require.resolve('@babel/runtime-corejs3/package.json')
)
}
]
]
};
复制代码