箭头函数转换插件
先来看看 ES 标准下的箭头函数 和 普通函数的 AST 区别
const sum = (a, b) => a + bconst sum = function(a, b) { return a + b }


我们需要把 ArrowFunctionExpression 节点换成 FunctionExpression 节点,从图中我们可以看到,我们需要修改节点的 type 、body 、range 等属性,比较繁琐。
由于 babel 的生态十分丰富且坚挺,我们使用 babel 的工具包 [@babel/core](https://babel.docschina.org/docs/en/babel-core/)和[@babel/types](https://babel.docschina.org/docs/en/babel-types/)来简化我们的操作。
@babel/core提供了JavaScript 转 AST,AST 转 JavaScript 的功能@babel/types提供了判断节点类型,创建节点的功能
const babel = require('@babel/core')const t = require('@babel/types')const source = `const good = () => {}let sum = (a, b) => { return a + b }`// 箭头函数转换插件const ArrowTransformPlugin = {visitor: {// path 是树的路径ArrowFunctionExpression: function (path) {const node = path.nodelet blockStatement = node.body// 如果不是代码块,就改成代码块if (!t.isBlockStatement(blockStatement)) {const returnStatement = t.returnStatement(node.body)blockStatement = t.blockStatement([returnStatement])}const fnExpression = t.functionExpression(null, node.params, blockStatement)path.replaceWith(fnExpression)}}}const result = babel.transform(source, {plugins: [ArrowTransformPlugin]})console.log(result.code)
class 类转换插件
简单模拟 babel 的类声明转换为函数声明,待完善的功能
- 类的继承
- super 方法 ```javascript const babel = require(‘@babel/core’) const t = require(‘@babel/types’)
const source = class Father {
constuctor(name) {
this.name = name
}
getName() {
console.log(this.name)
}
}
const ClassTransformPlugin = { visitor: { ClassDeclaration: function (path) { const { node } = path const classMethodNodeList = node.body.body const className = node.id let fnDeclaration = t.functionDeclaration(className, [], t.blockStatement([])) const prototypeMethods = [] classMethodNodeList.forEach(nodeItem => { if (nodeItem.key.name === ‘constuctor’) { // 添加构造函数的方法 fnDeclaration = t.functionDeclaration(className, nodeItem.params, nodeItem.body) } else { // 添加原型的方法 let prototype = t.memberExpression(className, t.identifier(‘prototype’)) const left = t.memberExpression(prototype, t.identifier(nodeItem.key.name)) t.identifier(nodeItem.key.name) const right = t.functionExpression(null, [], nodeItem.body) // // 创建赋值表达式,添加函数原型方法 const prototypeMethod = t.assignmentExpression(‘=’, left, right) prototypeMethods.push(prototypeMethod) } }) if (!prototypeMethods.length) { path.replaceWith(fnDeclaration) } else { prototypeMethods.push(fnDeclaration) path.replaceWithMultiple(prototypeMethods) } } } }
const result = babel.transform(source, { plugins: [ ClassTransformPlugin ] })
console.log(result.code)
<a name="Dq9qs"></a>## JSX 转换插件babel 转换 JSX 语法的工具包是 `@babel/preset-react`这个预设,它包含了以下插件- [@babel/plugin-syntax-jsx](https://www.babeljs.cn/docs/babel-plugin-syntax-jsx):让 @babel/core 能识别 JSX 语法,正确构建 babel 的 AST 标准树- [@babel/plugin-transform-react-jsx](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx):将 JSX 语法转换为 react 创建元素的语法- [@babel/plugin-transform-react-display-name](https://www.babeljs.cn/docs/babel-plugin-transform-react-display-name)如果开启了 `development`参数,还将包含以下插件- [@babel/plugin-transform-react-jsx-self](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx-self)- [@babel/plugin-transform-react-jsx-source](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx-source)下面我们试着实现将含 JSX 语法的 JS 代码转换成 babel 标准的 AST 的功能1. 词法分析:JS 代码 转换为 tokens2. 语法分析:tokens 转换为 AST```javascriptconst babel = require('@babel/core')const t = require('@babel/types')const source = `let element = <p>Konsoue</p>`/*** 词法分析:先将 JS 代码转成 tokens* [* { type: 'KeyWord', value: 'let' },* { type: 'WhiteSpace', value: ' ' },* { type: 'Identifier', value: 'element'}* { type: 'WhiteSpace', value: ' ' },* { type: 'Equal', value: '=' },* { type: 'WhiteSpace', value: ' ' },* { type: 'JSXElement', value: '<p>Konsoue</p>' },* ]*/function JSXToTokens(source) {const tokens = []let token;const isWord = /[a-zA-Z]/const isWhiteSpace = /\s/const isEqual = /=/for (let i = 0; i < source.length; i++) {let char = source.charAt(i)if (isWord.test(char)) {token = { type: 'Identifier', value: char }while (i++ < source.length) {char = source.charAt(i)if (isWord.test(char)) {token.value += char} else {if (['let', 'const', 'var'].includes(token.value)) token.type = 'KeyWord'i--break}}} else if (isWhiteSpace.test(char)) {token = { type: 'WhiteSpace', value: ' ' }while (i++ < source.length) {char = source.charAt(i)if (isWhiteSpace.test(char)) {token.value += char} else {i--break}}} else if (isEqual.test(char)) {token = { type: 'Equal', value: char }} else if (char === '<') {token = { type: 'JSXElement', value: char }let isClose = falsewhile (i++ < source.length) {char = source.charAt(i)token.value += charif (char === '/') {isClose = true} else if (char === '>' && isClose) {break}}} else {token = { type: 'String', value: char }while (i++ < source.length) {char = source.charAt(i)token.value += char}if (!Number.isNaN(Number(token.value))) token = { type: 'Numeric', value: Number(token.value) }else {let str = '', start = false// 去掉字符串外层的双引号 ""const top = token.value[0] // 获取第一个引号,用于判断是单引号还是双引号for (let j = token.value.length - 1; j > 0; j--) {if (start) str = token.value[j] + strelse {if (token.value[j] === top) start = true}}token.value = str}}tokens.push(token)}return tokens}/*** 语法分析:将 tokens 转成 AST* @param {*} tokens*/const parser = (tokens) => {const AST = {type: 'Program',body: [],sourceType: 'module'}let i = 0let currentTokenwhile (currentToken = tokens[i]) {if (currentToken.type === 'KeyWord') {const VariableDeclaration = { type: 'VariableDeclaration', declarations: [], kind: currentToken.value }i += 2currentToken = tokens[i]const VariableDeclarator = {type: 'VariableDeclarator',id: {type: 'Identifier',name: currentToken.value}}i += 2while (['WhiteSpace', 'Equal'].includes(tokens[i].type)) i++currentToken = tokens[i]if (['String', 'Numeric'].includes(currentToken.type)) {VariableDeclarator.init = {type: currentToken.type + 'Literal',value: currentToken.value}} else if (currentToken.type === 'JSXElement') {const value = currentToken.valueconst reg = /<([^>]+)>([^<]+)<\/([^>]+)>/const [, openTagName, childrenText, closeTagName] = value.match(reg)VariableDeclarator.init = {type: 'JSXElement',openingElement: {type: 'JSXOpeningElement',name: {type: 'JSXIdentifier',name: openTagName},},closeElement: {type: 'JSXOpeningElement',name: {type: 'JSXIdentifier',name: closeTagName}},children: {type: 'JSXText',value: childrenText}}}VariableDeclaration.declarations.push(VariableDeclarator)AST.body.push(VariableDeclaration)}i++}return AST}const tokens = JSXToTokens(source)const AST = parser(tokens)console.log(JSON.stringify(AST))
