React + TypeScript + Webpack を既存プロジェクト(ASP.NET MVC5)に導入してみた

# はじめに
　「JQueryもいいけれど、そろそろReactを使ってみたいな」
　そう思って挑戦してみたところ、なかなかに難儀でしたので、それをまとめてみます。「React + TypeScript + Webpack 」をASP.NET MVC5に適用する内容になっています。

　次にような方に役立つのではないでしょうか。

- JQueryは使ったことあるけれど、Reactを始めてみたい方
- １から作るのはサンプルやcreate-react-app をやってはみたものの、既存プロジェクトへの適用の仕方がよく分からない方

# 各パッケージの役割を整理

　正直なところフロントエンドがここまで複雑化しているのとは思いませんでした。
　たくさん登場人物がいて、さらに誰が何を担当しているのが分かりにくいです…。そもそもなんでフロントエンドの話に、サーバーサイドである、Node.js が出てくんねん！って感じですよ。過渡期なのでしょうけど、ちょっと魔境すぎません？

### 色々な登場人物が出てくる流れ
　大まかにこういう流れだと理解しています。

#### 1. React を使いたい
- やることは React.jsをインポートする。JQuery導入と同じ。怖くない。

#### 2. JSXが登場
- これまでのJavaScriptはHTMLに紐づくものだったけど、WEBサイト機能が複雑になってきたし機能単位でまとめた方がいいよね。JavaScript主体のJSXで書く方が便利。
- JSXは Babel でコンパイルしないと使えない。Babel 登場。
- Babelを使うのに Node.jsが必要。Node.jsも登場。

#### 3. TypeScriptが登場
- JavaScriptを直接書くより、最近のプログラミング言語っぽく書けるTypeScriptを使った方がいいよね。JSXじゃなくてTSXにしよう。
- TSXの導入に伴い Babel は必要なくなった。でも TSXファイルをコンパイルするのに Node.js が必要なので、こいつは残ります。

#### 4. webpackが登場
- 機能の拡大と共にJavaScriptファイルが沢山増えてしまった。沢山あると読み込みが遅くなる。そこでwebpackです。こいつは一括で取り込めるようにまとめたJavaScriptを作成できます。ついでに圧縮(miniファイル作成)もできるよ。
- TypeScriptのコンパイルを実行したり(コンパイルそのものは ts-loaderが行う)、どこにまとめたJavaScriptを置くかも担当します。
- Webpackを使うのにもやっぱりNode.jsが必要です。

#### 5. Reactを使う
- 4で出力した JavaScriptを読み込めば使用できます。

　要はブラウザのスクリプト言語としてはJavaScriptがデファクトスタンダード。でもJavaScriptは使いづらいから、それを何とかしようと色々している結果、今現在のような複雑なことになっているのだと思います。

# 前提

　既存プロジェクトとして、ここでは、ASP.NET MVC5とします。.NET Coreじゃなく.NET Framework 4.7.2 の方にしてみます。（その方が既存プロジェクトっぽいし、.NET Coreの方はテンプレートあるしね……）
　IDEは Visual Studio 2019 Community で確認していますが、Visual Studio 2017でも大丈夫でしょう。

## テンプレート通りに作成（既存プロジェクトのつもり）

![ASPNet.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/6070c2b9-5761-ec37-5132-9d9d0af8227c.png)
![MVC5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/110016d9-093f-10ac-2fae-83351e7d9fc4.png)
![開始.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/9aac45b7-05e8-dc04-f178-65ce9e6823d3.png)

# npmを導入

## npmとは
　各種パッケージを管理するものです。Visual Studioのデフォルトとしてある Nugetと同じような機能です。
　同じような機能を二つ使うのはややこしいですが、Nugetに無いので仕方がありません。フロントエンドのパッケージを管理するのが npm、サーバーサイドのパッケージを管理するのが、Nugetと分けるのがよさそうです。
（※デフォルトでは NugetでJQueryやBootstrapを管理しているので、後々削除する必要があります）

## 拡張機能のインストール
　Visual Studioではデフォルトでは npm を使えないので、拡張機能の管理を呼び出します。
![拡張機能開始.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/da37a492-d4d0-f9e0-78fd-358b6f7f61f4.png)

　ここでは、NPM Task Runner と Package Installerを入れておきます。
![NPM関連.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/b7eb2ad1-16ab-d2b2-2cbd-335304fba78a.png)

　WebPack Task Runner もあとで使うので入れておきます。
![webpackTaskRunner.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/5737aaa9-4a62-376d-e933-d36bff1e4e28.png)

　一度 Visual Studioを閉じてインストールを実行します。
![拡張機能インストール確認png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/8b4d974a-2af3-443b-3f92-1a00a438ff4d.png)

## npmでReactをインストール
　ソリューション エクスプローラー で右クリックをして、Quick Install Packagesを選択します。
![QuickInstallPackage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/c01c9caa-ac2b-d2a5-62d6-780490eaef89.png)

　npm を選び、react のバージョンを16.8.6（現時点の最新版）を選択し、Installを実行します。同じように react-domもインストールします。
![npmInstall.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/6f9193a9-fb87-c421-2635-339b4a533bcf.png)

　すると、プロジェクトの直下に packeage.json が作られているのが確認できます。これがパッケージを管理するファイルになります。プロジェクト管理にはなっていませんが、package-lock.jsonファイルと node_modulesフォルダも作成されていることが確認できます。node_modulesにパッケージの実体が存在します。

![NPMInstall後.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/b7381c9d-bcf1-b704-46b2-9b6840097cec.png)
（参考）
[package-lock.jsonについて知りたくても聞けなかったこと](https://qiita.com/yfujii1127/items/7ca887a45e0855917279)


# package.jsonの変更
　このまま必要なパッケージをnpmコマンドでインストールしていってもいいのですが、直接package.jsonを書き換えた方が早いのでそうします。

![packagejson書き換え.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/36665e91-3986-16db-48c5-39cce577901f.png)

```javascript:修正後のpackage.json
{
  "name": "myproject",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack",
    "watch": "webpack -w"
  },
  "devDependencies": {
    "ts-loader": "^5.4.3",
    "typescript": "^3.4.4",
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.1"
  },
  "dependencies": {
    "@types/react": "^16.8.14",
    "@types/react-dom": "^16.8.4",
    "react": "^16.8.6",
    "react-dom": "^16.8.6"
  },
  "private": true
}
```
　package.jsonの中身の詳細については下記ページが勉強になります。

- [webpack + TypeScript + Reactの最小構成](https://ics.media/entry/16329/#webpack-ts-react)
- [package.jsonの中身を理解する](https://qiita.com/dondoko-susumu/items/cf252bd6494412ed7847)

# 変更後のpackage.jsonのパッケージをインストール
　Visual Studio のメニューの 表示　-> その他のウィンドウ -> タスクランナー エクスプローラーを表示します。
![taskRunner.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/5f05b765-f67a-c963-29e3-cb0d36a7e3a6.png)

　Defaultsでインストールを実行します。
![taskRunner2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/d7f5237d-a5a2-beb6-d9e5-1c27611cce14.png)
　インストール終了。
![taskRunner3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/ae7e4965-6528-d8ca-0aa6-85f58d3ea4c0.png)

　インストールが成功すると、node_modulesフォルダ内のパッケージが増えていることが確認できます。
![npmのinstall後のnodemodule.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/ce6c3f30-183e-1d77-fb4c-aa578c802b87.png)

（注釈）ここまで「あれ？」と思った方もいると思います。package.jsonを作成して、直接書き換えることが出来るならば「Package Installer」は必要なさそうと思うかもしれません。確かにそうなのですが、今後追加でインストールしたいものが出来た時に便利です。

# TypeScriptの設定

　新しい項目の追加で、tsconfig.json を追加します。
![typescriptの設定ファイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/1448f9e8-1e47-2517-3fe1-a5fea8fc78ac.png)


　そして、tsconfig.json を次のように変更します。

```javascript
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",

    "jsx": "react",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "lib": [
      "es2019",
      "dom"
    ]
  }
}

```
　tsconfig.json の中身の詳細については package.json と同じく下記ページを参考にしています。

- [webpack + TypeScript + Reactの最小構成](https://ics.media/entry/16329/#webpack-ts-react)

# webpackの設定

　新しい項目の追加で、webpack.config.js を追加します。

![webpackの設定ファイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/87368d4c-4e22-056e-72d6-c5a0b2aab752.png)

　そして、webpack.config.js を次のように変更します。
　ここでは「entry」に設定されている "./src/main.tsx"のファイルを ./dist/main.js として変換すると設定しています。

```javascript:

module.exports = {
    // モード値を production に設定すると最適化された状態で、
    // development に設定するとソースマップ有効でJSファイルが出力される
    mode: "development",

    entry: "./src/main.tsx",

    // ファイルの出力設定
    output: {
        //  出力ファイルのディレクトリ名
        path: `${__dirname}/dist`,
        // 出力ファイル名
        filename: "main.js"
    },

    module: {
        rules: [
            {
                // 拡張子 .ts もしくは .tsx の場合
                test: /\.tsx?$/,
                // TypeScript をコンパイルする
                use: "ts-loader"
            }
        ]
    },
    // import 文で .ts ファイルを解決するため
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".json"]
    }
};

```

　webpack.config.js の中身についてもこちらを参考に（というかコピー）しています。

- [webpack + TypeScript + Reactの最小構成](https://ics.media/entry/16329/#webpack-ts-react)

# TSXファイルの追加と変更
　新しい項目の追加で、TypeScript JSXファイルを追加します。場所は上記 webpack.config.js で指定した./src/main.tsxです。

![tsxの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/e6cdefa7-7dc9-e400-e81b-dcd0e679c474.png)

　内容は create-react-app のHello Worldみたくしてみます。

```javascript:main.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

```
　同じくTypeScript JSXファイルを./src/App.tsx に追加します。

```HTML:App.tsx
import React from 'react';

const App: React.FC = () => {
    return (
        <div className="App">
            <header className="App-header">
                <p>
                    Hello, World!!
                </p>
                <a
                    className="App-link"
                    href="https://reactjs.org"
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    Learn React
                </a>
            </header>
        </div>
    );
}

export default App;

```
# WebPack Task Runnerでコンパイル

　WebPack Task Runnerを使って、TypeScriptのコンパイルを実行します。
![webpackTaskRunner実行.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/9acc25a0-2345-921b-b52d-034c8ba9052d.png)

実行結果
![webpackTaskRunner実行結果.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/01b751d1-e0d2-0a40-b594-042e6df4f33f.png)

　コンパイルが無事に実行されると　./dist/main.js が作成されます。
![distが出来る.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/9c464a6b-ef93-d5c8-4b61-1ae69d4875ef.png)

　以後、TSX(TypeSciprt) が変更されるたびにコンパイルするのは面倒なので、ビルドする前にWebPack Task Runnerでコンパイルが走るようにバインドしておきます。
![バインド設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/7291a75a-9a52-d9d4-0a85-e4b3ef6bbc1f.png)


# main.jsをHTML側で呼び出す
　main.jsを使うために、適当な index.cshtml を変更します。

```html:index.cshtmlを変更

@{
    ViewBag.Title = "Home Page";
}

<div id="root"></div>
<script src="~/dist/main.js"></script>

```
# ビルドして動作確認
　ビルドして、Hello World! が確認できれば無事に React が使えていることになります。

![動いた.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430322/b597bb87-0a2b-af34-7339-f8890caee2cc.png)

# おわりに
　登場人物がたくさん出てきて混乱してしまいますが、一つ一つ誰が何をやっているかを理解していければ大丈夫だと思います。色々やってますけど、最終的にはJavaScriptになるというだけです。
　逆に理解しないまま「とりあえずサンプルあるし～」と進めていくと、上手く行っている時はいいのですが、トラブル発生時にどこに問題が発生しているのか分からないことになります。というか、なった。

# 参考資料
- [Node.jsもES2015も使わないReact入門](https://qiita.com/ShinyaKato/items/98be579f648d9ca0674a)
- [最新版TypeScript+webpack 4の環境構築まとめ（React, Vue.js, Three.jsのサンプル付き）](https://ics.media/entry/16329/)
- [Visual Studio 2017でMVCプロジェクトにnpmを導入する](https://qiita.com/akihiro-nakano/items/50287bb644f3655ed37c)
- [ASP.Net MVC5 + TypeScript + React + Webpack](https://qiita.com/alfas/items/12466afeafce8576814f)
- [Node.jsはなぜインストールする必要があるの？](https://teratail.com/questions/41145)
- [package.jsonの中身を理解する](https://qiita.com/dondoko-susumu/items/cf252bd6494412ed7847)
- [package-lock.jsonについて知りたくても聞けなかったこと](https://qiita.com/yfujii1127/items/7ca887a45e0855917279)