1.背景
- 日常项目开发中总是避免不了对低版本浏览器做一些兼容处理,最常见的手段就是结合编译工具使用babel来处理一些语法的兼容,但是每次使用的时候其实Babel的配置和使用到的相关库总是云里雾里,网上的各种推荐方案眼花缭乱不知道到底应该怎么选择。
- 今天抱着学习的心态,和大家一起好好梳理一下
2.准备工作
- 考虑到大多数项目都在使用
webpack
,今天就用webpack
来学习验证babel
- 目录结构
- 简单的配置
const path = require('path');
module.exports = {
entry: './src/main',
mode: 'development',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
}
]
}
};
- 需要用的的依赖,先混个眼熟
core-js
、regenerator-runtime
、@babel/preset-env
、@babel/runtime
、@babel/runtime-corejs3
、@babel/plugin-transform-runtime
3.配置bable
我们就以promise
为例,看一下打包后补丁的效果
main.js
const a = 10;
const b = 11;
let add = (a, b) => {
return Promise.resolve(a + b);
}
add(a, b);
- 没有使用
babel
时,可以看到没有引入任何其他模块,语法也没有做转换 bundle.js
的结果
- 包体积
3.1 直接引入polyfill
- 安装
core-js
regenerator-runtime
- 配置
babel.config,js
module.exports = {
presets: [
["@babel/preset-env", {
useBuiltIns: false // or "entry" "usage" false
}]
]
}
main.js
中引入对应补丁
import "core-js/stable";
import "regenerator-runtime/runtime";
const a = 10;
const b = 11;
let add = (a, b) => {
return Promise.resolve(a + b);
}
add(a, b);
bundle.js
,可以看到是存在promise的polyfill的- 因为是全局引入包体积明显变大,948k
- 可能有的同学看到过直接引入
@babel/polyfill
的 - 其实是在
babel7.4.0
之前,可以直接安装@babel/polyfill
来转换 API,但是在 7.4.0 之后,就会提示让我们分开引入core-js/stable
和regenerator-runtime/runtime
了。
core-js
:是整个core-js
的核心,提供了基础的垫片能力,但是直接使用core-js
会污染全局命名空间和对象原型;regenerator-runtime/runtime
:主要用来兼容async generator
等语法
3.2 @babel/preset-env按需引入
- 首先呢我们先看一下
@babel/preset-env
有哪些选项
targets
targets
可以指定项目的运行环境。如果没有配置 targets,那么@babel/preset-env
会接着寻找项目中的browserslist
配置,browserslist
配置只会控制语法的目标环境。如果targets
和browserslist
都没有,那么@babel/preset-env
就会全量处理语法和 API。- browserslist 具体配置
useBuiltIns
useBuiltIns
决定了@babel/preset-env
该如何处理polyfill
。可选值有:“usage” 、“entry” 、和 false, 默认为 false。
false
,polyfill 就不会被按需处理会被全部引入。entry
,我们配置了targets,entry就是针对目标环境的全部,所以是对配置目标环境引入的所有 polyfill 扩展包,会自动将import “core-js/stable” 和 import “regenerator-runtime/runtime” 转换为目标环境的按需引入。usage
,则不需要手动导入 polyfill,babel 检测出此配置会自动进行 polyfill 的引入。
3.2.1 全局引入
- 安装
@babel/preset-env
- 配置
babel.config.js
首先看一下全局引入
module.exports = {
presets: [
["@babel/preset-env", {
targets: {
browsers: [
'Android >= 4.4',
'iOS >= 9.0',
],
},
useBuiltIns: "entry", // or "entry" "usage"
corejs: 3
}]
]
}
main.js
全局引入,不然useBuiltIns: "entry"
也不生效
import "core-js/stable";
import "regenerator-runtime/runtime";
const a = 10;
const b = 11;
let add = (a, b) => {
return Promise.resolve(a + b);
}
add(a, b);
- 我们可以和之前的全局引入打包结果对比一下948k=>822k
3.2.2 按需引入
- 接下来我们看一下按需引入
- 修改
babel.config.js
使用 usage
module.exports = {
presets: [
["@babel/preset-env", {
targets: {
browsers: [
'Android >= 4.4',
'iOS >= 9.0',
],
},
useBuiltIns: "usage", // or "entry" "usage"
corejs: 3
}]
]
}
main.js
中不需要全局引入polyfiill
了
const a = 10;
const b = 11;
let add = (a, b) => {
return Promise.resolve(a + b);
}
add(a, b);
- 打包结果,包体积又从822k => 154k,明显减少
3.3 @babel/plugin-transform-runtime进行优化
@babel/plugin-transform-runtime
这个插件呢主要有两个作用
- 自动移除语法转换后内联的辅助函数,替换为
@babel/runtime/helpers
里的辅助函数,以节省代码体积- 就是对 API 进行转换的时候,避免污染全局变量。
下面我们针对这两个功能具体看看效果
- 安装
@babel/plugin-transform-runtime
、@babel/runtime
、@babel/runtime-corejs3
3.3.1 优化体积
- 为了方便看效果,我们新建三个文件
- 三个文件都相同,就默认导出一个类
class A {
a = 1
}
export default A;
- 在入口文件
main.js
中引入
import A from './a.js';
import B from './b.js';
import C from './c.js';
const a = 10;
const b = 11;
let add = (a, b) => {
console.log(A, B, C);
return Promise.resolve(a + b);
}
add(a, b);
- 打包一下看看大小,没有配置插件时270k
- 配置
babel.config.js
使用@babel/plugin-transform-runtime
module.exports = {
presets: [
["@babel/preset-env", {
targets: {
browsers: [
'Android >= 4.4',
'iOS >= 9.0',
],
},
useBuiltIns: "usage", // or "entry"
corejs: 3
}]
],
plugins: [
['@babel/plugin-transform-runtime']
]
}
- 再次打包可以看到,270k => 166k,体积明显减小
- 怎么理解这个插件减小体积的功能呢?
- Babel 在转译时,有时候会使用一些辅助的函数来帮忙,比如我们需要转译 class 类,就会在文件中注入_classCallCheck 这个函数来辅助转换,如果每个文件都有相同的语法,那每个文件都会注入相同的一份辅助函数;
- 我们使用
@babel/plugin-transform-runtime
自动将需要引入的 helpers 函数替换为从@babel/runtime
中的引用。 - 那么配合webpack等打包工具,最终相同的引用只都公用一份,从而减小了体积。
3.3.2 避免全局污染
-
开头说到
@babel/plugin-transform-runtime
还有另一个关键的作用就是对 API 进行转换的时候,避免污染全局变量,但是看截图中引入的对应polyfill包还是从core-js中引用的,和没有使用插件时一样,似并没有改变。 -
其实这个插件是有一些选项的,具体可以查看官网 https://babeljs.io/docs/en/babel-plugin-transform-runtime/#options
-
我们上面的配置的默认选项是这样的
...
// plugins: [
// ['@babel/plugin-transform-runtime']
// ]
plugins: [
[
'@babel/plugin-transform-runtime',
{ // 默认配置
helpers: true,
corejs: false,
regenerator: true,
useESModules: false,
absoluteRuntime: false
}
]
]
}
- 可以看到
corejs: false
,这个参数就是用来设置是否做API转换以避免污染全局环境的,我们开启看看效果
plugins: [
['@babel/plugin-transform-runtime', {
"helpers": true,
"corejs": 3 // 可选值为 2 3 false
}]
]
- 引入的polyfill的包由原来的
core-js
=>core-js-pure
@babel/runtime-cojs3
- 是怎么避免的全局污染呢?
core-js@3其实分为一下几个模块:
- core-js:是整个 core-js 的核心,提供了基础的垫片能力,但是直接使用 core-js 会污染全局命名空间和对象原型;
- core-js-pure:core-js-pure 提供了独立的命名空间,不污染全局变量;
- core-js-compact:根据 Browserslist 维护了不同宿主环境、不同版本下对应需要支持特性的集合;
- core-js-builder:结合 core-js-compact 以及 core-js,并利用 webpack 能力,根据需求打包出 core-js 的 core-js-bundle
-
插件可以将 core-js 中 API 的 polyfill 直接修改原型改为从 @babel/runtime-corejs3中获取,避免了对全局变量和原型的污染;
-
当开启了
corejs
之后呢,由于@babel/plugin-transform-runtime
可以帮我们引入对应的polyfill,所以我们也可以不再使用@babel/preset-env
中添加polyfill的能力了 -
不过API转换主要是给开发JS库或npm包等的人用的,我们的前端工程一般仍然使用polyfill补齐API就可以了;
小小总结一下,一般项目开发就是使用@babel/plugin-transform-runtime
默认配置 + @babel/preset-env
按需引入 就可以啦
如有不对的地方,请大家及时指出,避免影响其他同学;