AST是个什么玩意儿.md

1. 语法转换:将新版本的 JavaScript 语法转换为旧版本的语法

1
pnpm i --save-dev @babel/core @babel/cli @babel/preset-env

test.js 测试用例

1
2
3
4
5
6
7
8
9
10
//语法
const a = (params = 1) => 1 + params;
const b = [1, 2]
const c = [...b, 3, 4, 5]
class DaWei {
}
new DaWei()
//API
const x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter((x) => x % 2 === 0)
const y = Object.assign({}, { name: 1 })

index.js 核心转换代码

第一个参数是代码

img

img

img

运行代码 node index.js

1
2
3
4
5
6
7
8
9
10
// 记得设置package.json的type为module
// 这些包很重要!!!
import Babel from '@babel/core'
import presetEnv from '@babel/preset-env' // es6-es5的核心包
import fs from 'node:fs'
const code = fs.readFileSync('./test.js', 'utf8')
const result = Babel.transform(code, {
presets: [presetEnv]
})
console.log(result.code)

看一下编译后的内容

编译之后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"use strict";

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 _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
var a = function a() {
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
return 1 + params;
};
var b = [1, 2, 3];
var c = [].concat(b, [4, 5]);
var Babel = /*#__PURE__*/_createClass(function Babel() {
_classCallCheck(this, Babel);
});
new Babel();

发现有些代码转换了,有些没有。

接下来要如何转换呢?主要通过 core.js

core.js 的实现原理:比如filter支持吗?

判断原型里面有没有filter? 没有的话,实现下Array的filter方法呢?

img

如何支持新特性例如 Object.assign Array.prototype.find 等

1
npm i core-js -D

index.js 增强编译

1
2
3
4
5
6
7
8
9
10
11
12
import Babel from '@babel/core'
import presetEnv from '@babel/preset-env'
import fs from 'node:fs'
const file = fs.readFileSync('./test.js', 'utf8')
const result = Babel.transform(file, {
//usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加
//corejs 3 是corejs的版本
presets: [
[presetEnv, { useBuiltIns: "usage", corejs: 3 }]
]
})
console.log(result.code)

img

转换之后的代码

1
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
"use strict";

require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.symbol.to-primitive.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.date.to-primitive.js");
require("core-js/modules/es.number.constructor.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
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); }
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.filter.js");
require("core-js/modules/es.object.assign.js");
require("core-js/modules/es.object.to-string.js");
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
//语法
var a = function a() {
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
return 1 + params;
};
var b = [1, 2, 3];
var c = [].concat(b, [4, 5]);
var Babel = /*#__PURE__*/_createClass(function Babel() {
_classCallCheck(this, Babel);
});
new Babel();
//API
var x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(function (x) {
return x % 2 === 0;
});
var y = Object.assign({}, {
name: 1
});

怎么支持jsx react呢

安装依赖 react react-dom

img

2. jsx代码转换react

测试用例 test.jsx

1
2
3
4
5
6
7
8
import react from 'react'
import { createRoot } from 'react-dom/client'

const App = () => {
return <div>lyric</div>
}

createRoot(document.getElementById('root')).render(<App />)

运行一下看一下效果

如果直接运行就会报错

编写代码 只需要增加一个预设即可 @babel/preset-react

1
npm install @babel/preset-react -D
1
2
3
4
5
6
7
8
9
10
11
12
import Babel from '@babel/core'
import presetEnv from '@babel/preset-env'
import fs from 'node:fs'
import react from '@babel/preset-react'
const file = fs.readFileSync('./app.jsx', 'utf8')
const result = Babel.transform(file, {
presets: [
[presetEnv, { useBuiltIns: "usage", corejs: 3 }],
react
]
})
console.log(result.code)

转换后的结果

其实是调用了React.createElement去创建元素

1
2
3
4
5
6
7
8
9
"use strict";

var _react = _interopRequireDefault(require("react"));
var _client = require("react-dom/client");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
var App = function App() {
return /*#__PURE__*/React.createElement("div", null, "\u5C0F\u6EE1\u662F\u8C01\uFF1F\uFF1F\uFF1F\uFF1F\uFF1F");
};
(0, _client.createRoot)(document.getElementById('root')).render(/*#__PURE__*/React.createElement(App, null));

预设是预设,插件是插件

3. 编写Babel插件 (这个是进阶部分)

img

1
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
import Babel from '@babel/core'
import fs from 'node:fs'
const file = fs.readFileSync('./test.js', 'utf8')
//babel会注入一个types对象里面包含了各种ast节点的方法
const transformFunction = ({ types: t }) => {
return {
name: 'babel-transform-function',
//visitor 是一个对象,它包含了一组方法,这些方法对应于 AST 中的不同节点类型。每当 Babel 遇到某种类型的节点时,都会调用 visitor 中对应的方法。
visitor: {
//匹配 箭头函数 当然也可以匹配别的东西 这儿只是案例
ArrowFunctionExpression(path) {
const node = path.node
const arrowFunction = t.functionExpression(
null, //node.id 是一个 Identifier 节点,表示函数名
node.params, //node.params 是一个数组,表示函数的参数
// BlockStatement 是 JavaScript 抽象语法树(AST)中的一种节点类型,表示一个由大括号 {} 包围的语句块。它是函数体、循环体、条件分支(如 if 语句)等代码块的基础结构
t.blockStatement([t.returnStatement(node.body)]), //node.body 是函数的主体,通常是一个 BlockStatement 节点
node.async //node.async 是一个布尔值,表示函数是否是异步的 (async 函数)
)
path.replaceWith(arrowFunction) //替换当前节点
}
}
}
}
const result = Babel.transform(file, {
plugins: [
transformFunction
]
})
console.log(result.code)

转换之后的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//语法
const a = function (params = 2) {
return 1 + params;
};
const b = [1, 2, 3];
const c = [...b, 4, 5];
class Babel {}
new Babel();
//API
const x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter(function (x) {
return x % 2 === 0;
});
const y = Object.assign({}, {
name: 1
});

4. 更多

前文只是引出 babel 实现的 AST 思想

如何开发一个Babel插件,可以参考:

  1. 如何开发一个Babel插件

  2. @babel/types

    描述了开发插件可能用到的各种方法及参数类型

  3. babel plugin handbook

  4. 手把手教你开发一个babel-plugin

背景

不同的项目、不同的页面都需要用到某种UI控件,于是很自然的将这些UI控件拆出来,单独建立了一个代码库进行维护。

1
import { A, B } from 'kb-bi-vue-component';

这个时候,问题出现了,在页面中,仅仅使用了AB两个组件,但是页面打包后,整个组件库的代码都会被打进来,增加了产出的体积,包括了不少的冗余代码。

很容易想到的一个解决方案是按照以下的方式引用组件。

1
2
import A from 'kb-bi-vue-component/lib/componentA';
import B from 'kb-bi-vue-component/lib/componentB';

这种方法虽然解决了问题,想引用哪个组件,就引用哪个组件,不会有多余的代码。

但是感觉可读性不高,有没有还能像第一种写法一样引用组件库,并且只引用需要的组件呢?写一个babel-plugin好了,自动将第一种写法转换成第二种写法。


AST是个什么玩意儿.md
http://example.com/2024/05/10/AST是个什么玩意儿/
作者
lyric
发布于
2024年5月10日
许可协议