2021-09-24

0からReact+TSの開発環境を構築する

フロントエンド開発の環境構築は大変ややこしいことで有名ですが、とりあえず一回やっておけば最低限流れくらいは理解できるだろうということで、今回はReactとTypescript(以下TS)の開発環境を0からやってみる

Node.jsのインストール

まずはNode.jsがなければ何も始まらないので、nodenvやnvmなどでインストールする

nodenv install 14.17.6

npmプロジェクトの作成

以下のコマンドでpackage.jsonを作成する
(途中の質問は全部エンター)

npm init

すると以下のような内容でpackage.jsonが生成される

{
  "name": "react-ts-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^4.4.3"
  }
}

TSの導入

TSはdev向けなので -D オプションをつける

npm install -D typescript

これで npx tsc コマンドが使えるようになるので、設定ファイルを生成する

npx tsc --init

これで以下のような設定ファイルが生成される
ほとんどコメントアウトされているので、コメントアウトされていない箇所だけを表示している

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true, 
    "skipLibCheck": true,
    "jsx": "react"
  }
}

今回はReactを使うので、jsxオプションにreactを指定しているところだけが初期値との違い
https://qiita.com/ryokkkke/items/390647a7c26933940470#jsx

TSはトランスパイラとしての側面もあるため、Babelと同じようにレガシーなブラウザに向けた変換を行う役割も担える
従って今回はBabelを導入せず、コンパイルはTSに任せる(ここではES5向けに変換されるように設定している)

ただし、近年状況が改善してきたとはいえ、Babelのように preset-env.browserslistrc による対象ブラウザの細かな指定までは行うことができないので
https://kangax.github.io/compat-table/es6/ などを参考に target の設定を行うと良い

...ES5への変換はともかく、ポリフィルとかまで考えるとやっぱりBabel入れて併用するのがいいんだろうか
IEサポートしてなくても、ポリフィルは必要になる可能性は十分にある気がする

webpackの導入

webpack本体とcliを入れる

npm install -D webpack webpack-cli

これでwebpackコマンドが使えるようになったので、まずはTSをビルドできるようにするためts-loaderを入れる

npm install -D ts-loader

続いてwebpackの設定を書いていくが、npx webpack init コマンドだと対話形式で一気に色々(lintの設定とかまで)やろうとしてくる
今回は1つ1つ確認しながら進行したいので、初期化コマンドは利用しないで進める

touch webpack.config.js で空のファイルを生成し、以下の内容を書く

const path = require('path')

module.exports = {
  mode: process.env.NODE_ENV,
  entry: "./src/index.tsx",
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "build.js",
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: [
          {
            loader: "ts-loader",
          },
        ],
      },
    ],
  },
}

ビルドの準備ができたので、Reactを入れていく

Reactの導入

reactとreact-domパッケージを入れる

npm install react react-dom

さらにTSの型定義ファイルも入れる(本番で不要なので-Dをつける)

npm install -D @types/react @types/react-dom

webpackのビルドのエントリポイントに指定したファイルにReactのコードを書き、ビルドを実行してみる

src/index.tsx

import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(
  <div>Hello world</div>,
  document.getElementById('root')
)

NODE_ENV=development npx webpack

asset build.js 1010 KiB [emitted] (name: main)
/ 略 /
webpack 5.53.0 compiled successfully in 1987 ms

無事にビルドできて、成果物がdistディレクトリに出力された
しかし、まだこのファイルをhtmlに埋め込んで確認できないので、jsの出力と同時にjsが埋め込まれたhtmlも生成してもらえるよう html-webpack-plugin を使う
元となるテンプレートを指定すれば、バンドルされたファイルを読み込んだhtmlを出力先に同時に作成してくれるというもの

npm install -D html-webpack-plugin

テンプレートは以下のように用意した
src/index.tsxではrootというIDを持つ要素にマウントするようにしたので、rootというIDを持たせている

src/index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>react ts sample</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

webpackの設定も書き換える(追記したところだけ表示している)
html-webpack-pluginはデフォルトテンプレートとしてsrc/index.ejsを使うが、今回は違うのでtemplateオプションで指定している
https://github.com/jantimon/html-webpack-plugin#options

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  output: {...},
  module: {...},
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' })
  ]
}

再度ビルドする

NODE_ENV=development npx webpack

成果物に src/index.html が含まれるようになったので、ブラウザで開いてみるとちゃんとReactが動作し、Hello worldが表示されている

webpack-dev-serverの導入

ビルド確認のために毎回htmlを開くのは大変面倒なので、webpack-dev-serverで開発用のサーバーを立てる

npm install -D webpack-dev-server から NODE_ENV=development npx webpack-dev-server で起動する
これで http://localhost:8080/ で表示することができる

静的なファイルをReactで扱えるようにする

CSS

まずはよくあるreset.css的なものを読み込めるようにする
そのためにはJSファイル内で読み込まれるCSSを文字列としてJSの世界に持ち込むことができるようになるcss-loaderと
JSの世界に持ち込まれたCSS文字列をDOMに挿入する役割を担うstyle-loaderの2つを利用する

npm install -D css-loader style-loader

ローダーを有効にする
ローダーはどういうわけか後ろから読み込まれるので、文字列としてCSSを読み込むcss-loaderを後に書く必要がある
useの部分の指定の順番は非常に大切

module.exports = {
  output: {...},
  module: {
    rules: [
      {...},
      {
        test: /\.(css)$/,
        use: [
          "style-loader",
          "css-loader"
        ]
      }
    ],
  },
  plugins: [...]
}

設定が完了したので、今回はmodern-css-reset( https://github.com/andy-piccalilli/modern-css-reset/blob/e7500a6c7fa35be8d23f95b6e85f2cde0203f422/dist/reset.css )を読み込んでみる
src/reset.css に配置し、 src/index.tsximport './reset.css' の一文を追加すると、反映されていることが確認できる

画像

次は file-loader を使って画像を読み込んでみる
npm install -D file-loader

webpackの設定に追加する

module.exports = {
  output: {...},
  module: {
    rules: [
      {...},
      {...},
      {
        test: /\.(png)$/,
        use: ["file-loader"]
      }
    ],
  },
  plugins: [...]
}

コンポーネントで読み込む

import React from 'react'
import ReactDOM from 'react-dom'
import './reset.css'
import Img from './example.png'

ReactDOM.render(
  <div>
    Hello world
    <img src={Img}></img>
  </div>,
  document.getElementById('root')
)

しかし、このままでは以下のようなTSのエラーが発生し、webpackのビルドが通らない
Cannot find module './example.png' or its corresponding type declarations.

これは自分で型定義ファイルを作成する必要があるらしい(アンビエント宣言)
https://typescript-jp.gitbook.io/deep-dive/type-system/intro/d.ts

好きな場所に image.d.ts のようなファイルを作成し、以下の内容で保存する
declare module '*.png'

これでwebpackのビルドが通るようになり、TS+React+webpackの設定が完了した
TS導入の際に、TSはトランスパイラとしての役割を担えると書いたが、ビルド後のjsファイルを見てみるとちゃんとアロー関数などがES5向けにコンパイルされていることも確認できる

今回のコードはここに置いておいた

参考文献