Babylon-AST初探査-実戦


以前の3つの文章の紹介を経て、ASTCRUDはすでに完成した.以下は主にvueから に移行する過程で使用する重要な技術の一部を通じて実戦する.
次の例のコアコードは依然として最も簡単なvue例である.
const babylon = require('babylon')
const t = require('@babel/types')
const generate = require('@babel/generator').default
const traverse = require('@babel/traverse').default

const code = `
export default {
  data() {
    return {
      message: 'hello vue',
      count: 0
    }
  },
  methods: {
    add() {
      ++this.count
    },
    minus() {
      --this.count
    }
  }
}
`

const ast = babylon.parse(code, {
  sourceType: 'module',
  plugins: ['flow']
})

本明細書のいくつかの操作を経て、最終的なウィジェットコードは以下の通りです.
Page({
  data: (() => {
      return {
        message: 'hello vue',
        count: 0
      }
  })(),
  add() {
    ++this.data.count
    this.setData({
      count: this.data.count
    })
  },
  minus() {
    --this.data.count
    this.setData({
      count: this.data.count
    })
  }
})

  注意:、私たちが前に紹介したのと一致して、上述の変換を完成するために、入力と出力をすべてAST explorerに入れて、その前後の構造の対比を見ます.vueコード回転ウィジェット
  文章の最初に示した2つのコードを比較し、変換を実現するには、次の手順が必要です.
  • data関数をdata属性に変換し、data関数
  • を削除する.
  • methodsの属性を抽出し、dataと同じレベルに配置し、methods
  • を削除する.
  • は、すべてのthis.[data member]this.data.[data member]に変換する.ここではdataの属性
  • のみを回転する.
  • 変更this.dataの下にthis.setDataを挿入するデータ変更
  • をトリガする.
    次はこの手順に従って、一歩一歩変換を完了し、一歩一歩コードの変化を見ると達成感があると思います.data属性の生成
    このステップでは、元のdata関数のreturnのオブジェクトを抽出します.AST explorerと組み合わせると、このパスを簡単に見つけることができます.
    const dataObject = ast.program.body[0].declaration.properties[0].body.body[0].argument
    console.log(dataObject)

    しかし、このコードの可読性とロバスト性は基本的に0でしょう.それは私たちが書いたdata関数に強く依存しているのが最初の属性です.ここでは主にtraverseを使用してノードにアクセスします.
    traverse(ast, {
      ObjectMethod(path) {
        if (path.node.key.name === 'data') {
          //        BlockStatement,   data   
          let blockStatement = null
          path.traverse({  // traverse     
            BlockStatement(p) {
              blockStatement = p.node
            }
          })
    
          //  blockStatement  ArrowFunctionExpression
          const arrowFunctionExpression = t.arrowFunctionExpression([], blockStatement)
          //   CallExpression
          const callExpression = t.callExpression(arrowFunctionExpression, [])
          //   data property
          const dataProperty = t.objectProperty(t.identifier('data'), callExpression)
          //     data    
          path.insertAfter(dataProperty)
    
          //    data  
          path.remove()
          // console.log(arrowFunctionExpression)
        }
      }
    })
    
    console.log(generate(ast, {}, code).code)

    プログラム出力:
    export default {
      data: (() => {
        return {
          message: 'hello vue',
          count: 0
        };
      })(),
      methods: {
        add() {
          ++this.count;
        },
    
        minus() {
          --this.count;
        }
    
      }
    };
    methodsの属性を1レベル上げる
    ここでは構造が固定されているため、methodsの属性はtraverseを採用していない.
    traverse(ast, {
      ObjectProperty(path) {
        if (path.node.key.name === 'methods') {
          //          methods  
          path.node.value.properties.forEach(property => {
            path.insertAfter(property)
          })
          //    methods
          path.remove()
        }
      }
    })

    プログラム出力:
    export default {
      data: (() => {
        return {
          message: 'hello vue',
          count: 0
        };
      })(),
    
      minus() {
        --this.count;
      },
    
      add() {
        ++this.count;
      }
    
    };
    this.memberからthis.data.member
    このステップでは、まずdata属性からデータ属性を抽出します.これはdataの関数に依存していますが、どのように書かれていますか.
      data: (() => {
        const obj = {}
        obj.message = 'hello vue'
        obj.count = 0
        return obj
      })(),

    これは私たちの転化方法に合わないだろう.もちろん、値を求めることで最終的なオブジェクトを得ることができますが、ここにも欠陥があります.もう1つの考え方は、他のメンバー関数を遍歴し、除外法を使用することです.
    要するに、this.dataのプロパティを取得する方法が必要です.本明細書では、datareturnメソッドによって、コードの例で引き続き取得する.
    //   `this.data`    
    const datas = []
    traverse(ast, {
      ObjectProperty(path) {
        if (path.node.key.name === 'data') {
          path.traverse({
            ReturnStatement(path) {
              path.traverse({
                ObjectProperty(path) {
                  datas.push(path.node.key.name)
                  path.skip()
                }
              })
              path.skip()
            }
          })
        }
        path.skip()
      }
    })
    console.log(datas)

    プログラム出力:
    [ 'message', 'count' ]

     データ属性をthis.data.に変更
    traverse(ast, {
      MemberExpression(path) {
        if (path.node.object.type === 'ThisExpression' && datas.includes(path.node.property.name)) {
          path.get('object').replaceWithSourceString('this.data')
        }
      }
    })

    このプログラムの出力:
    export default {
      data: (() => {
        return {
          message: 'hello vue',
          count: 0
        };
      })(),
    
      minus() {
        --this.data.count;
      },
    
      add() {
        ++this.data.count;
      }
    
    };
    this.setDataメソッドの追加
     変更this.dataの下にthis.setDataを挿入するには、まずその挿入位置、すなわちthis.dataの親ノードを見つけなければならないので、これが私たちの最初の操作です.(MemberExpressionは前のステップです.このステップのpathは前のステップと同じですから)
    traverse(ast, {
      MemberExpression(path) {
        if (path.node.object.type === 'ThisExpression' && datas.includes(path.node.property.name)) {
          path.get('object').replaceWithSourceString('this.data')
        }
      }
      const expressionStatement = path.findParent((parent) =>   
        parent.isExpressionStatement()
      )
    })

    挿入の場所を見つけたら、挿入する関数を構築します.このシリーズの最初の記事で紹介した(Create)[https://summerrouxin.github.i...]操作、忘れたのは復習してもいいですよ.次は直接コードをつけます.このコードは必ずAST explorerhとbabel-typesAPIと照らし合わせて、外から内への層の対照を見つけなければなりません.このコードの論理は次のようになります.
  • 挿入するコードの位置を見つけるには、まず付与操作かどうかを判断し、もしそうであればthis.memberの親ノード
  • を見つける.
  • 新規挿入するノード
  • 挿入ノード
  • traverse(ast, {
      MemberExpression(path) {
        if (path.node.object.type === 'ThisExpression' && datas.includes(path.node.property.name)) {
          path.get('object').replaceWithSourceString('this.data')
          //              
          if(
            (t.isAssignmentExpression(path.parentPath) && path.parentPath.get('left') === path) ||
            t.isUpdateExpression(path.parentPath)
          ) {
              // findParent
              const expressionStatement = path.findParent((parent) =>   
                parent.isExpressionStatement()
              )
              // create
              if(expressionStatement) {
                const finalExpStatement =
                  t.expressionStatement(
                    t.callExpression(
                      t.memberExpression(t.thisExpression(), t.identifier('setData')),
                      [t.objectExpression([t.objectProperty(
                        t.identifier(propertyName), t.identifier(`this.data.${propertyName}`)
                      )])]
                    )
                  )
                expressionStatement.insertAfter(finalExpStatement)
              }  
          }
        }
      }
    })

    プログラム出力:
    export default {
      data: (() => {
        return {
          message: 'hello vue',
          count: 0
        };
      })(),
    
      minus() {
        --this.count;
        this.setData({
          count: this.data.count
        })
      },
    
      add() {
        ++this.count;
        this.setData({
          count: this.data.count
        })
      }
    
    };

      以上は私たちの実戦紹介です.こちらはvueから へのコードの一部にすぎません.後で他のモジュールの紹介を続けることも考えられます.