Bable原理
为什么需要学习Bable?
作为前端开发者,虽然经常能听到Bable,但是我们在实际开发中,很少会直接接触Bable。那我们学习Bable有什么用呢?
因为Bable主要作用于代码的编写到线上的转变。这个过程是不可或缺的。
比如说编写ES6、TypeScript、开发React项目时使用到的JSX语法等,这些都是需要bable帮助我们进行代码转化。
Bable底层原理
Bable的本质,可以看成是一种编译器。Bable的将我们的源代码转化为浏览器可以识别的代码。而编译器则是将一种语言转化成目标语言。它们的作用十分相似,而且其转化过程也非常相似。
Bable是以微内核架构的(与postcss类似),虽然下载了Bable,但是Bable本身并不会做任何处理。Bable依赖于下载需要的plugins插件去完成转化。
Bable的执行过程:
bable在Webpack中使用
安装:
@bable/core是bable的核心,必须安装才能使用bable
npm install bable-loader @bable/core --dev--save
前面提到babel想要进行代码转化是依赖于一个个的plugins的,所以这里需要手动下载每一个plugins然后引用
安装要用到的plugins
- @babel/plugin-transform-arrow-functions(箭头函数转化)
- @babel/plugin-transform-block-scoping(块级声明const、let转化)
npm install @babel/plugin-transform-arrow-functions @babel/plugin-transform-block-scoping --save-dev
配置:
//webpack.config.js
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
plugins: [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping"
]
}
}
}
Babel的两种配置文件
在前面我们学习了babel在webpack里是怎么进行配置的。其中,babel-loader是需要另外下载很多插件,然后在options里进行配置的。
其实我们还可以把这里放在options里的配置抽取到配置文件里。
两种配置文件:babel.config.js和bablerc.js。(我们在项目中选择其中一种就可以了,推荐使用babel.config.js这种方式。)
推荐原因:
- 在babel7之后都是使用的babel.config.js
- 现在很多项目都是采用了分包管理的方式(比如Element plus、umi),而babel.config.js的配置是可以直接作用到所有的子包。但是bablerc.js要想配置到所有子包上,就会非常的麻烦。
preset-env
根据上边配置就可以实现用bable把箭头函数以及块级声明(const、let)这些代码进行转化。
但是其实这样是不合理的。试想下,我们也不清楚我们的项目需要转化哪些方面,也就不知道需要下载以及配置哪些plugins。
判断要使用bable转化哪些方面,是得根据我们项目所需要适配的浏览器进行配置所需的plugins,而不是现在手动下载配置plugins(低效且不合理)。
那有什么好方法解决吗?
使用@babel/preset-env这个插件:这是一个预设的插件集合,包含了一组相关的插件
安装:
npm install @bable/preset-env --save-dev
使用:
//webpack.config.js
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
"@babel/preset-env"
}
}
}
@babel/preset-env做什么事?
它会根据browserslistrc文件共享出来的我们的项目需要适配哪些版本的浏览器信息,然后自动根据这些浏览器预设所需要适配的plugins,这样就可以完美实现项目所需要的代码转化了。
(browserslistrc等相关浏览器适配知识,可以看webpack解决浏览器兼容性问题这篇笔记回顾)
如果我们想了解更多的babel相关的plugins,可以到babel官方网站查阅https://babeljs.io/
Polyfill
polyfill什么是?
Polyfill是一个补丁,可以帮助我们更好的使用JavaScript。
(我们在使用Js时,不确定浏览器支不支持我们使用的js语法,比如ES6。而Polyfill,就会帮我们打上对应的补丁。使我们的js代码被浏览器正常解析)
那polyfill的作用是否跟preset-env冲突了呢?
@babel/preset-env只是提供了语法转换的规则(例如箭头函数、解构赋值、class),但是它并不能弥补浏览器缺失的一些新的功能,如一些内置的方法和对象,(如Promise,Array.from等),此时就需要polyfill来做js的补丁,弥补低版本浏览器缺失的这些新功能。
所以它们并不会冲突,polyfill只会对preset-env仍旧无法让浏览器兼容我们的代码的情况下,去打需要的补丁。它们是相辅相成的。
(参考出处:https://zhuanlan.zhihu.com/p/138108118)
安装:
在babel7之前:使用@bable/polyfill(这个包已经被弃用,不推荐)
npm install @bable/polyfill --save
在babel7之后:core-js 或 regenerator-runtime (官方推荐使用这两个包)
(在很多第三方库,都是使用 regenerator-runtime这个包。
因为polyfill的垫片是在全局变量上挂载目标浏览器缺失的功能,就有可能会造成全局污染。而 regenerator-runtime不会污染全局,它会给需要转化的方法另起一个名字)
npm install core-js regenerator-runtime --save
使用:(使用@babel/preset-env的useBuiltIns属性配置polyfill,Polyfill有三种模式可以选择。)
//webpack.config.js
{
test: /\.jsx?$/,
//排除node_modules,因为我们安装各种包时,可能这些包已经配置了polyfill,这会发生冲突
exclude: /node_modules/,
use: {
loader: "babel-loader",
}
}
//bable.config.js
module.exports = {
presets: [
["@babel/preset-env", {
//useBuiltIns的三个属性,选择使用polyfill的类型
// false: 不用任何的polyfill相关的代码
// usage: 项目中涉及到需要哪些内容需要polyfill,就添加哪些(polyfill包含很多东西,我们在项目中只用到一部分)
// entry: 手动在入口文件中导入 core-js/regenerator-runtime, 根据browserslistrc配置的目标浏览器引入所需要的polyfill
useBuiltIns: "usage",
// 指定corejs的版本号为3,默认为2,版本太低可能会报错
corejs: 3
}],
]
}
注意点:如果是useBuiltIns:”entry”,那需要在入口js文件添加用到的两个包的导入。
//js入口文件,比如main.js
import "core-js/stable";
//或
//import "regenerator-runtime/runtime";
bable转化react:Preset-react
我们知道React是使用jsx语法的,jsx语法需要经过转化才能给浏览器解析。那babel的哪个plugins可以帮助我们完成呢?
对react jsx代码进行处理需要如下的插件:
- @babel/plugin-syntax-jsx
- @babel/plugin-transform-react-jsx
- @babel/plugin-transform-react-display-nam
总共需要下载并配置这三个plugins。(但是实际上的项目开发中,并不会一个个地去安装这些plugins,直接使用preset来配置就可以了。)
安装:
npm install @babel/preset-react -D
使用:
//bable.config.js
module.exports = {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
corejs: 3
}],
["@babel/preset-react"] //配置preset-react
],
}
bable转化TypeScript
TypeScript需要转化为JavaScript才能被浏览器解析。
其实在我们安装TypeScript到全局时,也自动帮我们安装了TypeScript Compiler(ts编译器)。我们写的ts代码,就可以使用这个tsc来手动编译。
但是真实开发中我们总不可能每个ts文件都进行手动编译,所以就可以让我们的webpack帮我们实现。
两种方式:ts-loader 或 preset-typescript
ts-loader的使用
安装:npm install ts-loader -D
ts-loader是依赖于TypeScript包的,本质上是使用了TypeScript Compiler
(要是此时没有安装TypeScript,其实也不需要专门安装的。这涉及node的知识:我们安装包时,如果这个包对其它包有依赖的话,node会帮我们自动安装其它包的)。
配置:
//webpack.config.js
{
test: /\.ts$/,
exclude: /node_modules/,
use: "ts-loader"
}
如果这时候我们项目的根目录下没有tsconfig.json这个配置文件,我们此时打包就会报错。
因为ts-loader本质还是去用tsc,所以需要用到tsconfig.json里的配置。
我们需要生成这个文件 。这个文件我们一般不会手动创建,使用tsc帮我们创建即可。
//命令行输入指令,自动生成tsconfig.json
tsc --init
这样我们就实现了ts的转化。但是其实这样还是存在问题的。
这里我们是用ts-loader,设想如果我们的项目还用到了Polyfill去处理代码,部分代码肯定是既要用到polyfill又要用到ts-loader的,那这种情况代码还能正确地被转化吗?
答案是不行的。
那如果babel也支持对TypeScript进行编译,那就可以实现TypeScript转化+polyfill了。
preset-typescript的使用
安装:
npm install @babel/preset-typescript -D
使用:
Webpack.config.js的配置:
//webpack.config.js
{
test: /\.ts$/,
exclude: /node_modules/,
// babel-loader不用依赖于tsc就能编译
use: "babel-loader"
}
Babel.config.js的配置:
//babel.config.js
module.exports = {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
corejs: 3
}],
["@babel/preset-typescript"]
],
}
Ts-loader vs babel?
它们的区别:
ts-loader(TypeScript Compiler)
- 本质上是使用了TypeScript Compiler;
- 无法与Babel的polyfill共同作用;
- 会对类型错误进行检测;
使用babel-loader(Babel)
- 无需借助TypeScript Compiler,Babel是非常强大的,无需依赖于tsc就可以对我们的ts进行编译;
- 可以实现TypeScript转化+polyfill;
- babel-loader在编译的过程中,不会对类型错误进行检测;
补充(关于类型错误的检测):
当我们使用了ts-loader,如果我们的代码没有使用ts规定的类型,就会在编译的时候直接报错,运行失败。
而使用了babel-loader,则是可以正常运行起来的,babel-loader并不会对类型错误进行检测。(在敲代码时,也会有错误提示。但是这个是因为vscode对TypeScript的支持。如果是其它编辑器,比如webstorm,默认不支持的,需要进行额外的配置。)
最佳实践
这样看来,它们各自都有一些不完美的地方,那我们在真实开发中应该如何选择呢?
我们可以看到TypeScript官方文档的说明:
https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html
官方的说法是:
如果我们的代码编译前后差别不大(就是并没有使用promise等特性),推荐使用tsc。
如果我们还需要用到ts之外的其它转化,官方推荐使用babel进行转化,并且还使用tsc进行类型检查。
看完官方的解释,现在很多项目都是会用到诸如promise等新特性,所以polyfill是不可或缺的。我们可以考虑第二种解决方案。
使用babel进行转化,并且还使用tsc进行类型检查这要怎么做到呢?
//package.json
"scripts": {
"build": "webpack --config webpack.config.js",
//这样配置的话,命令行输入npm run type-check就可以使用tsc进行类型检查
//--noEmit:不输出额外的文件,tsc编译默认输出对应js文件
"type-check": "tsc --noEmit",
//--watch:对代码变化进行监听,这样我们就无需手动输入npm run type-check
"type-check-watch": "tsc --noEmit --watch"
},
参考出处:
https://babeljs.io/
https://zhuanlan.zhihu.com/p/138108118
https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html