はじめに
こんにちは、WantedlyのDX (Developer Experience) チームでインターンをしていた田村です。今回のインターンではWantedlyのフロントエンドのトランスパイラをBabelからSWCに移行することを目標に調査を行いました。本記事では調査内容と我々が選んだ具体的な移行方法について紹介します。BabelプラグインをSWCに移植する方法やSWCプラグインの作成方法については、プラグイン作成編で詳しく説明しています。
SWCとは
SWCはRust製の高速なトランスパイラです。Next.jsがSWCの利用を推進しており、Next.js 12からはデフォルトのトランスパイラがBabelからSWCになりました。またSWCのソースコードはいくつかに分けてクレート化されており、Next.jsのみならずDenoの内部でも利用されています。
Babel vs SWC
SWCはBabelの置き換えを狙っており、基本的にはBabelと同様に使うことができます。より詳細な比較は以下のとおりです。
注
*1: Rust製のSWCカスタムプラグインを使うには、RustからWASMにコンパイルして.swcrcでパス ("プラグイン名.wasm") を指定することが必要になります。もしくは、SWC CLIでWASMバイナリのパスを渡すこともできます。
Babelパッケージに含まれるBabel公式プラグインのほとんどが既にSWCに移植されており、設定ファイルである.swcrcから、トリガーの設定を行うことができます。 Babel公式プラグインに限った移植状況についてはMigrating from Babelを参照してください。
WantedlyにおいてSWCに移行したい理由
WantedlyではいくつかのリポジトリでBabelを利用していますが、SWCに移行するメリットとして以下が挙げられます。
開発効率の向上
swcはBabelよりずっと高速に動作します。ローカル開発での動作確認やユニットテスト前のビルドやCI/CDでのビルドにかかる時間短縮できます。ビルド時間の数秒の違いは開発体験に大きく直結します。
Next.jsがswcを推進しているため
現在Wantedlyの最新世代のフロントエンドアーキテクチャではNext.jsが使われています。Next.jsは今後SWCのサポートを勧めていくことを明言しているため、Wantedlyでもこの時流に乗るのが良いと考えられます。
Next-SWC (Next.js Compiler)
ここでは、Next.jsとBabelを利用している環境をNext.js+SWCの環境に移行する際に、知っておく必要のあるNext-SWCについて解説します。
Next.jsがデフォルトで使うSWCは、SWCのラッパーになっていて、SWCで使える機能が一部制限されていたり、拡張されていたりします。このデフォルトで使われるSWCのことをNext-SWCといいます。(公式ではNext.js Compilerとも呼ばれています)
2022年3月9日現在、Next-SWCは本来SWCが参照する設定ファイルである.swcrcを参照しません。そのため、Next-SWCはカスタムプラグインと併用することができません。その代わりに現時点で、Next-SWCはSWCとは異なる方法でプラグインが組み込まれています。これらのプラグインはその性質上 (注1)、Next.js組み込みプラグインと呼ぶことができます。
例えば、Next.js組み込みプラグインにはbabel-plugin-remove-react-propertiesと同等の働きをするremove-react-propertiesや、babel-plugin-styled-componentsと同等の働きをするstyled-componentsなどがあります。これらの組み込みプラグインはnext.config.jsからトリガーの設定をすることができます。詳細についてはNext.jsのリリースノートを参照してください。
これらを表にまとめると以下のようになります。
O: 利用可能, X: 利用不可
*1: babel-plugin-syntax-typescript, babel-plugin-proposal-class-properties, etc.
*2: styled-components, remove-react-properties, etc.
*3: babel-plugin-styled-components, etc.
*4: 今後、多くのNext.js組み込みプラグインはSWCのカスタムプラグインとして使えるようにサポートされていくと思われます
注1: Next-SWCはRustで書かれており、SWCのライブラリを利用しています。Next-SWCは単にトランスパイルを行うためのラッパー関数を外部に提供しているだけでなく、Next-SWC と同時にビルドされるいくつかのRust製プラグインを含んでいます。これは余談ですが、Next.jsの組み込みプラグインを改造してSWCカスタムプラグインとして利用する試みを行いました。その結果、一部のNext.js組み込みプラグイン (styled-components) は、カスタムプラグインのインターフェースで提供されない情報を必要とすることが判明し、不可能だと考えられます。
BabelからSWCに移行する方法
ここでは、BabelからSWCに移行する際の具体的な選択肢について紹介します。まず、Babelを使っている環境には以下の二種類あります。
- Next.jsを使わずにBabelを使っている環境
- Next.jsとBabelを併用している環境
それぞれの構成では、移行方法が全く異なることに注意してください。
1. Next.jsを使わずにBabelを使っている環境
自分が利用しているBabelプラグインの移植版(SWCカスタムプラグイン)を用意して、.swcrcを作成した後に、Babelを利用している箇所を全てSWCに置き換える
補足
SWCプラグインをJSで書くためのドキュメントはありますが、Rustで書くためのドキュメントは現時点で整備されていません。
2. Next.jsとBabelを併用している環境
この構成ではSWCへの移行方法はいくつかあります。まず初めに、最も推奨される方法を説明します。
推奨される方法
Next-SWCが.swcrcの読み込みとカスタムプラグインをサポートするのを待ち、利用しているBabelプラグインのSWC移植版を用意した上で、Next.jsのトランスパイラを切り替えることで移行する
補足
現在のNext.jsは.babelrcが存在する場合のみBabelが使われるようになっています。今後、Next-SWCが.swcrcの読み込みをサポートするようになれば.babelrcと同様の設定を.swcrcに記載し、.babelrcを削除することで、トランスパイラの切り替えができるようになると思われます。
非推奨な方法1
プラグインの移植版を用意した上で、Next.jsと (Next-SWCではない) 通常のSWCを併用する
備考
- 本当に実行できるのか要検証
- もし実行可能なら、現時点でNext.jsとSWCカスタムプラグインを併用する唯一の手段である
- ビルドの設定が複雑化する可能性がある
- Next.js公式が想定している方法ではない
非推奨な方法2
.swcrcを読み込むようにNext-SWCを改造したものを利用する
備考
- カスタムプラグインとNext.js組み込みプラグインが利用可能
- 現在、Next-SWCは.swcrcを読み込まないように、あえて設定されているため、不具合が生じる可能性が高い
- Next.js公式が想定している方法ではない
まとめ
以上の調査によりNext.jsを使っているかに関わらず、BabelからSWCに移行するには、Babelで使っているプラグインの移植が必要だということがわかりました。 Babel公式プラグインや有名なプラグインの多くはSWC公式やNext.js公式が移植版を提供しています。しかし、それ以外のBabelカスタムプラグインは自分たちで移植する必要があります。
例えば、Wantedlyでは独自に開発しているui-react.macroというBabelカスタムプラグインがあります。このプラグインをSWCまたはNext-SWCで使うためには、Rustで再実装する必要があります。
RustでのSWCカスタムプラグインは別の記事(プラグイン編)で解説します。