箭头函数转换插件

先来看看 ES 标准下的箭头函数 和 普通函数的 AST 区别

  1. const sum = (a, b) => a + b
  2. const sum = function(a, b) { return a + b }

image.pngimage.png
我们需要把 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/)来简化我们的操作。

  1. @babel/core提供了JavaScript 转 AST,AST 转 JavaScript 的功能
  2. @babel/types提供了判断节点类型,创建节点的功能
  1. const babel = require('@babel/core')
  2. const t = require('@babel/types')
  3. const source = `
  4. const good = () => {}
  5. let sum = (a, b) => { return a + b }
  6. `
  7. // 箭头函数转换插件
  8. const ArrowTransformPlugin = {
  9. visitor: {
  10. // path 是树的路径
  11. ArrowFunctionExpression: function (path) {
  12. const node = path.node
  13. let blockStatement = node.body
  14. // 如果不是代码块,就改成代码块
  15. if (!t.isBlockStatement(blockStatement)) {
  16. const returnStatement = t.returnStatement(node.body)
  17. blockStatement = t.blockStatement([returnStatement])
  18. }
  19. const fnExpression = t.functionExpression(null, node.params, blockStatement)
  20. path.replaceWith(fnExpression)
  21. }
  22. }
  23. }
  24. const result = babel.transform(source, {
  25. plugins: [
  26. ArrowTransformPlugin
  27. ]
  28. })
  29. 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)

  1. <a name="Dq9qs"></a>
  2. ## JSX 转换插件
  3. babel 转换 JSX 语法的工具包是 `@babel/preset-react`这个预设,它包含了以下插件
  4. - [@babel/plugin-syntax-jsx](https://www.babeljs.cn/docs/babel-plugin-syntax-jsx):让 @babel/core 能识别 JSX 语法,正确构建 babel 的 AST 标准树
  5. - [@babel/plugin-transform-react-jsx](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx):将 JSX 语法转换为 react 创建元素的语法
  6. - [@babel/plugin-transform-react-display-name](https://www.babeljs.cn/docs/babel-plugin-transform-react-display-name)
  7. 如果开启了 `development`参数,还将包含以下插件
  8. - [@babel/plugin-transform-react-jsx-self](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx-self)
  9. - [@babel/plugin-transform-react-jsx-source](https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx-source)
  10. 下面我们试着实现将含 JSX 语法的 JS 代码转换成 babel 标准的 AST 的功能
  11. 1. 词法分析:JS 代码 转换为 tokens
  12. 2. 语法分析:tokens 转换为 AST
  13. ```javascript
  14. const babel = require('@babel/core')
  15. const t = require('@babel/types')
  16. const source = `
  17. let element = <p>Konsoue</p>
  18. `
  19. /**
  20. * 词法分析:先将 JS 代码转成 tokens
  21. * [
  22. * { type: 'KeyWord', value: 'let' },
  23. * { type: 'WhiteSpace', value: ' ' },
  24. * { type: 'Identifier', value: 'element'}
  25. * { type: 'WhiteSpace', value: ' ' },
  26. * { type: 'Equal', value: '=' },
  27. * { type: 'WhiteSpace', value: ' ' },
  28. * { type: 'JSXElement', value: '<p>Konsoue</p>' },
  29. * ]
  30. */
  31. function JSXToTokens(source) {
  32. const tokens = []
  33. let token;
  34. const isWord = /[a-zA-Z]/
  35. const isWhiteSpace = /\s/
  36. const isEqual = /=/
  37. for (let i = 0; i < source.length; i++) {
  38. let char = source.charAt(i)
  39. if (isWord.test(char)) {
  40. token = { type: 'Identifier', value: char }
  41. while (i++ < source.length) {
  42. char = source.charAt(i)
  43. if (isWord.test(char)) {
  44. token.value += char
  45. } else {
  46. if (['let', 'const', 'var'].includes(token.value)) token.type = 'KeyWord'
  47. i--
  48. break
  49. }
  50. }
  51. } else if (isWhiteSpace.test(char)) {
  52. token = { type: 'WhiteSpace', value: ' ' }
  53. while (i++ < source.length) {
  54. char = source.charAt(i)
  55. if (isWhiteSpace.test(char)) {
  56. token.value += char
  57. } else {
  58. i--
  59. break
  60. }
  61. }
  62. } else if (isEqual.test(char)) {
  63. token = { type: 'Equal', value: char }
  64. } else if (char === '<') {
  65. token = { type: 'JSXElement', value: char }
  66. let isClose = false
  67. while (i++ < source.length) {
  68. char = source.charAt(i)
  69. token.value += char
  70. if (char === '/') {
  71. isClose = true
  72. } else if (char === '>' && isClose) {
  73. break
  74. }
  75. }
  76. } else {
  77. token = { type: 'String', value: char }
  78. while (i++ < source.length) {
  79. char = source.charAt(i)
  80. token.value += char
  81. }
  82. if (!Number.isNaN(Number(token.value))) token = { type: 'Numeric', value: Number(token.value) }
  83. else {
  84. let str = '', start = false
  85. // 去掉字符串外层的双引号 ""
  86. const top = token.value[0] // 获取第一个引号,用于判断是单引号还是双引号
  87. for (let j = token.value.length - 1; j > 0; j--) {
  88. if (start) str = token.value[j] + str
  89. else {
  90. if (token.value[j] === top) start = true
  91. }
  92. }
  93. token.value = str
  94. }
  95. }
  96. tokens.push(token)
  97. }
  98. return tokens
  99. }
  100. /**
  101. * 语法分析:将 tokens 转成 AST
  102. * @param {*} tokens
  103. */
  104. const parser = (tokens) => {
  105. const AST = {
  106. type: 'Program',
  107. body: [],
  108. sourceType: 'module'
  109. }
  110. let i = 0
  111. let currentToken
  112. while (currentToken = tokens[i]) {
  113. if (currentToken.type === 'KeyWord') {
  114. const VariableDeclaration = { type: 'VariableDeclaration', declarations: [], kind: currentToken.value }
  115. i += 2
  116. currentToken = tokens[i]
  117. const VariableDeclarator = {
  118. type: 'VariableDeclarator',
  119. id: {
  120. type: 'Identifier',
  121. name: currentToken.value
  122. }
  123. }
  124. i += 2
  125. while (['WhiteSpace', 'Equal'].includes(tokens[i].type)) i++
  126. currentToken = tokens[i]
  127. if (['String', 'Numeric'].includes(currentToken.type)) {
  128. VariableDeclarator.init = {
  129. type: currentToken.type + 'Literal',
  130. value: currentToken.value
  131. }
  132. } else if (currentToken.type === 'JSXElement') {
  133. const value = currentToken.value
  134. const reg = /<([^>]+)>([^<]+)<\/([^>]+)>/
  135. const [, openTagName, childrenText, closeTagName] = value.match(reg)
  136. VariableDeclarator.init = {
  137. type: 'JSXElement',
  138. openingElement: {
  139. type: 'JSXOpeningElement',
  140. name: {
  141. type: 'JSXIdentifier',
  142. name: openTagName
  143. },
  144. },
  145. closeElement: {
  146. type: 'JSXOpeningElement',
  147. name: {
  148. type: 'JSXIdentifier',
  149. name: closeTagName
  150. }
  151. },
  152. children: {
  153. type: 'JSXText',
  154. value: childrenText
  155. }
  156. }
  157. }
  158. VariableDeclaration.declarations.push(VariableDeclarator)
  159. AST.body.push(VariableDeclaration)
  160. }
  161. i++
  162. }
  163. return AST
  164. }
  165. const tokens = JSXToTokens(source)
  166. const AST = parser(tokens)
  167. console.log(JSON.stringify(AST))