togami2864

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

2020-11-17

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

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


登場人物ややこしすぎ問題

記事を書いてる途中にぶち当たったのですがとにかく構築にいろいろなパターンが多い。デファクトがよくわからない。
本記事ではwebpack + ts-loader の構成で構築していこうと思います。(babel-loaderを使わないパターンです)
そのため、対象となる読者は以下のとおりです。 

・始めたてで、とにかく動かしてみたいだけ。
・細かいpolyfillは考えていない。
・作るものが小規模だ。(個人のTODOアプリ程度) 


環境

環境は以下の通りです。yarn使います。

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


package.json

まずはじめに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/17日現在)

  • webpack  ・・・・みんな大好きモジュールバンドラーの本体
  • webpack-cli ・・・・webpackコマンドがshellで使える様になる。package.jsonのscriptsにこのコマンドを割り当てることが多い
  • webpack-dev-server ・・・・開発サーバー用
  • html-webpack-plugin ・・・・必須ではないです。webpackがバンドルと一緒に.htmlも吐き出してくれます。(<div id=“root"></div>が書いてある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モードの環境は個別に設定することが推奨されています。詳しくは公式ドキュメントを参照してください。今回は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とindex.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 


すると.tsxの内容が表示されると思います。
ソースコードはこちら