Babel 是一款将未来的 JavaScript 语法编译成过去语法的 Node.js 工具。本文从 2019 年 11 月的 master 分支源码入手,介绍 Babel 在解决这类问题时是如何划分模块。
其中 babel-loader 隶属于 webpack,不在 Babel 主仓库。
常见的解析器有 acorn、 @babel/parser (babylon)、flow、traceur、typescript、uglify-js 等,各自的 AST 语法树大致相同。
let c = 0;
while (a < 10) {
const b = a % 2;
if (b == 0) {
c++;
}
}
console.log(c);
上面的这段代码通过 @babel/parser 解析后得到的 AST 语法树如下:
大部分模块代码量在百行左右,其中 StatementParser、ExpressionParser 和 Tokenizer 有较多复杂逻辑。
提供遍历 AST 语法树的能力,如:
traverse(ast, {
FunctionDeclaration: function(path) {
path.node.id.name = "x";
}
});
traverse(ast, {
enter(path) {
if (path.isIdentifier({ name: "n" })) {
path.node.name = "x";
}
}
});
path
对象上有下面的属性和方法:
将 AST 转为代码文本。示例用法:
import { parse } from '@babel/parser';
import generate from '@babel/generator';
const ast = parse('class Example {}');
generate(ast); // => { code: 'class Example {}' }
可以生成 source map。
import { parse } from '@babel/parser';
import generate from '@babel/generator';
const code = 'class Example {}';
const ast = parse(code);
const output = generate(ast, { sourceMaps: true, sourceFileName: code }); // => { code: 'class Example {}', rawMappings: ... }
// or
const output = generate(ast, { sourceMaps: true, sourceFileName: 'source.js' }, code); // => { code: 'class Example {}', rawMappings: ... }
还可以合并多个文件,同时生成 source map。
import { parse } from '@babel/parser';
import generate from '@babel/generator';
const a = 'var a = 1;';
const b = 'var b = 2;';
const astA = parse(a, { sourceFilename: 'a.js' });
const astB = parse(b, { sourceFilename: 'b.js' });
const ast = {
type: 'Program',
body: [...astA.program.body, ...astB.program.body]
};
const { code, map } = generate(ast, { sourceMaps: true }, {
'a.js': a,
'b.js': b
});
主要提供 transform 和 parse 相关的 API。
transform 的流程主要是 parse -> traverse -> generate。
parse 主要提供对 @babel/parser 的封装。
通过插件开关打卡语法解析能力。 @babel/parser 中判断了 plugin 开关,实现了这些语法解析能力。如 @babel/plugin-syntax-jsx:
parserOpts.plugins.push("jsx");
实现语法的转换。如 @babel/plugin-transform-exponentiation-operator:
export default {
name: "transform-exponentiation-operator",
visitor: build({
operator: "**",
build(left, right) {
return t.callExpression(
t.memberExpression(t.identifier("Math"), t.identifier("pow")),
[left, right],
);
},
}),
}
支持草案级别的语法转换。如 @babel/plugin-proposal-numeric-separator:
export default {
name: "proposal-numeric-separator",
inherits: syntaxNumericSeparator,
visitor: {
CallExpression: replaceNumberArg,
NewExpression: replaceNumberArg,
NumericLiteral({ node }) {
const { extra } = node;
if (extra && /_/.test(extra.raw)) {
extra.raw = extra.raw.replace(/_/g, "");
}
},
},
}
提供各类组合好的 plugins、syntax 和 helpers。
常用的是 @babel/preset-env,结合 browserslist 设置代码的兼容性。
从 Babel 7.4.0 起废弃,推荐使用 core-js 和 regenerator-runtime。其中 core-js 提供了 ECMAScript 的所有兼容代码,regenerator-runtime 提供了 async、generator 等函数的执行环境。
定义了 Babel 运行环境的辅助函数。如在 class 模块前插入 classCallCheck 的 helper。
plugin 内的函数调用方式:
export default {
visitor: {
ClassExpression(path) {
this.addHelper("classCallCheck");
// ...
}
};
生成的代码中将包含 classCallCheck:
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
提供 Babel 的运行环境,包括 regenerator-runtime。运行环境会提供一些辅助代码,如:
使用 @babel/helpers 的情况:
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
使用 @babel/plugin-transform-runtime 可以把这些代码复用起来:
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");
var Circle = function Circle() {
_classCallCheck(this, Circle);
};
@babel/runtime 源码中没有内容,依赖构建脚本将 @babel/helpers 中的代码复制过去。
除了这个运行环境之外,Babel 还提供了 @babel/runtime-corejs2 和 @babel/runtime-corejs3,分别是基于 core-js v2 和 v3 提供的运行环境。可以在 @babel/plugin-transform-runtime 的 corejs 参数中设置使用的运行环境。
提供基础的类型值,创建类型的函数,便于 @babel/plugin、 @babel/parser 等使用。
const binaryExpression = t.binaryExpression('+', t.numericLiteral(1), t.numericLiteral(2))
打印出错位置。示例代码:
import { codeFrameColumns } from '@babel/code-frame';
const rawLines = `class Foo {
constructor()
}`;
const location = { start: { line: 2, column: 16 } };
codeFrameColumns(rawLines, location);
输出的结果是:
1 | class Foo {
> 2 | constructor()
| ^
3 | }
面向控制台输出有颜色的代码片段。
import highlight from "@babel/highlight";
const code = `class Foo {
constructor()
}`;
highlight(code); // => "\u001b[36mclass\u001b[39m \u001b[33mFoo\u001b[39m {\n constructor()\n}"
展示在控制台上:
模板引擎。
import template from "@babel/template";
import generate from "@babel/generator";
import * as t from "@babel/types";
const buildRequire = template(`
var %%importName%% = require(%%source%%);
`);
const ast = buildRequire({
importName: t.identifier("myModule"),
source: t.stringLiteral("my-module"),
});
generate(ast).code // => var myModule = require('my-module');
Babel 的辅助函数,包含常用操作、测试函数等,内容比较庞杂。
在命令行编译。
babel script.js # 输出编译的结果
在浏览器编译。如:Babel 官网等会用到。
<div id="input"></div>
<div id="output"></div>
<button id="transform">转换</button>
<!-- 加载 @babel/standalone -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script>
document.getElementById('transform').addEventListener('click', function() {
const input = document.getElementById('input').value;
const output = Babel.transform(input, { presets: ['es2015'] }).code;
document.getElementById('output').value = output;
});
</script>
@babel/standalone 也会自动编译和执行 <script type="text/babel"></script>
和 <script type="text/jsx"></script>
中的代码。
<div id="output"></div>
<!-- 加载 @babel/standalone -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- ES2015 代码会被编译执行 -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
提供在命令行执行高级语法的环境。 @babel/cli 只转换,不执行,@babel/node 会执行。不适合生产环境使用。
babel-node -e script.js # script.js 里面可以使用高级语法
提供在 Node.js 运行环境内编译和执行高级语法。不适合生产环境使用。
require("@babel/register")();
require("./script.js"); // script.js 里面可以使用高级语法
Array.from
// input
Array.from([1, 2, 3])
// output
var _array_from_ = require('@babel/runtime-corejs3/core-js-stable/array/from');
_array_from_([1, 2, 3]);
// input
<div className="text">{content}</div>
// output
React.createElement('div', { className: 'text' }, content);
class
// input
class Example extends Component { constructor(props) { super(props) } }
// output
var _inherits_ = require('@babel/runtime-corejs3/helpers/interits');
var _class_call_check_ = require('@babel/runtime-corejs3/helpers/classCallCheck');
var _possible_constructor_return_ = require('@babel/runtime-corejs3/helpers/possibleConstructorReturn');
var _get_prototype_of_ = require('@babel/runtime-corejs3/helpers/getPrototypeOf');
var _create_class_ = require('@babel/runtime-corejs3/helpers/createClass');
var Example = function (_Component) {
_inherits_(Example, _Component);
function Example(props) {
_class_call_check_(this, Example);
return _possible_constructor_return_(this, _get_prototype_of_(Example).call(this, props));
}
_create_class_(Example, []);
return Example;
}(Component);
1
lpbname777 2020-01-06 09:39:28 +08:00
|
2
OxO 2020-01-06 10:59:08 +08:00
支持,收藏了!
|