create-react-appコマンド無しでReact + TypeScript環境をつくる

 create-react-appコマンド無しでcreate-react-appっぽいreact + typescriptの環境構築していきます。 今回、create-react-appぽいとは次のようなことを指します。

- とりあえずts,tsxが動く
- yarn buildでbuildファイルを生成する。
- yarn startでローカルサーバーが自動で起動する。
- ローカルサーバーが起動中、編集すると、自動でリロードされる(ホットリロード)

## 登場人物ややこしすぎ問題  
とにかく構築にいろいろなパターンが多い + デファクトがよくわからない。[^1]
本記事では**webpack + ts-loader**の構成で構築していこうと思います。(**babel-loaderを使わないパターンです**)
そのため、対象となる読者は以下のとおりです。 

```
・始めたてで、とにかく動かしてみたいだけ。
・細かいpolyfillは考えていない。
・作るものが小規模だ。(個人の練習アプリ程度) 
```  
##環境
環境は以下の通りです。yarn使います。

```
$ node -v
v13.11.0
$ yarn -v
1.22.4 
```  

## init
まずはじめにpackage.jsonを生成します。

```
$ yarn init -y                //-yで対話をスキップ
```  
##パッケージのインストール
次にwebpackともろもろのインストールを行います。

```
$ yarn add -D webpack@4.43.0 webpack-cli@3.3.12 webpack-dev-server@3.11.0 html-webpack-plugin typescript ts-loader
```  
**正常な動作を確認しているのは上記のバージョンのwebpack,webpack-cli,webpack-dev-serverのみです。バージョンを指定しない場合webpack v5系がインストールされるので注意してください。(2020年11/21日現在)**[^2]

 - **webpack**  　・・・・みんな大好きモジュールバンドラーの本体
 - **webpack-cli** ・・・・webpackコマンドがshellで使える様になる。package.jsonのscriptsにこのコマンドを割り当てることが多い  
 - **webpack-dev-server** ・・・・開発サーバー用  
 - **html-webpack-plugin** ・・・・必須ではないです。webpackがバンドルと一緒に.htmlも吐き出してくれます。自前でhtmlファイルを用意してもいいですが、使い方がとても簡単かつ楽なので筆者はいつもwebpackにまかせてます。 
 - **typescript** ・・・・いつものあいつ  
 - **ts-loader** ・・・・webpackがバンドルを作成するときにtsx—> jsx —> jsとコンパイルしてくれます。babel-loaderとごっちゃになってハゲそうになった。

## scriptsの設定
 先にpackage.jsonにscriptsを設定しておきましょう。

```
"scripts":{
   // dev-serverをdevelopmentモードで起動
   "start": "webpack-dev-server --config ./webpack.config.js --mode development”,   
   // productionモードでbuildファイルの出力
   "build":"webpack --mode production"
  } 
```  
## reactのインストール
 次にreactと型定義ファイルをインストールします。こちらの詳細は割愛します。

```
$ yarn add react react-dom
$ yarn add -D @types/react @types/react-dom
```  

ここから諸々のファイルを作成していきます。最終的なディレクトリ構成を載せておきます。

```
├── build 　　　　　　//buildディレクトリ
│   ├── bundle.js
│   └── index.html
├── package.json
├── src
│   ├── index.tsx　　//エントリーポイント
│   └── template   // htmlpluginのテンプレート
│       └── index.html       
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
```
## webpack.config.js
webpack.config.jsを作成します。

```
// webpack.config.js
const path = require("path");

//htmlプラグインのための呪文
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: {
    bundle: "./src/index.tsx", // エントリーファイルの位置
  },
  output: {
    filename: "[name].js",   // 今回はbundle.jsで吐き出される(entryで指定)
    path: path.join(__dirname, "build"),　// buildという名前のbuildディレクトリを吐き出す
    chunkFilename: "[name].js",//今回は触れません　
  },
  devServer: {
    contentBase: path.join(__dirname, "build"),
    compress: true,
    port: 8080,
    open: true,        //yarn start コマンドを入力すると自動でブラウザに遷移します。
    historyApiFallback: true,
  },
  module: {
    rules: [
      {
        test: /\.ts(x?)$/,    //.ts または.tsxに対して
        loader: "ts-loader",  // ts-loaderを使用
      },
    ],
  },
  resolve: {
    extensions: [".ts", ".js", ".tsx", ".jsx"],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: `${__dirname}/src/template/index.html`, //テンプレートファイルの場所
      filename: "index.html",                                                      //テンプレートファイルの名前
      inject: "body",                                                             //bodyタグの直前でwebpackのバンドルを埋め込み
    }),
  ],
};
```  
おまけ: 本当はwebpackのprodモードとdevモードの環境は個別に設定することが推奨されています。詳しくは[公式ドキュメント](https://webpack.js.org/guides/production/)を参照してください。今回はscriptsで無理やり分けてます。

## tsconfig.json
次にtsconfig.jsonを作成します。ts-loaderはこのファイルの設定に従います。
必ず "jsx":"react"を指定しましょう。
ほぼデフォルトのコピペです。細かいのはお好みで。  

```
// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "resolveJsonModule": true,
    /* Basic Options */
    "allowJs": false /* Allow javascript files to be compiled. */,
    "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
    "sourceMap": true /* Generates corresponding '.map' file. */,
    "outDir": "/build" /* Redirect output structure to the directory. */,
    "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
    "strictNullChecks": true /* Enable strict null checks. */,

    /* Additional Checks */
    "noUnusedLocals": true /* Report errors on unused locals. */,
    "noUnusedParameters": true /* Report errors on unused parameters. */,
    "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
    "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
    /* Module Resolution Options */
    "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
    relative to the 'baseUrl'. */
    structure of the project at runtime. */
    "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,

    /* Advanced Options */
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  },
  "include": ["src"]
}

```  
## 仕上げ
.tsxとhtmlのテンプレートを作成します。

```
// src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(<h1>hello</h1>, document.getElementById("root"));
```  

```
// src/template/index.html
// ! + tab　キーで作ってます。
// <div id = "root"></div>を忘れずに

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id='root'></div>
</body>

</html>
```  

ここで、

```
$ yarn start 
```
するとindex.tsxの内容が描画されると思います。  
ソースコードは[こちら](https://github.com/togami2864/react-ts_env)  
[^1]: ts-loaderでjsxまでコンパイル + 型チェックしてbabel-loaderでjsにコンパイルするのがいいのでしょうか？詳しい方いらっしゃったらご教示頂ければ幸いです。
[^2]: 現状webpack v5系だとちょくちょくcliやdev-serverがエラー起こします。ググると大体バージョン落としたら治るパターンが多いです。