以前在用babel 的时候有个地方一直挺晕的,@babel/preset-env
和 @babel/plugin-transform-runtime
都具备转换语法的能力, 而且都能实现按需 polyfill
,可是网上又找不到比较明确的答案, 趁此次尝试 roullp 的时候试了试.node
若是咱们什么都不作, 没有为babel 编写参数及配置, 那babel 并无那么大的威力, 它什么都不会作, 正是由于各个预设插件的灵活组合、赋能, 让 babel 充满魅力, 创造奇迹es6
首先是 @babel/preset-envchrome
这是一个咱们很经常使用的预设, 几乎全部的教程和框架里都会让你配置它, 它的出现取代了 preset-es20**
系列的babel 预设, 你不再须要繁杂的兼容配置了。 每出一个新提案就加一个? 太蠢了。segmentfault
有了它, 咱们就能够拥有所有, 而且! 它还能够作到按需加载咱们须要的 polyfill。 就是这么神奇。
可是吧, 它也不是那么自动化的, 若是你要是不会配置,颇有可能就没有用起它的功能promise
无论怎么养, 首先试一下,眼见为实浏览器
首先建立一个 index.js
,内容以下, 很简单babel
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) console.log(arr)
而后咱们在根目录下建立一个 .babelrc
文件, 帮咱们刚刚说的预设加进去框架
{ "presets": [ ["@babel/preset-env"] ] }
而后我咱们打包一下(这里我用的是roullup)函数
看一下产出的结果ui
咱们能够看到, 它babel帮咱们作了这几件事情:
奇怪, 为何 babel 不帮咱们转换 map ? 还有 promise 这些也都是es6的特性呀
嗯~,会不会是咱们的目标浏览器不对, babel 以为不须要转换了, 会不会是这样, 那咱们加一个 .browserslistrc 试一下
那就。让咱们在根目录下建立一个 .browserslistrc
好。如今让咱们再打包一次.
咦, 没什么效果。 跟刚刚同样啊。 说明不是目标浏览器配置的问题, 是babel 作不了这个事。
由于默认 @babel/preset-env 只会转换语法,也就是咱们看到的箭头函数、const一类。
若是进一步须要转换内置对象、实例方法,那就得用polyfill, 这就须要你作一点配置了,
这里有一个相当重要的参数 "useBuiltIns",他是控制 @babel/preset-env 使用何种方式帮咱们导入 polyfill 的核心, 它有三个值能够选
这是一种入口导入方式, 只要咱们在打包配置入口 或者 文件入口写入 import "core-js"
这样一串代码, babel 就会替咱们根据当前你所配置的目标浏览器(browserslist)来引入所须要的polyfill 。
像这样, 咱们在 index.js 文件中加入试一下core-js
// src/index.js import "core-js"; function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) console.log(arr)
babel配置以下
[ "presets": [ ["@babel/preset-env", { "useBuiltIns": "entry" } ] ] }
当前 .browserslistrc 文件(更改目标浏览器为 Chrome 是为了此处演示更直观,简洁), 咱们只要求兼容 chrome 50版本以上便可(当下最新版本为78)
Chrome > 50
那打包后如何呢?
恐怖如斯啊,babel把咱们填写的 import "core-js"
替换掉, 转而导入了一大片的polyfill, 并且都是一些我没有用到的东西。
那咱们提高一下目标浏览器呢? 它还会导入这么多吗?
此时, 咱们把目标浏览器调整为比较接近最新版本的 75(当下最新版本为78)
// .browserslistrc Chrome > 75
此刻打包后引入的 polyfill 明显少了好多。
但一样是咱们没用过的。
这也就是印证了上面所说的, 当 useBuiltIns 的值为 entry 时, @babel/preset-env 会按照你所设置的目标浏览器在入口处来引入所需的 polyfill,
无论你需不须要。
如此,咱们能够知道, useBuiltIns = entry 的优势是覆盖面积就比较广, 一股脑所有搞定, 可是缺点就是打出来的包就大了多了不少没有用到的 polyfill, 而且还会污染全局
这个就比较神奇了, useBuiltIns = useage 时,会参考目标浏览器(browserslist) 和 代码中所使用到的特性来按需加入 polyfill
固然, 使用 useBuiltIns = useage, 还须要填写另外一个参数 corejs 的版本号,
core-js 支持两个版本, 2 或 3, 不少新特性已经不会加入到 2 里面了, 好比: flat 等等最新的方法, 2 这个版本里面都是没有的, 因此建议你们用3
此时的 .babelrc
{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 } ] ] }
此时的 index.js
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) console.log(arr) console.log( hasNumber(2) )
此时的 .browserslistrc
> 1% last 10 versions not ie <= 8
打包后:
nice ,够神奇, 咱们用的几个新特性真的统统都加上了
这种方式打包体积不大,可是若是咱们排除node_modules/目录,赶上没有通过转译的第三方包,就检测不到第三方包内部的 ‘hello‘.includes(‘h‘)这种句法,这时候咱们就会遇到bug
剩下最后一个 useBuiltIns = false , 那就简单了, 这也是默认值 , 使用这个值时不引入 polyfill
这种方式会借助 helper function 来实现特性的兼容,
而且利用 @babel/plugin-transform-runtime 插件还能以沙箱垫片的方式防止污染全局, 并抽离公共的 helper function , 以节省代码的冗余
也就是说 @babel/runtime 是一个核心, 一种实现方式, 而 @babel/plugin-transform-runtime 就是一个管家, 负责更好的重复使用 @babel/runtime
@babel/plugin-transform-runtime 插件也有一个 corejs 参数须要填写
版本2 不支持内置对象 , 但自从Babel 7.4.0 以后,拥有了 @babel/runtime-corejs3 , 咱们能够放心使用 corejs: 3 对实例方法作支持
当前的 .babelrc
{ "presets": [ ["@babel/preset-env"] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] }
当前的 index.js
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) console.log(arr) console.log( hasNumber(2) )
打包后以下:
咱们看到使用 @babel/plugin-transform-runtime 编译后的代码和以前的 @babel/preset-env 编译结果大不同了,
它使用了帮助函数, 而且赋予了别名 , 抽出为公共方法, 实现复用。 好比它用了 _Promise 代替了 new Promise , 从而避免了建立全局对象
useage 和 @babel/runtime 同时使用的状况下比较智能, 并无引入重复的 polyfill
我的分析缘由应该是: babel 的 plugin 比 prset 要先执行, 因此preset-env 获得了 @babel/runtime 使用帮助函数包装后的代码,而 useage 又是检测代码使用哪些新特性来判断的, 因此它拿到手的只是一堆 帮助函数, 天然没有效果了
实验过程以下:
当前index.js
function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) const hasNumber2 = (num) => [4, 5, 6, 7, 8, 9].includes(num) console.log(arr) console.log( hasNumber(2)) console.log( hasNumber2(3) )
当前 .babelrc
{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] }
打包结果:
跟 useage 的状况不同, entry 模式下, 在通过 @babel/runtime 处理后不但有了各类帮助函数还引入了许多polyfill, 这就会致使打包体积无情的增大
我的分析: entry 模式下遭遇到入口的
import "core-js"
及就当即替换为当前目标浏览器下所需的全部 polyfill, 因此也就跟 @babel/runtime 互不冲突了, 致使了重复引入代码的问题, 因此这两种方式千万不要一块儿使用, 二选一便可
实现过程以下:
当前 index.js:
import "core-js" function test() { new Promise() } test() const arr = [1,2,3,4].map(item => item * item) const hasNumber = (num) => [4, 5, 6, 7, 8].includes(num) const hasNumber2 = (num) => [4, 5, 6, 7, 8, 9].includes(num) console.log(arr) console.log( hasNumber(2)) console.log( hasNumber2(3) )
当前 .babelrc
{ "presets": [ ["@babel/preset-env", { "useBuiltIns": "entry" } ] ], "plugins": [ [ "@babel/plugin-transform-runtime", { "corejs": 3 } ] ] }
当前 .browserslistrc 的目标版本(为了减小打包后的文件行数为又改成chrome 了, 懂那个意思就行)
Chrome > 70
打包结果:
@babel/preset-env 拥有根据 useBuiltIns 参数的多种polyfill实现,优势是覆盖面比较全(entry), 缺点是会污染全局, 推荐在业务项目中使用
上面 1, 2 两种方式取其一便可, 同时使用没有意义, 还可能形成重复的 polyfill 文件