Babel入门篇
Babel 是一个广泛使用的 JavaScript 和 TypeScript 转译器(Transpiler),主要用于将高级的 JavaScript 代码转换为向下兼容的低版本 JavaScript 代码,以便在不同的运行环境中执行。Babel 在现代前端开发中扮演着至关重要的角色,特别是在处理模块化代码、新语言特性以及跨浏览器兼容性方面
小贴士
文章中涉及到的代码示例你都可以从 这里查看 ,若对你有用还望点赞支持
什么是babel?
Babel 是一种将源代码从一种形式转换为另一种形式的工具链,包括编译器、插件系统、预处理器等
主要功能
- 将最新的 JavaScript 转换为向下兼容的代码
- 将 Typescript 转换为JS代码
- 将 JSX 语法转换为普通的JavaScript代码
- 提供了对 ES 模块和 CommonJS 模块的支持,并能够将代码转换为兼容不同环境的格式
- 通过源码分析删除无用代码等代码优化
工作原理及核心包
Babel最主要的核心包为@babel/core
,它负责所有的工作,如:解析、代码编译、代码生成等等,除此之外还有很多插件
和预设
,这些都是用来定制编译
内部流程逻辑的
使用方式
Babel提供了多种使用方式,比如终端命令工具cli、babel-register、babel-core编码、集成构建工具等多种形式,下面来看下各种用法
Babel CLI
Babel CLI以终端命令的形式调用babel进行代码转译,适合快速编译验证用途
小贴士
注意babel7及以上与旧版本发生了很大变化,babel7以后以模块形式将源码划分了多种包,并且遵循@babel/
前缀规范,如:@babel/cli
,而7以前的包为babel-cli
本文全部以Babel7及以上版本为讲解依据
首先安装依赖包,这里推荐局部安装cli代替全局安装,除此之外还要安装core,它是转移代码的核心,cli只是调用命令提取参数,最终调用core
npm i @babel/cli @babel/core -D
接下来创建一个文件 index.js
:
const logger = () => console.log('logger');
在根目录下执行以下命令:
➜ npx babel index.js
const logger = () => console.log('logger');
2
会将转译结果直接输出到终端,会发现和源码一样
命令行支持的参数有很多,比如将源码编译后的内容输出到指定文件,或者将指定目录编译到指定目录
npx babel 文件 -o 目标文件
npx babel 目录/文件 -d 目标目录
# watch 模式
npx babel 文件... -w
2
3
4
你可以使用以下命令查看babel的所有命令参数:
➜ npx babel -h
Usage: babel [options] <files ...>
Options:
-f, --filename [filename] The filename to use when reading from stdin. This will be used in source-maps, errors etc.
--presets [list] A comma-separated list of preset names.
--plugins [list] A comma-separated list of plugin names.
--config-file [path] Path to a .babelrc file to use.
--env-name [name] The name of the 'env' to use when loading configs and plugins. Defaults to the value of BABEL_ENV, or else NODE_ENV, or else 'development'.
--keep-file-extension Preserve the file extensions of the input files.
-w, --watch Recompile files on changes.
-o, --out-file [out] Compile all input files into a single file.
-d, --out-dir [out] Compile an input directory of modules into an output directory.
-D, --copy-files When compiling a directory copy over non-compilable files.
--verbose Log everything. This option conflicts with --quiet
--quiet Don't log anything. This option conflicts with --verbose
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面你也发现了输入和输出的内容没有任何区别,为什么会这样?
因为你没有告诉babel如何处理源码,babel提供了presets
和plugin
来执行处理逻辑,常见的如@babel/preset-env
,这里安装它然后babel执行时指定presets:
➜ npm i @babel/preset-env
➜ npx babel --presets @babel/preset-env index.js
"use strict";
var logger = function logger() {
return console.log('logger');
};
2
3
4
5
6
7
执行后就会看到语法降级了,这就是preset的神奇之处。还有很多配置但使用cli的形式很难管理,后面我们到配置文件再讲
Babel Register
babel register主要是集成到代码中,引入它并放入程序的第一行,需要注意的是待转译的源码不能和babel register的引入放在一起,只有babel register加载完后才能编译源码
安装:
➜ npm i @babel/register -D
使用:
// index.js
require("@babel/register");
require("./app.js"); // 程序源码
2
3
最后使用node执行node index.js
,假设这里的app.js
源码为:
import { createServer } from "node:http"
createServer((req, res) => {
if (req.url === "/" && req.method === "GET") {
res.end("Home Page")
} else if (req.url === "/about" && req.method === "GET") {
res.end("About Page")
} else {
res.statusCode = 404
res.end("Not Found")
}
}).listen(3000, () =>console.log("Server running on port 3000"))
2
3
4
5
6
7
8
9
10
11
12
创建一个Nodejs服务,心细的你肯定发现源码中使用的是ES模块import/export
语法,Nodejs默认是不支持的,除非遵循一些魔法
那这里启动后发现还是会报错:
➜ node index.js
/Users/xxx/babel/guide/register/server.js:1
import { createServer } from "node:http";
^^^^^^
SyntaxError: Cannot use import statement outside a module
2
3
4
5
和前面一样没有给babel说明如何处理代码,这里就需要使用配置文件来告诉babel如何处理了,这里先跟着创建.babelrc
:
{
"presets": ["@babel/preset-env"]
}
2
3
现在再运行就会成功了
📢 一般不推荐使用babel register,原因很简单。此种方式只会在运行时先进行内存层面的转译后再运行源码,相比将源码编译成已处理的文件再执行耗时更多
Babel Core
Babel Core是babel转译核心模块,负责解析、转换和生成 JavaScript 代码,@babel/core
也提供了代码层面的API调用
const babelCore = require("@babel/core");
babelCore.transformFile(
"./index.js",
// 配置选项
{
presets: ["@babel/preset-env"],
},
(err, result) => {
console.log(result);
}
);
2
3
4
5
6
7
8
9
10
11
12
转译后是一个对象,其中code
为输出的代码
还支持多种用法主要包括:transform、parse、traverse等等
babel.transform('class App {}', cb)
babel.transformFromAst
babel.parse
babel.parseAsync
babel.traverse
// ...
2
3
4
5
6
通常情况下babel/core
的API使用场景都为了定制化需求,比如:开发一款定制化打包分析工具、源码转译、开发插件等
现入门阶段读者先不必关心这个,后面我们再看使用它如何定制化
配置文件
上面已经了解了几种转译源码的方式,由于没有告诉babel如何处理源码,因此转译后的内容和源码一模一样
要告诉babel如何处理源码最常见的方式就是配置文件,创建配置文件后babel在每次运行时都会先查找配置文件
多种配置及优先级
babel支持多种形式的配置文件和形式,以babel.config.*
或.babelrc.*
等开头,支持的后缀有.json
、.(js|mjs|cjs)
,挺五花八门的。不过但到现在最重要的区别就是项目范围
和文件范围
的配置
💥💥💥谨记以下几点:
- 以
babel.config.*
开头的为项目范围的配置,一般放在根目录下 - 以
.babelrc.*
开头的为文件范围的配置,一般放在想特殊处理某个目录下的文件里 .babelrc.*
的优先级高于babel.config.*
.babelrc.*
也可以当做项目范围配置放在根目录下
除此之外babel的配置还可以放在package.json
中:
{
"name": "package-name",
"babel": {
"presets": [ ... ],
"plugins": [ ... ],
}
}
2
3
4
5
6
7
这里举个babel.config.*
和.babelrc.*
的使用场景,假设有个Monorepo仓库如下:
babel.config.json
packages/
mod1/
package.json
.babelrc
src/index.js
mod2/
package.json
src/index.js
2
3
4
5
6
7
8
9
上面根目录下定义所有项目范围的babel配置,而mod1子模块单独设置了.babelrc
配置,优先级更高
babel配置文件的选项有很多,你可以查看babel配置选项,但对于大多数人来说基本上只会用到以下几种
{
"presets": [],
"plugins": [],
"env": {}
}
2
3
4
5
通常都是使用json
对象形式进行配置,比较特殊为函数式配置:
module.exports = function(api) {
api.cache(true);
if (process.env.NODE_ENV === "development") {
api.plugins.push(/*...*/);
}
// ...
return {};
};
2
3
4
5
6
7
8
9
10
插件plugins
plugins用来给babel提供处理源码的逻辑的,每个插件都定制了如何处理特定的代码,它是babel工具的灵魂,没有它代码转译都没多大作用
{
"plugins": ["@babel/plugin-transform-modules-commonjs"]
}
2
3
预设presets
presets预设是一系列插件的集合,那既然可以定义plugin为啥还要presets❓
一般情况下plugin都是为了实现某个单一转译功能,相对目前复杂的项目来说,如果只能配置plugin的话会变得很麻烦,用户要记住各种plugin。而presets就是来解决此类问题的,一般presets中包含了一系列常用的plugin,用户直接引入presets就可以了
{
"presets": ["@babel/preset-env"]
}
2
3
环境env
env用来区分不同环境的配置
{
"env": {
"dev": {
"presets": [
"@babel/preset-env"
]
},
"prod": {
"presets": []
}
}
}
2
3
4
5
6
7
8
9
10
11
12
常见预设
项目中babel的预设用的最多,以下是常用的presets
@babel/preset-env
@babel/preset-env 是一个最常用的智能预设,主要将最新的JS语法转换成指定目标环境的语法
{
"presets": [
[
"@babel/preset-env",
{ /* options */ }
]
]
}
2
3
4
5
6
7
8
有几个重要的选项:
targets
:指定最低的浏览器支持情况,比如:
"targets": "> 0.25%, not dead"
"targets": {
"chrome": "58",
"ie": "11"
}
2
3
4
5
推荐使用.browserslist
文件单独来维护浏览器支持情况
useBuiltIns
:此选项告诉babel如何对降级后的语法添加垫片支持,虽然babel可以将最新的js特性向后兼容为旧语法,但一些API在旧语法中没有实现,这时候就需要自行实现逻辑进行垫片
useBuiltIns的值有以下3种:
false
:不进行任何垫片处理entry
:在程序入口引入corejs
,babel会根据目标环境自动删减留下最终的部分垫片usage
:不需要在程序中引入corejs
,babel会根据目标环境自动引入环境需要的垫片
useBuiltIns
通常要配合corejs
选项使用
corejs
:一些polyfill的实现集合
在旧版本的babel工具链中有个@babel/polyfill库,它是由corejs和regenetor-runtime组成
babel7以后就废弃了这个库,并将其拆分开了。用户需要自行下载corejs
,当然如果你下载了@bable/polyfill
就不用再下载corejs了
corejs目前有二大版本:2、3,2已经不再维护新特性了,推荐使用3
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": { "version": "3.40", "proposals": true }
// "corejs": "3.4.0"
}
]
],
}
2
3
4
5
6
7
8
9
10
11
12
默认情况下babel只会引入es的标准特性core-js/stable
,而一些es提案特性则不会自动添加,如果要使用可以添加"proposals": true
开启
🌰 下面举个例子理解下
假如我们的源码为index.js
:
const p = new Promise();
export const logger = () => console.log("logger");
async function run() {
const helper = await import("xxx");
}
2
3
4
5
然后创建babel配置文件.babelrc
:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "ie 11", // 支持ie11
"modules": false, // 不转换为其他模块,最终还是es语法模块
"useBuiltIns": "usage", // 根据环境自动添加polyfill
"corejs": { "version": "3.40", "proposals": true }
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
然后编译后的代码如下:
// 辅助函数
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _regeneratorRuntime() { /* 省略... */ }
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
// 垫片
import "core-js/modules/es.symbol.js";
import "core-js/modules/es.symbol.description.js";
import "core-js/modules/es.symbol.async-iterator.js";
import "core-js/modules/es.symbol.iterator.js";
import "core-js/modules/es.symbol.to-string-tag.js";
import "core-js/modules/es.error.cause.js";
import "core-js/modules/es.array.iterator.js";
import "core-js/modules/es.array.push.js";
import "core-js/modules/es.array.slice.js";
import "core-js/modules/es.function.name.js";
import "core-js/modules/es.iterator.constructor.js";
import "core-js/modules/es.iterator.for-each.js";
import "core-js/modules/es.json.to-string-tag.js";
import "core-js/modules/es.math.to-string-tag.js";
import "core-js/modules/es.object.get-prototype-of.js";
import "core-js/modules/es.object.to-string.js";
import "core-js/modules/es.promise.js";
import "core-js/modules/es.string.iterator.js";
import "core-js/modules/web.dom-collections.for-each.js";
import "core-js/modules/web.dom-collections.iterator.js";
var p = new Promise();
export var logger = function logger() {
return console.log("logger");
};
function run() {
return _run.apply(this, arguments);
}
function _run() {
_run = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
var helper;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return import("xxx");
case 2:
helper = _context.sent;
case 3:
case "end":
return _context.stop();
}
}, _callee);
}));
return _run.apply(this, arguments);
}
// import "core-js"
export function App() {}
export var app = new App();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
可以看到最终的代码生成了一些babel的辅助代码,还根据targets
环境从corejs中引入的一些必需的垫片
@babel/preset-react
@babel/preset-react 主要来处理react和jsx语法的,它由@babel/plugin-systax-jsx
、@babel/plugin-transform-react-jsx
等几个插件组成,比较简单就不介绍了
这里有个app.jsx
export default function App() {
return (
<div>
<h1>Hello World</h1>
<div/>
);
}
2
3
4
5
6
7
创建配置后编译文件:
export default function App() {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", null, "Hello World"));
}
2
3
如果把div
改成<>
后:
export default function App() {
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Hello World"));
}
2
3
@babel/preset-typescript
@babel/preset-typescript 用来解析转换Typescript语法的,比较简单不多做介绍
@babel/preset-flow
@babel/preset-flow 用来解析 flow 类型语法的,类似于Typescript,比较简单也不推荐使用不多做介绍
/* @flow */
function logger(msg: string) {
console.log(msg);
}
2
3
4
Babel Polyfill
Babel Polyfill 是一个垫片集合,是最新版本babel和corejs使用方式的前身,它不支持treeshaking,已经不推荐使用了,请以@babel/preset-env
使用为准‼️
@babel/polyfill
使用非常简单,直接在项目口引入即可:
import "@babel/polyfill";
Babel Runtime
Babel Runtime 是babel编译后一些运行时辅助函数的集合。从上面的编译读者应该知道,最终代码文件里有很多辅助函数,如果在大项目中最终编译的文件甚至更多,那么每个文件中都会有babel的辅助函数,会变得越冗余
Runtime就是专门将这些辅助函数提取到一个文件中,提高复用避免变量污染的。在开发时一般结合 @babel/plugin-transform-runtime 插件告诉babel将辅助函数提取为Runtime里的代码
{
"plugins": [
["@babel/plugin-transform-runtime", {
"helpers": true
}]
]
}
2
3
4
5
6
7
现在我们再编译后看下生成的代码:
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
import _regeneratorRuntime from "@babel/runtime/regenerator";
var p = new Promise();
export var logger = function logger() {
return console.log("logger");
};
// ...
2
3
4
5
6
7
8
可以看到辅助函数都从@babel/runtime
中引入了,这在大项目中一定程度上减小文件体积,提高复用率
Runtime也支持corejs选项:
{
"plugins": [
["@babel/plugin-transform-runtime", {
"helpers": true,
"corejs": "3", // 3 | 2
"regenerator": true
}]
]
}
2
3
4
5
6
7
8
9
这里使用了corejs
选项后就会从Runtime
中引入corejs相关垫片
import _Promise from "@babel/runtime-corejs3/core-js-stable/promise";
不同版本的corejs有对应的runtime-core
库,可以这样安装:
# corejs false
npm install -save @babel/runtime
# corejs 2
npm install --save @babel/runtime-corejs2
# corejs 3
npm install --save @babel/runtime-corejs3
2
3
4
5
6
划重点‼️ 需要注意的是Runtime
和@babel/preset-env
的corejs选项只能出现一个,否则会生成多余的垫片
一般推荐在开发项目中推荐preset-env
使用corejs
和useBuiltIns
,Runtime
用来抽取辅助函数即可
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": { "version": "3.40", "proposals": true }
}
]
],
"plugins": [
["@babel/plugin-transform-runtime", {
"helpers": true
}]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这里要知道preset-env
生成的辅助函数直接修改变量可能会污染全局变量,而Runtime
不会,因此在开发库文件时需要Runtime
使用corejs
构建工具集成
工作中通常都会在项目构建工具中集成babel
webpack
webpack是大型项目使用率最高的构建工具,webpack集成babel 也很简单,这里不深入webpack配置
安装loader:
npm i babel-loader -D
安装babel依赖:
npm i @babel/core @babel/preset-env
在webpack.config.js
中配置:
module: {
rules: [
{
test: /\.(?:js|mjs|cjs)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
// babel options
options: {
targets: "defaults",
presets: [["@babel/preset-env"]],
},
},
},
],
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
也可以创建单独的babel配置文件,webpack会将他们合并:
// .babelrc
{
"presets": [
["@babel/preset-env", {
"targets": "IE 11"
}]
]
}
2
3
4
5
6
7
8
rollup
rollup使用babel 也很简单,直接安装 @rollup/plugin-babel
插件
import resolve from "@rollup/plugin-node-resolve";
import babel from "@rollup/plugin-babel";
/**@type {import('rollup').RollupOptions} */
const config = {
input: "./src/index.js",
output: {
// file: "./dist/bundle.js",
dir: "./dist",
format: "cjs",
},
plugins: [resolve(), babel({ babelHelpers: "bundled" })],
};
export default config;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后在src下而不是根目录下创建.babelrc.json
,配置和babel一样就可以了
执行编译:
npx babel -c rollup.config.js