Webpack5 + ReScript + TypeScript + React で Web アプリケーションを作るときのテンプレートを作りました。
https://github.com/anyakichi/webapp-template
だいたい React アプリを作るときは create-react-app とかから始めると思うのですが、これで作った環境は何がどうなっているのかよくわからないし、個別のカスタマイズがやりにくいという欠点があります。eject したときに生成される webpack の config もやたらでかいし、本質的に何が自分に必要で何が不要なのかわかりづらい。
ということで最小構成・必要十分的なものを目指して自分で作ってみました(意外とまともなテンプレートが見つからなかったという話でもある)。あと(しばらくブランクがあるが)OCaml 使い的には ReScript 環境もちゃんと整えるというのがサブテーマ。
一応要件としては以下を想定しています。
- ReScript, TypeScript どっちでもいけること。
- HMR がちゃんと動くこと。
- Production 用には minimize した JS/CSS が生成できること。
CSS をどうするか
CSS は css-loader と mini-css-extract-plugin (と css-minimizer-webpack-plugin)を使うことにしました。Webpack を使って環境を作るときには mini-css-extract-plugin ではなく style-loader が使う例が多いのですが、Production 用に minimize するときには mini-css-extract-plugin を使うように Webpack の公式でも紹介されています。
https://webpack.js.org/plugins/mini-css-extract-plugin/#minimizing-for-production
また HMR の都合で Development のときには style-loader、Production のときには mini-css-extract-plugin を使うという棲み分けをすることもあるようです。
ただ、試してみたところ mini-css-extract-plugin でも HMR できてそうだったので、だったら style-loader はいらないだろいうということでこうなりました。
sass-loader とかが必要な人は適宜追加していただければ良いと思います。
TypeScript をどうするか
まず選択肢が 2 つあります。babel-loader でやるか ts-loader でやるか。
ts-loader は HMR に対応しておらず、第一選択肢は babel-loader になります。実際 create-react-app でも babel-loader で TypeScript のトランスパイルをしているようです。
ただ、babel-loader を使って実際に React を動かそうとすると “React is not defined” というエラーが出るのをどうにも回避できず、またプラグインをいろいろ追加しないとビルドできないことが多々あり、今回のコンセプトとは裏腹に複雑になりそうな印象でした。babel-loader で頑張る路線で行くなら、create-react-app をそのまま使ったほうが良いと思います。
じゃあどうするのかというと、ts-loader でトランスパイルしてしまって、これを babel-loader に食わせる。実際これが HMR 用の react-refresh-webpack-plugin でも例示されている方法でもあり、現状は一番安定しているのではないかと思います。
https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/main/examples/typescript-without-babel
いっそ全部 ts-loader でやってしまった方が話が簡単なので、.js と .jsx も ts-loader でトランスパイルするようにしています。
ReScript の導入
たまたま私が TypeScript である程度できてしまったものを ReScript に移行しようとしていたのでハイブリッド環境を作ることになっているのですが、genType を使って TypeScript との相互運用性を高める設定にしてあります。
サンプルプログラムは ReScript (.res) ではなく ReasonML (.re) で入れてありますが、個人的に ReScript はちょっと実運用に入れるにはまだ早いかなあという印象は持っているのでこうなっています。
ReasonML は JavaScript と OCaml の中間を取ったような中途半端なな文法の言語にはなっていますが、どちらかというと OCaml への未練が残っているというか、最終的に JavaScript になるという前提で言うと不便な部分が多いという印象です。ReScript は未練を振り切って JS 側に寄せた、という感じで個人的にはこれは妥当だと感じています。
じゃあ ReScript で書くのがいいのかというとそれは別の話で、私はコアは OCaml で書いて、JSX を含めた glue 部分を ReScript で書くようなモデルにすると思います。その上で、glue は JS 寄りのほうが都合が良いという考えです。
HMR 対応
ほとんど前段で書いてしまいましたが、HMR 対応もできた環境になっています。微妙なハマりどころで webpack の config の entry で index.tsx とかを明示的に指定しておかないとビルド・表示まではできるのに HMR が動かないというのはあったのですが、まあそれくらいです。
個人的に OAuth2 認証でトークンは揮発性(WebStorage などには保存しない)というアプリを持っているのですが、HMR じゃなくてリロードが入ると認証からやり直しになってしまって非常に開発効率が悪いんですよね。
react-refresh-webpack-plugin はとてもいい感じに使えています。
まとめ
ということでシンプルな Webpack 環境のたたき台として参考にしていただければと思います。
作ってはみたものの、まだ自分ですら十分に運用できておらず、何か足りないところもあるかもしれません。気づいた点などがあれば適宜更新していくつもりです。