React+TypeScriptの環境構築(create-react-appなし)

create-react-appを使用せずにReact+TypeScriptの環境構築について解説します。(個人開発の備忘録も兼ねているので、何か気づいたことがあれば教えていただけると嬉しいです。）

## create-react-appを使用しない理由
create-react-appを使用すると、簡単にReactのプロジェクトを作成することができるが、webpack（JSやCSSなどのリソースファイルを１つにまとめたり、JSXのような特殊な記法で書かれたファイルを変換するツール）の設定が隠蔽されているため、プロジェクトに応じてloaderやpluginを新たに読み込むことが難しくなります。

build周りの設定がブラックボックス化してしまうため、今回はcreate-react-appを使用せずに環境構築を行なっていきます。

##構成
最終的なフォルダとファイル構成は以下のとおり。

```
react_app/
  ├ node_modules/
  ├ dist/
       └ bundle.js
  ├ public/
       └ index.html
  ├ src/
       ├ App.tsx
       └ index.tsx
  ├ package-lock.json
  ├ package.json
  ├ tsconfig.json
  └ webpack.config.json
```

## 1. 準備
適当なディレクトリを作成し、必要なモジュールをインストール。

```Bash
#プロジェクトのフォルダを作成
$ mkdir react_app
$ cd react_app

#package.jsonを作成
$ npm init -y

#typescriptをインストール
$ npm i -D typescript

#webpackをインストール
$ npm i -D webpack webpack-cli webpack-dev-server

#loaderをインストール
$ npm i -D ts-loader

#pluginをインストール
$ npm i -D html-webpack-plugin

#React、ReactDOMをインストール
$ npm i react react-dom

#型定義ファイルをインストール
$ npm i -D @types/react @types/react-dom
```
- `typescript`（typescript本体）
- webpack
    - `webpack`（webpack本体）
    - `webpack-cli`（webpackコマンドを使うために必要なパッケージ）
    - `webpack-dev-server`（開発環境用のローカルサーバー）
- loader
    - `ts-loader`（webpackからtypescriptを読み込むために必要なローダー）
- plugin
    - `html-webpack-plugin`（webpackが生成したバンドルファイルを指定したhtmlに埋め込む）
- react
    - `react`（React本体）
    - `react-dom`（ReactのDOMライブラリ）
    - `@types/react`（reactの型定義）
    - `@types/react-dom`（react-domの型定義）

######補足
パッケージインストール時にオブション -D をつけると、package.jsonのdevDependenciesに記録される。
webpackやtypescriptなど開発専用のパッケージはdevDependenciesインストールする。

## 2.　package.jsonの編集
ルートディレクトリのpackage.jsonを以下のように編集。
script.testを削除して、script.start（開発環境用のローカルサーバー起動コマンド）とscirpt.build（webpack.config.jsonで設定した自前のbuildコマンド）を追加。

```diff:package.json
{
  "name": "portfolio",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
-   "test": "echo \"Error: no test specified\" && exit 1",
+   "build": "webpack",
+   "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/react": "^17.0.38",
    "@types/react-dom": "^17.0.11",
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.2.6",
    "typescript": "^4.5.4",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}
```

## 3. webpack.config.jsの作成
ルートディレクトリにwebpack.config.jsを作成。

```javascript:webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    //モードをdevelopment、production、noneから設定（必須）
    //development: 開発時のファイル出力モード（最適化より時間短縮、エラー表示を優先）
    //production: 本番時のファイル出力モード（最適化されて出力）
    mode: 'development',
    //メインとなるjsファイル(エントリーポイント)
    entry: './src/index.tsx',
    //ファイルの出力設定
    output: {
        path: path.join(__dirname, '/dist'), //出力先のディレクトリ（絶対パスで指定）
        filename: 'bundle.js' //出力ファイル名
    },
    //デバッグのためのSourceMap（ビルド前後の対応関係を記述したファイル）の出力設定
    devtool: 'inline-source-map',
    //対象のファイルを変換するためのloaderを設定
    module: {
        rules: [
            {
                test: /\.tsx?$/, //build対象（loaderを適用するファイル）を指定
                loader: 'ts-loader', //適用するloaderを指定
            }
        ]
    },
    //importの際に省略する対象の拡張子を配列で指定
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.json'] //指定されている拡張子のファイルはimportの際に拡張子を省略できる
    },
    //webpack-dev-serverの設定
    devServer: {
        static: path.join(__dirname, '/dist'), //表示する静的ファイル（HTML）の場所を指定
        open: true, //ブラウザを自動的に起動
        port: 3000 //ポート番号を指定（デフォルトのポート：8080）
    },
    //pluginの設定
    plugins: [
        new HtmlWebpackPlugin({ //webpackでbuildされたJSやCSSを表示するHTMLを自動的に生成するplugin
            template: './public/index.html', //テンプレートとして使用するHTMLファイルを指定
            filename: 'index.html' //生成するHTMLファイル名
        })
    ]
}
```

## 4. tsconfig.jsonの作成
以下のコマンドを実行して、TypeScriptの設定ファイルを作成します。

```Bash
$ tsc --init
```

作成されたtsconfig.jsonの内容を一部修正します。
(コメント文は省略）

```diff:tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
+   "jsx": "react",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
```

## 5. HTML
ルートディレクトリにpublicフォルダを作成して、その中にindex.htmlを作成

```html:public/index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>react_app</title>
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>
```
## 6. React App
ルートディレクトリにsrcフォルダを作成して、その中にindex.tsxとApp.tsxを作成

```react:src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(
    <App />,
    document.getElementById('app')
)
```

```react:src/App.tsx
import React from 'react'

const App = () => {
    return (
        <div>
            <h1>Hello React!</h1>
        </div>
    )
}

export default App
```

## 7. webpackの実行
package.jsonで設定したscriptを実行。
npm run build を実行し、webpackがdist/bundle.jsを生成。
npm run start を実行し、[http://localhost:3000](http://localhost:3000)にアクセスすると'Hello React!'と表示される。

```bash
$ npm run build #webpackの実行
$ npm run start #webpack-dev-serverの起動
```