这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
在开发中我们很少直接去接触babel,但babel对于前端开发来说是不可缺少的一部分。
尤其是在我们想要使用ES6+的语法,想要使用TypeScript的时候,我们都离不开Babel。
babel简介
Babel是一个工具链,主要用于旧浏览器或者缓解中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript
包括:语法转换、源代码转换、Polyfill实现目标缓解缺少的功能等
Babel使用的是微内核
架构,即babel只提供核心的架构@babel/core
@babel/core
和postcss一样,能做的功能是有限的,或者说基本没有什么功能
如果我们需要使用babel的转换功能,那么我们就需要为babel添加对应的插件或预设
在命令行使用
# @babel/core:babel的核心代码,必须安装
# @babel/cli:可以让我们在命令行使用babel
npm install @babel/cli @babel/core
# 使用babel来处理我们的源代码
# src:是源文件的目录
# --out-dir:指定要输出的文件夹dist
# npx babel <源文件或文件夹路径> --out-dir <输出文件夹>
npx babel src --out-dir dist
此时,babel并没有给我们转换任何的代码,即转换前后代码是基本一致的
所以如果我们需要转换,我们可以为babel配置对应的plugins
# @babel/plugin-transform-arrow-functions ==> 转换箭头函数
# @babel/plugin-transform-block-scoping ===> 将const 和 let 转换为 var
npm install @babel/plugin-transform-arrow-functions @babel/plugin-transform-block-scoping -D
# 转换 使用多个插件用逗号分隔(前后不要有空格)
npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions
但是如果要转换的内容过多,一个个设置和下载插件是比较麻烦的,我们可以使用预设(preset)
所谓预设,其实就是插件的集合包
# 安装基本预设
npm install @babel/preset-env -D
# 编译
npx babel src --out-dir dist --presets=@babel/preset-env
babel的底层原理
我们可以将babel看成就是一个编译器
而编译器的核心工作就是从一种源代码(原生语言)转换成另一种源代码(目标语言)
Babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另外一源代码
Babel的工作流程:
- 解析阶段(Parsing) ===> 将源代码经过解析,生成AST(抽象语法树)
- 转换阶段(Transformation) ===> 遍历阶段,将对应的节点应用对应的插件,进行代码的转换,形成新的AST
- 生成阶段(Code Generation) ===> 将新的AST转回我们平时常见的代码,此时新生成的代码就是已经被转换后的代码
babel-loader
# 安装
npm i @babel/core babel-loader -D
// 为babel配置plugins
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-block-scoping'
]
}
}
}
]
}
// 为babel配置presets
// babel会根据browserslist查询到的需要适配的浏览器所支持的语法, 自动使用预设包中所需要使用的plugin
// 常见的预设有@bebel/preset-env, @babel/preset-typescript, @babel/preset-react
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env'
]
}
}
}
]
}
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
// 可以给预设设置一些参数,使用数组表示
// 参数1是预设包,参数2是传入预设包的参数
// targets就是需要兼容的目标浏览器 --- 配置的targets属性会覆盖browserslist中的配置
// 但是我们一般不推荐在这里进行设置,而是在browserslist中进行设置
// 因为在browserslist中设置的配置是可以在多个工具之间进行共享的
presets: [
['@babel/preset-env', {
targets: 'chrome 88'
}]
]
}
}
}
]
}
配置文件
可以将babel的配置信息放到一个独立的文件中,babel给我们提供了两种配置文件的编写
- babel.config.json(或者.js,.cjs,.mjs)文件 --- 可以直接作用于Monorepos项目的子包,更加推荐 --- 使用与Bebel7版本及以上
- .babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件 --- 早期使用较多的配置方式,但是对于配置Monorepos项目是比较麻烦的
babel.config.js --- 位于项目根目录下
module.exports = {
presets: [
'@babel/preset-env'
]
}
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader'
}
}
]
}
babel的配置文件不仅仅可以导出一个对象,同样可以导出一个函数, 该函数可以接收一个参数, 这个参数就是babel在编译时候使用的api对象
module.exports = api => {
// 开启babel对于配置文件的缓存操作
// babel会自动在每次打包的时候判断配置文件是否发生改变,并尽可能的使用缓存来提升编译时babel的打包性能
api.cache(true)
const presets = [
'@babel/preset-env'
]
const plugins = [
['@babel/plugin-transform-runtime', {
corejs: {
version: 3,
proposals: true
}
}]
]
return {
presets,
plugins
}
}
polyfill
babel默认情况下只能对新语法特性进行转换,
而一些新的API(例如: Promise, Generator, Symbol等以及实例方法例如Array.prototype.includes等)是没有做实现的,
为了可以在没有实现这些新特性API的浏览器中可以使用这些新特性API,就需要使用polyfill
所以polyfill更类似于一种补丁,可以帮助我们更好的使用JavaScript
# 安装
# 注意: 这2个包是开发依赖 不是生产依赖 --- 因为他们的作用是在生产环境代码 为对应的代码加上补丁
npm install core-js regenerator-runtime --save
module: {
rules: [
{
test: /\.js$/,
// 包中的代码可能已经打上了polyfill,如果此时再次对包中的代码进行打补丁操作,可能会出现代码冲突,所以需要排除
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
useBuiltIns
false
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{ // useBuiltIns 可以有3种选项:false|usage|entry --- 这个属性就是告诉babel如何打补丁的
useBuiltIns: false // false 不打补丁
}
]
]
}
usage
// 会根据源代码中出现的语言特性,自动检测所需要的polyfill
// 这样可以确保最终包里的polyfill数量的最小化,打包的包相对会小一些
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3 // corejs的默认版本是2,但是一般使用的是3,所以需要手动指定
// 可以设置的版本是major和minor 例如 corejs: 3.16 (corejs: 3.16.0 => error)
}
]
]
}
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3, // 设置使用corejs的版本号
propsals: true // 设置corejs在进行转换的时候对提议阶段的特性进行支持
}
}
]
]
}
entry
// 只要是目标浏览器需要支持的新特性,我就引入对应的polyfill,无论我的代码中有没有实际使用到
// 所以使用这种方式打包后的代码,体积相对也会比较大
module.exports = {
presets: [
[
'@babel/preset-env',
{
// 默认这么写是不会生效的,
// 需要在入口文件中添加 `import 'core-js/stable'; import 'regenerator-runtime/runtime'
useBuiltIns: 'entry'
}
]
]
}
// index.js
import 'core-js/stable
import 'regenerator-runtime/runtime'
Plugin-transform-runtime
我们使用的polyfill,默认情况是添加的所有特性都是全局
如果我们正在编写一个工具库,这个工具库需要使用polyfil,别人在使用我们工具时,工具库通过polyfill添加的特性,可能会污染它们的代码,此时可以使用Plugin-transform-runtime
, 它所实现的polyfill功能是局部的
# 安装
npm install @babel/plugin-transform-runtime -D
注意:因为我们使用了corejs3,所以我们需要安装对应的库
# 注意: 这个包是开发依赖
npm i @babel/runtime-corejs3
// @babel/plugin-transform-runtime 和 useBuiltIns不要混用
module.exports = {
plugins: [
// @babel/plugin-transform-runtime 所引入的polyfills是按照usage的方式进行引入的
['@babel/plugin-transform-runtime', {
corejs: 3
}]
]
}
编译JSX
npm i react react-dom
npm install @babel/preset-react -D
module.exports = {
presets: [
'@babel/preset-react'
]
}
编译TS
# ts-loader在编译的时候是依赖于tsc的,所以typescript会作为ts-loader的依赖包一起被下载下来
npm i ts-loader -D
typescript如何进行编译,是需要依赖于typescript的依赖配置文件的,所以需要先生成typescript所对应的依赖配置文件
tsc --init
// 配置
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader'
}
]
}
ts-loader本质上还是使用tsc
来进行ts代码的编译,所以ts-loder会将typescript作为依赖项一起下载
但是在实际开发中,我们还有另一种编译ts的方式,也就是babel-loader
的preset-typescript
babel-loader
的本身就可以使用babel对typescript进行编译,所以此时不需要安装typescript
# 安装
# 虽然@babel/preset-typescrip中只有一个插件@babel/tranform-typescript
# 但是不排除以后在预设包中扩展其它插件的可能性,所以推荐使用预设包
npm i @babel/preset-typescript -D
webpack.config.js
module: {
rules: [
{
test: /\.ts$/,
// 为了避免babel多次转换出现错误,还是推荐不对node_modules下的文件进行打包
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
babel.config.js
module.exports = {
presets: [
'@babel/preset-typescript'
]
}
ts-loader vs preset-typescript
名称 | 功能 |
---|---|
ts-loader | 1. ts代码转换为js代码时,如果ts代码书写错误,在编译的时候会提示对应的错误信息 2. 不会为编译后的代码添加相应的polyfill,默认不会进行ES6代码的转换(需要在tsconfig.json中进行配置) |
babel-loader | 1. 可以直接编译TypeScript,并且可以实现polyfill的功能 2. 在编译的过程中,不会对类型错误进行检测 也就是说如果出现错误,babel-loader并不会将错误输出在控制台 |
所以我们在实际开发中,可以将两者结合在一起使用
webpack.config.js
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
// 也就是使用ts-loader对ts的编译转换能力
// 使用babel的代码转换功能,但是不使用它对于TS代码的转换功能
use: [
'babel-loader',
'ts-loader'
]
}
]
}
babel.config.js
module.exports = {
presets: [
'@babel/preset-env'
]
}