社内勉強会資料。
最近、babylonでJavaScriptのコードを変換するツールを作っていて、まだ出来てませんが、現状調べたことのまとめです。
ASTの基礎
JavaScript ASTを始める最初の一歩 | Web Scratch
上記記事がよいです。ざっくりまとめると、
- AST => コードをパースした抽象構文木のこと
- JavaScriptの場合はJavaScriptオブジェクト(JSON)として表現
- ツールの分類
- Parser ... ソースコードをASTに変換する
- Traverser ... ASTの木構造を探索。ノードを差し替えたり、削除したりする
- Generator ... ASTからソースコードを生成する
- Parserは大きく2系統(Esprima, Acorn)
- babylonはAcornベースで、JSXなどESTreeから拡張してる
- ParserによってASTのフォーマットが若干(ではないかも)異なるので、Generatorは同じ系統のものを使う
- Esprima -> escodegen, babylon -> babel-generatorみたいな感じ
Esprimaで試して、何か足りないならAcorn -> Babylonという感じでやるのが良いと思います。
上記記事に気付かず始めてしまったのでbabel系のツールを使っていますが、EsprimaかAcornの方が安定している&ドキュメントが充実しているようです。
babel系のツール群
- babylon
- Parser
- https://github.com/babel/babylon
- Acornベース、JSXやFlowtype、stageN系のやつもパースできる
- babel-generator
- babel-traverse
- Traverser
- https://github.com/babel/babel/tree/master/packages/babel-traverse
- ドキュメントがないので泣きながらテストコードやソースコードを読んだが、ast-typesと同じインタフェースな気がする。。
- babel-types
- ASTのNodeの生成など
コードの変換
ASTを使ったソースコード変換の流れ
JSer.infoの記事にも書いておりますが、下記のような流れです。
- JavaScriptのソースコードをASTに変換する(Parser)
- ASTを探索して、任意のノードを入れ替えたり、削除したりする(Travarser)
- 2で出来たASTからJavaScriptのソースコードを生成する(Generator)
babel-parserでソースコードをASTに変換して、babel-traveseでASTを探索して、babel-typesで作ったASTと入れ替えたりしつつ、出来上がったASTをbabel-generatorでソースコードに変換するという感じです。
小さなサンプル
パッケージをインストールします。
$ npm install -S babylon babel-traverse babel-generator babel-types
hoge
をfuga
に変換するコードを書いてみました。(babelで書いてしまったので、その辺りのパッケージも入れてください)
// hoge.js
import {parse} from 'babylon';
import generate from 'babel-generator';
import traverse from 'babel-traverse';
import * as t from 'babel-types';
// hogeをfugaに変更する関数
const replaceHogeToFuga = code => {
// 1. JavaScriptのソースコードをASTに変換する
const ast = parse(code);
// 2. ASTを探索して、任意のノードを入れ替えたり、削除したりする
traverse(ast, {
// ノードがIdentifierの場合に呼び出される
// pathはNodePathのインスタンス、ast-typesと似た感じのインタフェース
Identifier: path => {
if (path.node.name === 'hoge') {
// ast-typesと同じかと思われたが, replaceはreplaceWithだった
path.replaceWith(t.identifier('fuga'));
}
}
});
// 3. 2で出来たASTからJavaScriptのソースコードを生成する
const output = generate(ast, {}, code);
return output.code;
};
console.log(replaceHogeToFuga("hoge('Hello');"));
babel-nodeで実行してみます。
$ babel-node hoge.js
fuga('Hello');
いえーい
おわり
- babel-traverseのドキュメントがなく、調べるのに時間がかかってしまい、本当に基礎の基礎の話になってしまった。
- そもそもbabylonを選んでしまったのが間違いだったかもしれない。
- 次はもうちょっと濃いノウハウを