前言
项目中看到了一堆babel
包,但是不知道这些babel
包各自的作用到底是啥,因此想梳理一下这些babel
包的用法,了解各自的原理,确认是否有必要引入该babel
包。
本文通过babel + webpack来介绍babel。
babel是啥?
从官网中就可以知道
babel是JavaScript的编译器
它主要的作用是以下三点:
- 转换语法
- 为目标环境中缺失的语法提供垫片(Polyfill)
- 源码转换(模块代码)
目录结构
|- node_modules
|- src
- index.js
|- .browserslistrc
|- babel.config.js
|- index.html
|- package.json
|- webpack.config.js
复制代码
ES6转ES5
有三个包密不可分,@babel/core
、@babel/preset-env
、babel-loader
babel-loader
作用:该包允许使用babel
和webpack
编译JavaScript文件
因此我们可以在webpack.config.js
文件中配置
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
}
复制代码
@babel/core
有了babel-loader还不够,从babel-loader源码中可以发现,babel-loader包中依赖了@babel/core
,如下图所示:
那这个包到底是干啥用的呢?
把 js 代码分析成 ast(Abstract Syntax Tree,抽象语法树) ,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本 js。
总结:把js代码转换成AST,便于插件分析。
@babel/preset-env
有了以上两个包还不够,我们需要确定我们的目标浏览器来判断是否需要转换代码,因此还需要@babel/preset-env
,它的作用是:
@babel/preset-env是一个智能预设,它使你可以使用最新的JavaScript,而无需微观管理目标环境所需的语法转换(以及可选的浏览器polyfill)。这都既使工作更加方便,又能让JavaScript包更小。
因此我们可以在babel.config.js
中配置
module.exports = {
presets: [
["@babel/preset-env"]
]
}
复制代码
同时在.browserslistrc
指定目标浏览器,更多配置请查看github
babel
会根据目标浏览器判断是否需要把新的语法转成旧的语法
> 1%
last 2 versions
ie >= 11
chrome >= 49
复制代码
然后在index.js
中写一段包含ES6语法的代码,如:
const box = document.createElement('div');
box.innerText = 'Hello world!!!';
document.body.appendChild(box);
复制代码
然后使用webpack打包就能发现const
转换成了var
,实现了ES6转换ES5。
还有一个问题,为啥我配了babel.config.js就能作用于webpack打包呢?
从源码中可以发现,在@babel/core
中会读取babel.config.js
文件(如下图),通过对文件内的配置对JS代码进行转换
配置查看babel
- target:目标浏览器
- useBuiltIns: 'entry'(入口导入) | 'usage'(按需加载) | false
ES6 API转换
但是Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如Set、Map等。
因此如果在index.js
书写如下代码,其中map将不会被转换
const box = document.createElement('div');
const map = new Map();
map.set('Hi', 'Hello world!!!');
box.innerText = map.get('Hi');
document.body.appendChild(box);
复制代码
那如果需要转换,如何实现?
@babel/polyfill
作用:为不支持ES6 API语法的浏览器提供polyfill(垫片),有三种导入方式:
- 一开始直接在index.js中导入
- webpack.config.js指定
module.exports = {
//...
entry: ['@babel/polyfill', './src/index.js']
}
复制代码
- 在babel.config.js中指定
module.exports = {
presets: [
'@babel/preset-env',
{
useBuiltIns: 'usage'
}
]
}
复制代码
core.js & regenerator-runtime
从babel 7.4.0开始,babel不推荐直接使用@babel/polyfill而是直接使用core.js
(为ES6提供polyfill)和regenerator-runtime
(提供generator、async、await的 polyfill)
在core.js介绍中可以发现根据需要引入不同的文件,例如:
import "core-js/stable"; // ES和web标准的polyfill
import "core-js/es"; // 仅包含ES的polyfill
复制代码
值得注意的是core-js
中ES6支持的程度比@babel/polyfill
更大,比如core-js
就支持globalThis,但是@babel/polyfill
不支持
不过在项目中一般不直接导入,安装了这两个包后利用@babel/preset-env
相关配置就可以转换ES6 API(只安装,不导入),在babel.config.js
配置如下:
module.exports = {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
corejs: 3 // 由corejs安装的版本抉择,如果是2.x,则不需要另外写,因为默认值是2
}]
]
}
复制代码
注:最好加上@babel/plugin-transform-runtime
插件,不需要创建多个相同的helper
@babel/plugin-transform-runtime & @babel/runtime-corejs3
@babel/plugin-transform-runtime:
作用:使helper和polyfill统一引入,引入的对象与全局变量完全隔离
注:
- helper为一些通用函数,如果不使用该插件的话,那么每一个文件遇到需要转换的代码时都会生成相应的helper,比如class的_classCallCheck函数造成多次引入
- API不会直接写到全局对象上,因此与全局对象隔离,这也是区别于上述的两种方法
@babel/runtime-corejs3
作用:与core.js功能类似,都能提供polyfill,其中包依赖regenerator-runtime
,因此也能转换异步函数
babel.config.js
配置如下:
module.exports = {
presets: [
[
"@babel/preset-env"
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
corejs: 3
}
]
]
};
复制代码
tips
如果在node_modules
内有ES6需要转成ES5,可以在webpack.config.js
中这样配置:
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules(?!(\/|\\)xxx)/, // 单个包
loader: "babel-loader",
}
]
}
};
复制代码
@babel/plugin-proposal-class-properties
作用:转换class中使用static
的静态属性
因为该方案是一个提案,因此需要使用插件来转换
module.exports = {
//...
plugins: ["@babel/plugin-proposal-class-properties"]
}
复制代码
项目中看到babel-plugin-transform-class-properties
有这个包,功能和上面介绍的包功能类似,但由于该包很长时间不维护了,因此推荐使用@babel/plugin-proposal-class-properties
配置查看babel
总结
目前使用的babel包就是这些,后面如果有用到更多的babel
包将会在本文补充。