🙄

Rails+Vite+typescriptで動いてる環境にReactを追加する。

2023/11/28に公開

概要

Rails+Viteの環境にあとからReactを追加しました.プラグインはVite4.0以降で導入された@vitejs/plugin-react-swcを使用します。コンパイルにswcを使っていて、babelベースの@vitejs/plugin-reactよりも「単一スレッドでBabelより20倍高速であり、4コアで70倍高速です。」だそうです。

バージョンはこんな感じです。

Rails: 7.1.1
vite_rails: 3.0.17
typescript: 5.2.2
vite: 4.5.0
@vitejs/plugin-react-swc: 3.5.0
React: 18.2.38

インストール

yarn add react react-dom @vitejs/plugin-react-swc

余談ですが、bin/vite buildで書き出されるpublic/viteディレクトリはgitignoreだから、公開時にサーバーサイドでbin/vite buildをすることになります。そうすると全てのモジュールがサーバーサイドで必要になるのでどんな環境でも全てインストールされるように全部dependenciesに入れてます。どうするのが正しいのか自信はありませんが。

viteの設定

// vite.config.ts
import { defineConfig } from 'vite';
import RubyPlugin from 'vite-plugin-ruby';
import react from '@vitejs/plugin-react-swc';

export default defineConfig({
  plugins: [RubyPlugin(), react()]
});

@react-refreshの設定

この辺で指摘されてますが、@react-refreshのランタイムを手動で設定する必要があるらしく、vite-rubyでそのスクリプトが提供されています。

vite_react_refresh_tagapplecation.html.erbに書いてやればOKです。

<!doctype html>
<html>
  <head>
    ...
    <%= vite_client_tag %>
    <%= vite_react_refresh_tag %>
    ...
  </head>

この設定を忘れると下記のようなエラーが出ます。

React refresh preamble was not loaded. Something is wrong.

このエラーメッセージで別のものが色々引っかかるので深みにハマりました。ちなみに、タグの代わりに本家の指摘通り下記のようなタグを入れると・・・

<%= vite_client_tag %>
<script type="module">
  import RefreshRuntime from 'http://localhost:3036/vite-dev/@react-refresh'
  RefreshRuntime.injectIntoGlobalHook(window)
  window.$RefreshReg$ = () => {}
  window.$RefreshSig$ = () => (type) => type
  window.__vite_plugin_react_preamble_installed__ = true
</script>

下記のエラーができます。

Error: React refresh runtime was loaded twice. Maybe you forgot the base path?

読んでないのに2度読まれてると・・・仕組みは追わなかったので理屈は分かりませが、このせいで「既にランタイムの設定はできている」と思い込みさらに時間を浪費しました。

typescript

import React from 'reactの省略

React17以降はimport React from 'react';を省略できますが、typescriptの警告が出るのでtsconfig.jsonに設定を追加します。

{
  "compilerOptions": {
    ...
    "jsx": "react",
    "allowUmdGlobalAccess": true,
    ...
  }
}

@rails/ujs

今回の環境では@hotwired/turboをOFFにしたかったので、link_toでdeleteメソッドを実現するために@rails/ujsを使用していますがtypeを認識しません。

Could not find a declaration file for module '@rails/ujs'. '...rails-ujs.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/rails__ujs` if it exists or add a new declaration (.d.ts) file containing `declare module '@rails/ujs';`ts(7016)

@types/rails__ujsはあるのですが、バージョンが6系までしかなく認識しなかったのでrails-ujs.d.tsを自分で書きました。

declare const Rails: {
  start(): void;
};

declare module '@rails/ujs' {
  export default Rails;
}

7系が出たらそちらを使うのが良いかと思います。

Discussion