LEFログ

:smile

Rails 7 で React & TypeScript を導入する方法(tsx, esbuild, jsbundling-rails)

※この記事では「新しくRails 7 アプリを作成するときのこと」を想定していますが、Rails 6を7に上げるときにもそれなりに役立つ情報だと思います!

結論

rails newのときにesbuildを指定してあげれば、あとはJavaScriptファイルや.jsxファイルを、.tsxにしてあげるだけでOK。

型チェックや構文チェックを導入するには少し注意が必要。

概要

現在、Rails 7を使ってフロントエンドを構築する方法には、大きく分けて3種類あります。

  • importmapsを使って、素のJavaScriptを使ってフロントエンドを作る
  • esbuildを使って、JSXファイルを使ってフロントエンドを作る
  • Rails 7をAPIモードにして、フロントエンドはNextjsで作る

自分はこの2番めの「esbuildを使ってフロントエンドを作る」を選択しました。理由は、Railsのslim上にReactを書きたかったからです。

ただ、色々調べてみても、肝心なことが分かりませんでした。それは……

Rails 7でTypeScript(TSX)が使えるのかどうか

ということです。

ネット上を調べまくった結果、次の記事を見つけました。

Does importmap-rails not work with a Rails app that uses TypeScript? · Issue #124 · rails/importmap-rails

dhh commented on Apr 30, 2022

Correct, import map serves the JavaScript directly without compilation. Typescript is compiled. So if you want TS, you'll need to use jsbundling-rails. Or you could of course also just switch from TS to regular JavaScript 👍

Railsの生みの親であるDHHさんが、jsbuildling-railsを使えばTypeScriptを使えるよ、と言っていました。

そのため、TypeScriptが使えることは分かったのですが、TSXが使えるかはちょっと判然としませんでしたし、どのようにセットアップをすれば良いのかが曖昧でした。

今回、試行錯誤した結果、TSXRails 7上で動かす方法を見つけたので、その手順を解説していきたいと思います。

歴史的経緯

その前に、まずはRails 7におけるJavaScriptとの関係性について、歴史的経緯を書きたいと思います。背景を理解しておくことで、この後に載っている具体的手順についても理解が容易になると思います。

(※分かりやすさのため、正確さに欠けている部分もありますのでご注意ください。もし明らかな誤りがありましたらお気軽にご指摘コメントをくださると嬉しいです🙏)

はじめに、2つの言葉について解説します。

  • トランスパイル(コンパイル):JavaScriptじゃないものをJavaScriptに変換する
  • バンドリング:色んなプログラム(モジュール)をくっつけて、JavaScriptが動くようにする

例えば、JSXというファイルは、トランスパイルをすることで素のJavaScriptに変換して、ブラウザ上で動くようになっています。そのため、JSXがそのままブラウザ上で動いているわけではありません。

また、トランスパイルするだけではなく、ファイルを結合したり、ラップしたり、色んなことをおこなう必要があります。そのため、バンドリングすることで色んなプログラミングをくっつけて動かすようにする必要があります。

第 16 章 JavaScript のモジュールシステム | frontend-training

トランスパイルをしてくれるツールとして有名なのがbabelで、バンドリングしてくれるツールとして有名なのがwebpackです。これらについては、Google検索すると解説記事がたくさん出てくると思います。

また、ここでloader(ローダー)という概念も出てきます。これは、バンドルする前にトランスパイルすること(JSじゃないものをJSにすること)を可能にします。

全Rubyistに今すぐ伝えたいwebpackとwebpacker - Qiita

Rails 6では、webpackerというものが、JavaScript関係の処理を全部おこなってくれていました。

webpackerはRubyのgemです。Railsでもwebpackが楽に使えるように作られました。

しかし、webpackerには色々な問題がありました。

代表的なのは次の2つです

  • 動作が遅い
  • 環境構築が大変で、エラーが出やすい

自分も1年以上前にRailsチュートリアルを学習していたとき、webpacker周りの環境構築が大変でかなり苦労しました。解決方法としては、nodeのバージョンを下げる必要がありました(他にも各種の設定を変更する必要もあります)。

後に知ったのですが、ベテランの方でもwebpacker周りでは苦労されているようでした。そのため、特に初学者にとってはRails 6を学習するうえでの大きな障壁になっていたのは間違いありません。実際、2020年から2022年においては、初学者の方はRails 5で学習に取り組んでいる事例が多かったように見受けられます。

また、webpackerには他に問題がありました。

Rails7がもつフロントエンドへの「答え」

つまり、「ローカルからではなくネット上から必要な機能を読み込めばいいじゃん」というのがDHHの考えらしいです。

補足をすると、ES moduleという仕組みは、ネットワーク経由で読み込むことができます。そして、それをそのままブラウザ上で動かすことができます。

importmapという仕組みでは、JavaScriptimport文を使って、簡単にURLを読み込むことができるようになります。この際、実際のURLと、import AAA from BBBfrom以下の部分(BBB)を結びつける(マッピング)しているので、importmapという名前がつけられています。

ネット上からファイルを読み込んでいるため、バンドリングが必要なくなるというメリットもあります。

Rails 7.0 で標準になった importmap-rails とは何なのか?

しかし、ここで思い出してほしいのが、トランスパイルの問題は解決していないということです。

例えば、JSXやTSXでファイルを作ってしまうと、このままだとトランスパイルすることができません。

importmapでは、素のJavaScriptを書くことで、トランスパイルをしないようにするというアプローチを取っています。そしてhtmというライブラリを使うことで、JSXのような形でJavaScriptを書けるようにしています。

トランスパイルをしないようにしているのは、Railsの動作を速く・シンプルにしたいというDHHの思想が関係しています。

RailsとReactを利用するならばimportmap-railsを避けた方が良さそう | 人と情報

※この記事でjsbuilding-rails(じぇいえす・びるでぃんぐ・れいるず)と書かれていますが、正確にはjsbundling-rails(じぇいえす・ばんどりんぐ・れいるず)です。ご注意ください。

しかし、いくらJSXに近い書き心地を実現したからと言って、それはJSXではありません(もちろん、TypeScriptやTSXもimportmapでは使えません)。そのため、本物のJSX, TSXでReactを書きたい場合には、別の方法を取る必要があります。

ここで出てくるのが、jsbundling-railsというトランスパイルしてくれるビルドツールです。これを使うことで、esbuildやwebpackなどのビルド環境を選択することができます。

つまり、webpackerの代わりに導入されたgemがjsbundling-railsで、これを使うことでesbuildが使えるようになるということです。

そして、esbuildがトランスパイルしてくれるので、JSXもTSXも無事に動くようになります。

それでは、次にTSXを動かす具体的手順について見ていきましょう。

具体的手順

tsxを動かすまで

まずは、

gem install rails

で最新版のRails 7をインストールします。

gem install bundler

でbundlerを最新にします。

最新にしないとエラーが発生する場合があります。最新にしたくない場合は、エラーの出ないバージョンを指定すると良さそうです。

rails new hello_app -d postgresql --css=tailwind --javascript=esbuild

これで、DBにPostgres、CSSにTailwinds CSS、ViewにTSXを使える状態でセットアップできます(hello_appにはアプリの名前を入れてください)。

そうすると、package.jsonはデフォルトでこうなっているはずです。

  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets",
    "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify"
  }

"build": "esbuild app/javascript/*.*この部分が凄く重要です。

これは、app/javascript/以下にあるjsxファイルやtsxファイルをトランスパイルしてくれるよ、ということを明示的に示してくれています(*.*は全てのファイルを表している)。

だから、app/javascript/application.jsなどを、application.tsxに書き換えても、問題なく動作します。

yarn add react react-dom

このyarnコマンドでreactを使う前準備をします。

rails g controller home index

reactを描写するためのcontrollerを設置します。

tailwind.config.jsでslimやtsxを読み込むための設定もします

以下のコードをcontent: []内に追記

    './app/views/**/*.html.slim',
    './app/javascript/**/*.tsx'

あとは、Rails内のファイルを色々書き換えてあげれば大丈夫です。

こちらの記事を参考にして、viewへとReactが表示されるようにしましょう。

Railsにesbuildを使ってReactを導入する(Dockerで構築)

この記事の、「esbuildによるJavaScript編集方法(React導入)」を参考にしてファイルを書き換えてあげると、Reactの動作確認をしやすいと思います(docker-compose run --rm webについては、今回はDockerを使わないので無視してください)。

https://zenn.dev/naoki0722/articles/272ef57c6dafba#esbuild%E3%81%AB%E3%82%88%E3%82%8Bjavascript%E7%B7%A8%E9%9B%86%E6%96%B9%E6%B3%95(react%E5%B0%8E%E5%85%A5)

application.jsxとなっている箇所は、application.tsxにしても問題なく動きます。

最後に

./bin/dev

をおこないます。

これはesbuildを使っているときのRails起動方法です。

rails sしてもReactは動かないのでご注意ください。

実際の画面

この画面が出たら、Create databaseをクリックしてあげればOKです

慌てずに、画面上のCreate databaseをクリックしてあげましょう。

Rails 7が起動します。6より速いです。

TSXでReactを描写できます

型チェック、構文チェックをおこないたいとき

esbuildには型チェック機能がありません。

esbuildはTypeScriptの型チェックをしない - Minerva

esbuildはスピードを優先するため、IDEなどでも可能な型チェックをしない。そのため、コミットやビルドなどの節目で、noEmitオプション付きでtscコマンドを実行したほうがよい。

そのため、型チェックやLinterを使いたいときには、ちょっとした工夫が必要です。

具体的には、以下のようなコマンドを叩きます。

まずは型チェックの実現方法から説明致します。

npm install --save-dev typescript @types/node
// tsconfig.jsonを生成してくれます
npx tsc --init
// オプション付けるときはこんな感じで
npx tsc --init --project tsconfig.json --noEmit --jsx react-jsx
npx tsc --noEmit
npm i --save-dev @types/react

tsconfig.json"target": "es2016""es2022"にしておくと良いでしょう。

これでnpx tsc --noEmitコマンドで型チェックができるようになります。

次に、ESLintとPrettierの導入方法を説明します。

npm init @eslint/config
npm install --save-dev --save-exact prettier
npm install --save-dev eslint-config-prettier
// .eslintrcファイルを編集する

eslint-config-prettierはLinterの競合を防ぐためのものです。

詳細は→ prettier/eslint-config-prettier: Turns off all rules that are unnecessary or might conflict with Prettier.

これで各種コマンドでeslintとprettierを使えるようになります。

注意して頂きたいのは、ここではnpmを使うことです。

yarnを使ってしまうと、チェック機能に関してはうまく動かないときがあります。

このページ下部の参考URLの方法を一通り試してみてもダメでした。最新のRails7では動かないのかもしれません。そのため、Lint周りについてはnpmのほうに入れておいたほうが設定も複雑にならず、良さそうです。

そのためチェッカーについては、npm側にインストールしておいて、npm側からファイルをチェックさせる方法を自分は取りました。

  • Rails7に組み込むライブラリ→yarnを使う
  • Rails7のファイルをチェックするライブラリ→npmを使う

このように責務を分けることで、エラーを予防することができます。

yarnに統一する方法について

現在、上記の理由によりなぜかyarnでチェックができない状態になっています。これは、自分の環境構築方法がうまく行っていない可能性も高いです。
特に、package.jsontsconfig.json周りでyarnがうまく動いていない気がします。

本来であればyarnだけで動くようにしたほうが、特に巨大なプロジェクトでは望ましいでしょう。デプロイ時にエラーが出てしまう可能性もあります。

package-lock.json と yarn.lock を両方コミットするとデプロイ時にエラーが出るので気をつけよう

なので、yarnだけでうまく設定を整えることができたら、後日追記致します。よろしくお願いします🙏

※もし、ここをこう設定すればyarnだけでも問題なく動くよ~という情報がありましたら、お気軽にコメントください。

重要な追記(2023-02-25):yarnで設定を統一する方法

yarnで設定を統一する方法をやっと見つけられたので、追記致します。

まず、型チェックについてはこのようなコマンドを打って設定します。

yarn add -D typescript ts-node @types/node @types/react
yarn run tsc --init --project tsconfig.json --noEmit --jsx react-jsx
// tsconfig.json内のtargetを"es2022"にする
yarn run tsc --noEmit

型チェックについては、yarn run tsc --noEmitで出来るようになります。

次は、eslintとprettierについて見ていきます。

yarn add eslint --dev
yarn create @eslint/config
yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
yarn add --dev --exact prettier
echo {}> .prettierrc.json
yarn --dev add eslint-config-prettier
// eslintrc.jsonを編集する→"extends"の最後に"prettier"を追加
// eslintrc.jsonに"settings"の項目を追加する
yarn eslint ./app/javascript
yarn prettier --check ./app/javascript 

これらのコマンドを打つことで、最後の2行のコードでyarnでeslintとprettierでチェックすることが出来るようになります。

設定ファイルの変更箇所などは、次の見出しの参考URLをご参考ください。

ちなみに、package.jsonが既にあって、node_modulesが無いときは、yarnコマンドを叩いてあげると自動的に作成されるはずです。
yarn init -yは、package.jsonがない場合に使うコマンドですが、rails newをした段階でおそらく作られているはずです。

yarnに設定を統一する上で参考にしたURL

これを設定してあげると、細かなエラーも消えます。

  "settings": {
    "react": {
      "version": "detect"
    }
  },

対話形式でeslintを設定しているので、

npm install eslint eslint-plugin-react --save-dev
yarn add eslint eslint-plugin-react -D

は多分おこなわなくて大丈夫なはずです。

npmでしかインストール方法が記述されていない公式ドキュメントが多くて、そこをどうやってyarnに読み替えるかが、一番難しかったポイントでした……💧

サンプルリポジトリ

完成形のコードを見たい方のために、サンプルリポジトリをご用意しました。

もし実例を見たい方はご参考ください。

github.com

https://github.com/lef237/React-on-Rails7-by-TypeScript

謝辞

FjordBootCamp 内の質問・雑談タイムで cafedomancer さんからご助言を頂き、セットアップまでの道筋を確認することができました!✨

この場を借りて改めてお礼を申し上げます。ありがとうございました🙏

参考URL

※他にも参考URLが増えましたら追記致します。

追記:yarn run tsc --init --project tsconfig.json --noEmit --jsx react-jsxについて(2023-03-18)

以前のブログ記事では、

npx tsc --init --project tsconfig.json --noEmit --jsx react
yarn run tsc --init --project tsconfig.json --noEmit --jsx react

このように書いていました。

これでも問題はないのですが、処理を速くするために少しだけ修正しました。

npx tsc --init --project tsconfig.json --noEmit --jsx react-jsx
yarn run tsc --init --project tsconfig.json --noEmit --jsx react-jsx

このように、--jsxの後の引数にreact-jsxを使ってコマンドを実行すると、React17で新しく追加された処理の早い変換方法が適用されます。

もし、既にyarn run tsc --init --project tsconfig.json --noEmit --jsx reactコマンドを打っていても、問題はありません。ご安心ください。
tsconfig.jsonファイルを以下のように書き換えれば、自動的に新しい変換方式が適用されます。

    // 「"jsx": "react",」をこのように書き換えてあげるだけでOK!  
    "jsx": "react-jsx",    

参考URL