この記事は、Node.js Advent Calendar の11日目の記事です。
Motivation
なんか、色々あって大量の更新が必要な場合がありますが、正規表現で直してミスないか確認するのもそろそろしんどくなっていて、前回紹介してもらったjscodeshiftを勉強しようと思いました。
試しに使う列
まず列として APIの変更 を対応してみましょう。
new Vue({
attached: function() {
doSomething()
}
});
がら
new Vue({
mounted: function() {
this.$nextTick(function () {
doSomething()
})
}
});
に変換されたらOKですね。
最初に AST Explorer を開いてtransformをクリックしcodeshiftを選択してください。左上に元になるコードを貼り付けたら準備完了です。
流れ
やり方と知っている前提でこんな感じになるかな想定しています。
- 対象となるコードを探す
- 変換結果のASTを確認する
- 変換コードを書く
- codemodに落とす
- コードに適用する
対象となるコードを探す
まずは元になるコードの ast tree を確認してみましょう。
マウスで tree をクリックしながら変更したい部分を探します。この場合は key の名前がattached なっている property が対象です。
変換結果のASTを確認する
同じ感覚で結果コードの ast tree を確認してみましょう。
変換コードを書く
AST Explorer 左下にすでに例があります。 find で探して forEach の中で変換するのだけわかれば充分です。
// Press ctrl+space for code completion
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.Identifier) // 対象を探す
.forEach(path => { // 変換
j(path).replaceWith(
j.identifier(path.node.name.split('').reverse().join(''))
);
})
.toSource();
}
まずは対象になるこどを探してみましょう。 type が Property で name が "attached" になっているものだけ探します。
.find(j.Property, path => {
return path.key && path.key.name === "attached"
})
仕様がわからないどきも()の中にカソールを入れたらこのようにhintが出るので助かります。
まずは名前から変えてみます。
.forEach(path => {
path.value.key.name = "mounted"
})
お勧めする方法かはわからないんですが普通にアサインするだけでいけました。
あとは中身を取っどいて
let inner = path.value.value;
上のast tree を参考しながらない部分を作ります。
j.functionExpression(
null,
[],
j.blockStatement([
j.expressionStatement(
j.callExpression(
j.memberExpression(
j.thisExpression(),
j.identifier("$nextTick")
),
[inner]
)
)
])
);
ctrl + spaceで自動補完するので書くのはサクサクですが、継続が深くて確認しならがら植えないのが苦痛でした。
完成したコードはこんな感じです。
export default function transformer(file, api) {
const j = api.jscodeshift;
return j(file.source)
.find(j.Property, path => {
return path.key && path.key.name === "attached"
})
.forEach(path => {
path.value.key.name = "mounted"
let inner = path.value.value;
path.value.value =
j.functionExpression(
null,
[],
j.blockStatement([
j.expressionStatement(
j.callExpression(
j.memberExpression(
j.thisExpression(),
j.identifier("$nextTick")
),
[inner]
)
)
])
);
})
.toSource();
}
codemodに落とす
react-codemod のディレクトリ構造を参考して作ります。
transforms/MyTransform.js
transforms/__tests__/MyTransform-test.js
transforms/__testfixtures__/MyTransform.input.js
transforms/__testfixtures__/MyTransform.output.js
testの中には
jest.autoMockOff();
const defineTest = require('jscodeshift/dist/testUtils').defineTest;
defineTest(__dirname, 'MyTransform');
と書にjestで実行できます。 細かい設定は上のプロジェックトを参考してください。
プロジェックトに使ってみる
こんな感じでいけます。
jscodeshift path/to/target -t transforms/MyTransform.js
結論
まだ慣れてないからでもあると思いますが、regexpに比べて地味に時間がかかるのが辛いですね。
でも、パタン毎に確実に潰せるしテストとして使い方を確認できるのは嬉しいです。
参考
- https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb#.k2hsr9rqd
- https://vramana.github.io/blog/2015/12/21/codemod-tutorial/
- https://www.toptal.com/javascript/write-code-to-rewrite-your-code
- https://medium.com/airbnb-engineering/turbocharged-javascript-refactoring-with-codemods-b0cae8b326b9#.ioagaknfu
- https://github.com/cpojer/js-codemod
- https://github.com/reactjs/react-codemod