Open1

実直に React + TypeScript を構築する

やまじゅんやまじゅん

最初の一歩・Reactを素で構築

↓ webpack(とそのCLI)、開発サーバ、babel、react

yarn
yarn add --dev webpack webpack-cli webpack-dev-server html-webpack-plugin
yarn add --dev @babel/core @babel/runtime @babel/plugin-transform-runtime @babel/preset-env babel-loader @babel/preset-react
yarn add react react-dom

まずはTypeScriptなしで動かすまで

↓ この状態を目指すよ

/exampleApp
├── dist/
├── node_modules/
├── package-lock.json
├── package.json
├── src
│   ├── Hello.jsx
│   ├── app.jsx
│   └── index.html
└── webpack.config.js
/src/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>exampleApp</title>
</head>
<body>
    <noscript>
      <strong>javascriptを有効にしてください</strong>
    </noscript>
    <div id="app" />
</body>
</html>
/src/Hello.jsx
import React from 'react';

export const Hello = () => <h1>Hello</h1>;
/src/app.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import { Hello } from './Hello'

ReactDOM.render(
  <React.StrictMode>
    <Hello />
  </React.StrictMode>,
  document.getElementById('app')
);
/webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: path.resolve(__dirname, "src/app.jsx"),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "app-[hash].js",
  },
  resolve: {
    modules: [path.resolve(__dirname, "node_modules")],
    extensions: [".js", ".jsx"],
  },
  module: {
    rules: [
      {
        test: [/\.js$/, /\.jsx$/],
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env", "@babel/preset-react"],
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src/index.html"),
    }),
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
  },
};

↓ ビルドしてみましょう

$ npx webpack --config webpack.config.js

↓ 開発サーバを起動してみましょう

$ npx webpack serve --config webpack.config.js

開発サーバの停止はCtrl + cです

TypeScript化

TypeScript は、もはや無くてはならないものです。
型による静的チェックによって(客観的に間違ってると判断できる)誤りを随時発見でき、エディタの補完機能が利用できて生産性が向上します。学習コストもさほどではありません。

yarn add --dev @babel/preset-typescript typescript @types/react @types/react-dom

webpack.config.js を編集し、resolve.modulesmodule.rules 周辺を変更します。

/webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: path.resolve(__dirname, "src/app.jsx"),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "app-[hash].js",
  },
  resolve: {
    modules: [path.resolve(__dirname, "node_modules")],
-   extensions: [".js", ".jsx"],
+   extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      {
-       test: [/\.js$/, /\.jsx$/],
+	test: [/\.ts$/, /\.tsx$/],
        use: [
          {
            loader: "babel-loader",
            options: {
-             presets: ["@babel/preset-env", "@babel/preset-react"],
+	      presets: [
+               '@babel/preset-env',
+               '@babel/preset-react',
+               '@babel/preset-typescript',
+             ],
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src/index.html"),
    }),
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
  },
};

↓ 初期設定コマンドを叩き、tsconfig.json を作ります
tsconfig.json が作成されます。

$ npx tsc --init

↓ (私の)tsconfig.jsonはこんな感じにしています

tsconfig.json
{
  "compilerOptions": {
    "target": "esnext" /* 発行されたJavaScriptのJavaScript言語バージョンを設定し、互換性のあるライブラリ宣言を含めます */,
    "jsx": "react" /* 生成されるJSXコードを指定します */,
    "module": "commonjs" /* 生成されるモジュールコードを指定します */,
    "moduleResolution": "node" /* TypeScriptが特定のモジュール指定子からファイルを検索する方法を指定します */,
    "noEmit": true /* コンパイルからのファイルの発行を無効にする */,
    "isolatedModules": true /* 他のインポートに依存することなく、各ファイルを安全にトランスパイルできることを確認してください */,
    "allowSyntheticDefaultImports": true /* モジュールにデフォルトのエクスポートがない場合は、「import x from y」を許可します。 */,
    "esModuleInterop": true /* CommonJSモジュールのインポートのサポートを容易にするために、追加のJavaScriptを発行します。これにより、タイプの互換性のために `allowSyntheticDefaultImports`が有効になります */,
    "forceConsistentCasingInFileNames": true /* インポートのケーシングが正しいことを確認する */,
    "strict": true /* すべての厳密なタイプチェックオプションを有効にする */,
    "noImplicitAny": true /* 暗黙の `any`型を使用した式と宣言のエラーレポートを有効にします */,
    "strictNullChecks": true /* タイプチェックの際は、 `null`と` undefined`を考慮に入れてください */,
    "noUnusedLocals": true /* ローカル変数が読み取られない場合のエラーレポートを有効にします。 */,
    "noUnusedParameters": true /* 関数パラメータが読み取られないときにエラーを発生させる */,
    "noImplicitReturns": true /* 関数で明示的に返されないコードパスのエラーレポートを有効にします。 */,
    "skipLibCheck": true /* すべての.d.tsファイルのタイプチェックをスキップします。 */
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules", "src/__test__"]
}

続いて、JSX を TSX にリネームします

  • /src/Hello.jsx/src/Hello.tsx
  • /src/app.jsx/src/app.tsx

↓ ファイル名を変更したので webpack.config.json のエントリポイントを変更です

/webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
- entry: path.resolve(__dirname, "src/app.jsx"),
+ entry: path.resolve(__dirname, "src/app.tsx"),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "app-[hash].js",
  },
  resolve: {
    modules: [path.resolve(__dirname, "node_modules")],
    extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      {
	test: [/\.ts$/, /\.tsx$/],
        use: [
          {
            loader: "babel-loader",
            options: {
	      presets: [
                '@babel/preset-env',
                '@babel/preset-react',
                '@babel/preset-typescript',
              ],
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src/index.html"),
    }),
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
  },
};

npm スクリプトの変更

↓ 並列実行させたいので npm-run-all というパッケージを導入

$ npm i -D npm-run-all

↓ まずはこんな感じ

package.json
{
  ...
  "scripts": {
    "tsc": "tsc",
    "tsc:watch": "tsc -w",
    "webpack:build": "cross-env NODE_CONFIG_ENV=production webpack --config webpack.config.js",
    "webpack:start": "cross-env NODE_CONFIG_ENV=development webpack serve --hot --progress --config webpack.config.js",
    "build": "run-s tsc webpack:build",
    "start": "run-p tsc:watch webpack:start",
  }
}

↓ 登録したスクリプトを実行してみましょう

yarn start