JavaScript
AST
vue.js
babel
Vuex

vuexのボイラープレートをbabelで飛び越えろ! -> はじめの一歩

最近、仕事でvue.jsを触り始めたのですが、そこで使用しているvuexも、やはりボイラープレートが多いです。
そのため、vuexにおけるボイラープレートをbabelで自動生成してコード記述量を削減していく、
というのをモチベーションにしばらくbabel関連のコードを書いてみようと思います。

なお、vue.jsについては(そしてbabelもですが...)触り始めて間もないので、そもそもコード自体の書き方がおかしい部分があるかもしれません。
そこについてはコメントなどからツッコミ入れていただければ幸いです。
Qiitaには編集リクエストというのがあるので、こちらからリクエストとかもらえるのかしら?

今回のゴール

今回実施したいコード変換のゴールを下記のように設定しました。

下記のようなコードを、

// IN
import { CREATE, SELECT, UPDATE, DELETE } from './mutation-types'

下記に変換する
↓↓↓↓↓↓↓↓↓↓↓

// OUT
// mutation-types.js に生成
export const CREATE = "CREATE";
export const SELECT = "SELECT";
export const UPDATE = "UPDATE";
export const DELETE = "DELETE";

vuexを使用した際にimportしたメンバーをmutation-types.jsにも生成してしまえるようにしたいと思い、記述したコードをbabelを利用して変換かけるところまでをゴールにしました。
(ハードル低いですが、高いとやる気なくなっちゃうのでね、、、まずは変換まで)

前回babylonbabel-generatorだけ使って書きましたが、今回はそのような制限はかけずに書いています。

追記(2018/1/8)に下記のコードの修正版を追記しました。

import template from 'babel-template'
import generate from 'babel-generator'
import * as t from 'babel-types'

// 生成用テンプレート
const buildRequire = template(`
  export const ACTION_NAME = ACTION_STR
  `, { sourceType: 'module' })

module.exports = (babel) => {
  let actionName = null; // importしているメンバーを取得用
  let filename = null; // import元のファイル名取得用に用意

  return {
    visitor: {
      ImportDeclaration: path => {
        path.node.specifiers.forEach((n) => {
          actionName = n.imported.name
          filename = path.node.source.value
          const test = buildRequire({
            ACTION_NAME: t.identifier(actionName),
            ACTION_STR: t.StringLiteral(actionName)
          });
          console.log(generate(test).code)
          // console.log(filename) // => ./mutation-types
        })
      }
    }
  }
}

importDeclarationを使うことでいい感じに取得できるので、コードを書いていても頭への負担が少ないです。
そのため、今のところは挫折しないで頑張れそう笑。

const code = `
import { CREATE, SELECT, UPDATE, DELETE } from './mutation-types';
`;

require('babel-core').transform(code ,{plugins:['./plugin.js']}).code

で、上のコード(input.js)を実行すると、

babel-node input.js

# ↓実行結果
export const CREATE = "CREATE";
export const SELECT = "SELECT";
export const UPDATE = "UPDATE";
export const DELETE = "DELETE";

とりあえず意図したコードに変換できています。
この生成したコードをファイルmutation-types.jsに書き込むようにすれば、やりたいことが実現できそうです。。。!

実際にファイルに書き込むにはどうすれば良いかは次の課題にしようと思います。

追記(2018/1/8)

上に書いたコードを修正しました。

// plugin.js
import template from 'babel-template'
import * as t from 'babel-types'

// 生成用テンプレート
const buildRequire = template(`
  export const ACTION_NAME = ACTION_STR
  `, { sourceType: 'module' })

export default () => {
  return {
    visitor: {
      ImportDeclaration: path => {
        path.node.specifiers.forEach((n) => {
          const buildAst = buildRequire({
            ACTION_NAME: t.identifier(n.imported.name),
            ACTION_STR: t.StringLiteral(n.imported.name)
          });
          // tempalteを使って生成したASTを対象コードの後ろに挿入
          path.insertAfter(buildAst)
        })
        // 対象箇所自体(import〜の部分)を削除
        path.remove()
      }
    }
  }
}

path.insertAfterpath.removeを使うことで、色々と中身をいじることが出来ることを学びました。

下記のコードを実行することで、変換が可能です。

const code = "import { CREATE, SELECT, UPDATE, DELETE } from './mutation-types';"
const newCode = require('babel-core').transform(code,{plugins:['./plugin.js']}).code
console.log(newCode)

Babel Plugin Handbookは何度か繰り返し目を通しておくと、こういう処理したいなーって時にすぐに実現方法が浮かんできて良いかもと思いました。

参照

babel@6プラグインの単純な作成方法について

Babel types

Babel Plugin Handbook