なるべく簡単にReactを部分導入する


いわゆるレガシー環境では、SPAのフロントエンドでイケイケみたいなことはなく、LaravelやRubyOnRailsなどのMPAアプリケーションであることが多いと思います。
※決してMPAが良くないということではありません。近頃のSSRなどの流れを考えると、MPAの良さも十分あると思っています。

そういった環境ではReactではなく、jQueryなどを使いゴリゴリ書くこと多いです。
ただ最近の流行を見ていると、『宣言的UIが書きたい!!』『やっぱりReactが使いたい!!』と感じることは多々あります。

そこで今回は、レガシーな環境でもReactを利用できる方法を2つ紹介します。
また、本番環境を変えるのは大変&影響範囲が大きくなってしまうので、なるべく簡単に導入できる方法を紹介します。


## 1.CDNを使う

ReactはCDNで公開されているものを利用することができます。
公式のドキュメントに載っている方法ですが、簡単に導入できる反面、できないことも多いです。

ファイルをダウンロードして、CDNではなく、自社サーバに設置して利用も可能です。

```javascript
// 開発環境
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

// 本番環境
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
```

#### コード例

```HTML
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

<div id="root"></div>
```

```javascript
const root = document.querySelector("#root");
const App = ()=>{
  return (<div>Reactの出力</div>);
};
ReactDOM.render(<App />, root);
```

### メリット
**簡単にReactを導入することができる**
既存プロジェクトにscriptタグを足すだけでReactが書けるのは非常に魅力的です

### デメリット
**ライブラリが使いにくい**
CDNで公開されているライブラリを読み込み使うことになり、非常に不便＆使いにくい（他に方法があれば申し訳ないです）
cdnjsなどには多くのライブラリが公開されていますが、それでも全てのライブラリが公開されているわけではないので、利用できない場合は多いです。

**複雑な処理が書けない**
SPAのアプリケーションとして作っていないので、ルーティングなどの処理は難しい
ページを跨いだ状態の共有ができない（cookieを介してできないことはないですが、今回はReactの世界だけという意味で）

**TypeScriptが書けない**
やっぱりTypeScriptで書きたい...

## 2.　ローカルでビルドしたものを読み込む
この方法では、ローカルの開発環境にNode.jsの環境を用意し、そこでWebpackを用いてビルドします。出力されたJSファイルをScriptタグで読み込むことでReactを利用することができます。
これにより先ほどのデメリットであるライブラリが利用しにくいやTypeScriptで書けないなどの問題を解決することができます。

私が導入したときは、既存のコンテナ環境にプラスしてNode.jsでReact開発用のコンテナを作成しました。開発環境だけの変更なので、本番環境に影響を出すことなく修正を行うことができました。。

### 手順
#### 1.Docker環境を作る
以下のような形でDockerfileを作り、docker-compose.ymlで読み込みました。
既存プロジェクト内でJSなどのファイルの保存場所が決まっている場合は、そのディレクトリをマウントしてください。
（初回起動時はwebpackがないので、エラーが出ると思います。その場合は、ローカルでインストールしてから起動するか、CMDをコメントアウトして起動してインストールした後に再起動してください）
```Dockerfile
FROM node:17.4.0-bullseye-slim

WORKDIR /src

CMD ["npx", "webpack", "--watch"']
```

#### 2.Reactを書く
CDNの例と同じようにReactを記述します。

#### 3.設定をする
まずライブラリをインストールします。今回は以下のライブラリをインストールしました。

```json:package.json
{
  "name": "react-sample",
  "version": "1.0.0",
  "devDependencies": {
    "@babel/core": "^7.19.6",
    "@babel/preset-env": "^7.19.4",
    "@babel/preset-react": "^7.18.6",
    "@babel/register": "^7.18.9",
    "@types/react": "^18.0.23",
    "@types/react-dom": "^18.0.7",
    "babel-loader": "^8.2.5",
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.4.1",
    "typescript": "^4.8.4",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

```
また、TypeScriptで開発する場合は、tsconfig.jsonを作成しておきましょう。
```json:tsconfig.json
{
  "compilerOptions": {
    "allowJs": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "lib": ["dom", "es2017"],
    "module": "esnext",
    "moduleResolution": "node",
    "noEmit": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "esnext",
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./"
  }
}

```

最後にwebpackの設定をしていきます。以下にconfigの例を記載します。利用する場合は、適宜ファイルパスを変更してください。

**ポイントは３つです。**
- TypeScriptを使えるようにする
- 変更の監視は特定のディレクトリだけにする
    - watchFilesで監視するファイルを指定します
    - 必要のないファイルまで監視してしまうと重くなったり、意図しないビルドが発生してしまう可能性があるので（既存プロジェクトの場合は特に）、最低限のファイルを監視しています。
- 複数のファイルを生成する
    - SPAではないので、ページごとにビルドする必要があります
    - ここではentryとoutputの部分でそれを実現しています
    - ファイルを増やす場合はentryの部分に追加してください

```js:webpack.config.js
require('@babel/register');

const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');


const src  = path.resolve(__dirname, 'data/static/js/react');
const dist = path.resolve(__dirname, 'data/static/js/react/dist');

module.exports = {
  mode: 'development',
  entry: {
    sample: src +'/frontend/sample/index.jsx',
  },
  output: {
    path: dist,
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        use: [
            {
              loader: "babel-loader",
              options: {
                presets: [
                  "@babel/preset-env",
                  "@babel/react"
                ]
              }
            }
          ]        
      },
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: [
            {
                loader: "babel-loader",
                options: {
                    presets: [
                        "@babel/preset-env",
                        "@babel/react"
                    ]
                }
            },
            { loader: "ts-loader" }
        ],
    }
    ]
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx']
  },
  optimization: {
    minimizer: [new TerserPlugin({
      extractComments: false,
    })],
  },
  plugins: [],
  performance: {
    maxEntrypointSize: 500000,
    maxAssetSize: 500000,
  },
  watchOptions: {
    ignored: ['**/node_modules'],
  },
  devServer: {
    watchFiles: ["data/static/js/react/**"],
  },
}
```


#### 4.ビルドして表示を確認する
dockerを起動すると、webpackがウォッチモードで実行されるので、起動時と変更時にビルドが走ります。そして、webpackのoutputで指定したディレクトリにビルドされたJSファイルが出力されます。

既存のサイトでそのJSファイルを読み込んで画面表示を行いましょう。ホットリロード機能はないので、手動でリロードする必要があります。

### メリット
**CDN方法のデメリットが解決できる**
先ほどのデメリットであった、ライブラリやTypeScriptが簡単に利用できるようになります

### デメリット
**JSファイルのサイズが大きくなることがある**
規模によってはビルド後のJSファイルのサイズが大きくなり、パフォーマンスの低下が発生します。状況に合わせてReactのファイルだけはCDNから読み込むようにするなどしてください。


## まとめ
本投稿では、レガシー環境でよくあるMAPのフロントエンドサイトでReactを使う方法を2つ紹介させていただきました。
~~結局ここまでReactを書いてしまうと、これだけで満足できなくなって次はNext.jsでSSRをしたい...みたいになっていきます。~~

最初からフルリプレイスみたいなことはなかなか難しいので、これをきっかけにしてフロントエンドサーバがっつり入れて、モダンな環境に変えていく足掛かりにしたいですね！

## 参考文献

https://note.com/tabelog_frontend/n/n8d6734ddee33

https://www.webopixel.net/javascript/1642.html

https://qiita.com/tetsurotayama/items/a2e4edd5bc46bc592c32