はじめに
株式会社リゾーム 企画・開発部第4グループの平松です。
現在、自社サービスの一つである交渉管理ware*1の改修を行っています。
現時点の交渉管理wareのRailsのバージョンは5.2.6です。最新版にバージョンアップしていこうと思っているのですが、その改修の一環として今回はCoffeeScriptからES2015以降のJavaScriptへの移行を行いました。
弊社のプロジェクトでは逐次CoffeeScriptからJavaScriptへの移行が進んでおり、当サービスにおいてもJavaScriptの方が他のライブラリやフレームワークを使う際など開発や保守が行いやすいと判断し、移行することにしました。
今回の移行ですがWebpacker等を使った移行ではなくSprocketsを使っているため、Sprockets用の作業内容が含まれています。ご注意ください。
移行の準備
JavaScriptに移行していきますがファイル一つ一つ手作業で直していくのは時間が掛かります。 そのため、decaffeinateというツールを使ってJavaScriptに変換していきます。
decaffeinateのGitHubのページに変換ガイドがあるのでそれに沿って進めていきます。
まずは、ツールのインストールから始めます。
npm install -g bulk-decaffeinate decaffeinate eslint
インストールできたら次は変換していくのですがその前に、変換できるかどうか確認します。 問題なければコマンドを打って変換していきます。
bulk-decaffeinate check
JavaScriptに一括変換
下記のコマンドでCoffeeScriptからJavaScriptに一括変換します。
-d オプションでディレクトリの指定をしています。
-f オプションでファイルの指定が可能です。
bulk-decaffeinate convert -d app/assets/javascripts
これでJavaScriptに変換できました。
しかし、単純に変換をかけただけではエラーが発生し、動きませんでした。
まずは正しく動くように修正し、その後、リファクタリングをしていきます。
参考: decaffeinate/conversion-guide.md at master · decaffeinate/decaffeinate · GitHub
動作するように修正
thisの前にsuperを配置
クラスが親クラスを継承している場合、thisを呼ぶ前にsuperを呼ぶ必要があります。
superを呼び出すことで親クラスのコンストラクターを呼ぶことができます。
先にthisを呼んだ場合、ReferenceErrorになります。
// before class foo extends bar { constructor(hoge) { this.hoge = hogehoge; super(hoge); // エラーになります } }
// after class foo extends bar { constructor(hoge) { super(hoge); // superを先に呼ぶ this.hoge = hogehoge; } }
ES6(ES2015)に対応
元々のCoffeeScriptの方でES6の構文で書かれている箇所もあるため、それに対応していきます。
交渉管理wareではアセットファイルの管理にSprocketsを使用しています。
GitHubのページにES6サポートの項目があるのでそれに沿って進めます。
babel-transpilerをインストールする
Gemfileに追記してbundle installをします。
gem 'babel-transpiler'
manifest.jsのコンパイル対象に「.es6」を追加する
ES6に対応したファイルがコンパイル対象になるようにmanifest.jsに追記します。
//= link_directory ../javascripts .es6
拡張子を「es6」に変更する
拡張子を変更してES6ファイルとして扱われるようにします。
hogehoge.js => hogehoge.es6
ここまで行ったところ、正常に動作することが確認できました。
参考:
GitHub - rails/sprockets: Rack-based asset packaging system
Sprockets 4 なら ES6書けるよ - Qiita
リファクタリング
正常に動作するところまでできたので次はリファクタリングをしていきます。
varをlet,constに置き換える
varは再宣言でき、またスコープ外から参照できるため扱いづらいです。
そのため、letもしくはconstをできるだけ使うようにします。
let hoge = hogehoge; // もしくは const hoge = hogehoge;
constructorの引数をデフォルト引数にする
decaffeinateで変換した結果、元々デフォルト引数だったものがif文でデフォルト値を定義するように変換されていたため、元に戻しました。
// before class Foo { constructor(hoge) { if (hoge == null) { hoge = 'hoge'; } } }
// after class Foo { constructor(hoge = 'hoge') { } }
アロー関数を使用する
function()
と書かれている箇所を() =>
のように修正します。
アロー関数自体はthisを保持しないため、アロー関数によってthisの参照先が変わることはありません。
そのため、selfにthisを代入する必要がなくなります。
※ 元々の処理がthisの値が変わること前提の場合、アロー関数にしてしまうと動作が変わってしまうので注意が必要です。
// before var self = this; this.hogehoge = function() { self.foo = "baz"; };
// after this.hogehoge = () => { this.foo = "baz"; };
var self = this;
を削除する
前述のアロー関数を使用することでselfを使う必要がなくなるので下記のコードを削除し、selfをthisに置換します。
var self = this; // この行を削除
配列をforからforEachで処理する
decaffeinateで変換した結果、forで配列を処理をしていた箇所があったため、forEachで簡潔にします。
// before for (let index = 0; index < hoges; index++) { hoge = hoges[index]; // 何らかの処理 }
// after hoges.forEach(hoge => { // 何らかの処理 }); // または hoges.forEach(hoge => "何らかの処理");
まとめ
今回はdecaffeinateを使用してCoffeeScriptをJavaScriptに一括変換しました。decaffeinateは便利ですが、既存の処理が変わっていたり、そもそも正しく動かなくなってしまうこともあるため、JSファイルの数によっては手動で直したほうが手間がかからないかもしれません。
移行作業の中で難しかったところはアロー関数に変更していった部分です。thisの値によってアロー関数を使わない方がいいところもあったため、都度確認が必要でした。
また、作業自体は動作確認をしながら進めていき、漏れがないように注意してましたがテストを流した際に何個か落ちてしまいました。
各機能の処理内容の理解とともにテストの重要性が感じられた作業となりました。
今後も改修を続けていくので内容がまとまったら記事を出していこうと思っています。