ロゴ

create-react-appなしでReact+typescriptの環境を構築しwebpackを理解する

作成日:2022年7月5日

目次

はじめに

reactのチュートリアルから誰もがお世話になるcreate-react-app。
reactのアプリケーションをコマンド一発で楽々に作ることができますよね。
便利だし、早いし、typescriptの環境構築も楽勝です。

めちゃくちゃ便利なだけあってreact初心者は、
自動でインストールされているwebpack等の関連ライブラリを意識できません。
create-react-appを使用して開発を始めると、「package.json??なんやそれ、、怖いから触らないでおこう」的な軽いノリで、
モダンJSにおいて大事な部分を見て見ぬふりをしながら、reactを触り始めることができてしまいます。

今回は、create-react-appを使わずにreact環境構築をして、
いろいろすっ飛ばしてきた部分を理解しつつ、create-react-appと遜色ない環境を作っていきます。

準備

npmで各種パッケージをインストールし、環境構築していきます。
※Node.Js、npmをインストールできていない方は下記を参考にしてください。
Node.js、npmのインストール方法|Qiita

プロジェクトフォルダを作成

プロジェクト名は「react-forum」とします。

#「react-forum」フォルダの作成
$ mkdir react-forum  
#react-forumフォルダ直下に移動
$ cd react-forum


package.jsonを生成

package.jsonの中にはインストールしたパッケージがJson形式で記録されています。

実行すると、コマンドラインでいろいろ聞かれると思いますが全部エンターを押して飛ばしてもらって問題ないです。

#package.jsonを作成
$ npm init

ここまではお約束といった感じですね。

Webpackをインストールする

webpackの主な役割を簡単に説明すると、
複数のJavascriptやcss等のモジュールを一つのjsファイルにまとめてくれるライブラリです。

Webpackについては、下記で詳しく説明しておりますので、もっと知りたい方は下記を参考にしてください。

Webpackのパッケージを二つインストールします。

  • webpack …Webpackの本体
  • webpack-cli …インストールしたWebpackをコマンドラインから実行できるツール


 $ npm install webpack webpack-cli

 ここで package.jsonの中身を確認してみましょう。

package.json

{
  "name": "react-forum",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  //この中にインストールしたパッケージ名とバージョンが追加されていきます。
  "devDependencies": {
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

devDependenciesにwebpack、webpack-cliが追記されていることが確認できれば問題ないです。

ちなみに、先ほどののインストールは「--save-dev」というオプションを付けましたが、これを付けた場合、
開発環境でのみ使うことができるパッケージとしてインストールされます。
「--save-dev」を付けなかった場合本番環境、開発環境両方で使えるパッケージとなり、
package.jsonのdependenciesに追加されます。

webpackはローカルでビルドする際にしか使用しないので、「--save-dev」オプションをつけてインストールします。

webpack.config.jsを作成

「webpack.config.js」はwebpackの設定ができるJsファイルです。
入出力ファイルの設定はもちろんのこと、ビルド時のカスタマイズなど様々な設定ができます。

ルートディレクトリにwebpack.config.jsを作成します。

$ mkdir webpack.config.js


今回は出力ファイルを「dist/static/main.js」としています。
各設定項目の説明はコメントに記載。

webpack.config.js

const path = require('path')
module.exports = {
  // 'development'に設定すると、バンドルファイルに「元のコード」と「変換後のコード」との
  // 対応関係が記述されたコメントが記述されます
  // 'production'に設定すると、コメントは記述されず、最適化されたコードで変換されます
  mode: 'development',

  // 最初に実行する変換前のファイル
 // 今回はsrcフォルダのindex.tsを指定
  entry: './src/index.ts',
 //バンドルファイルの出力設定
  //__dirnameでプロジェクトのルートパスを取得できます。
  output: {
      path: path.join(__dirname, '/dist/static'), 
      filename: 'main.js' //出力ファイル名
  },
 //importの際に省略する拡張子をextension:配列で指定
    resolve: {
        extensions: ['.ts', '.tsx', '.js'] 
    },
};


Typescriptの導入

reactに限らず、webpack+typescriptの開発環境を作る上で必要な手順は下記のようになります。

  1. typescriptをjavascriptに変換(トランスパイル)するモジュールのインストール(typescript)
  2. 1の変換モジュールをwebpackで使えるようにするライブラリのインストール(ts-loader)
  3. トランスパイルをどのように行うのかの設定
  4. webpackでバンドルする際に、トランスパイルを実行するように設定


まずは1,2のモジュールをインストールします。

$ npm install --save-dev typescript ts-loader


次にトランスパイルをどのように行うかの設定を行います。
トランスパイルの設定を行います。

$ mkdir tsconfig.json


tsconfig.json

{
    "compilerOptions": {
      "target": "es5",//出力するjavascriptのバージョン
      "jsx": "react",//jsxをReact.createElementに変換する設定。jsxのままにするような場合は"preserve"で指定
      "strict": true,//trueにするとtypescriptのコンパイル時の型チェックがより厳格になる。
      "esModuleInterop": true,//CommonJSとESモジュール間との相互運用を可能にするためのフラグです。積極的にtrueで大丈夫です。
      "removeComments": true,//出力ファイルのコメントを削除するかどうか
      "skipLibCheck": true,//.d.tsファイルの型チェックをスキップする。
      "forceConsistentCasingInFileNames": true//importするファイル名の大文字小文字を区別するかどうか。
    }
}


最後に、4の手順です。
webpack.config.jsにmodule.rulesを追加し、「.ts」「.tsx」ファイルに対してts-loaderを使ってトランスパイルする設定します。

webpack.config.js

const path = require('path')
module.exports = {
  mode: 'development',
  entry: './src/index.ts',
  output: {
      path: path.join(__dirname, '/dist/static'), 
      filename: 'main.js' //出力ファイル名
  },
  //追加
  module: {
    rules: [
      {
        // 「.ts 」または「.tsx 」ファイルを変換するよ~
        test: /\.(ts|tsx)$/,
        // ts-loaderを使って変換するよ~
        use: 'ts-loader',
      },
    ],
  }
  resolve: {
        extensions: ['.ts', '.tsx', '.js'] 
  },
};


Reactをインストール

Reactを使うためにはreact本体と、react-domの二種類のパッケージをインストールする必要があります。
react本体には、おなじみのreact機能が入っていて、
react-domはreactとDOMを橋渡しするような役割があります。

$ npm install react react-dom


Reactのtypesをインストール

せっかくtypescriptを使っているのにreactのtypesがないことには意味がありません。

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

@types/reactの中身に興味がある方はnode_modules/@types/react内の「index.d.js」、「global.d.js」のソースを見てみることもお勧めします。

Reactで動作確認

ここまでで

typescriptでreact開発

webpackでバンドル

までの一連の流れを行う設定はできています。
あとは、reactの中身を書いて、webpackの出力ファイル(デフォルトで/dist/main.js)
をhtmlで読み込んで表示していきます。

index.ts、app.tsxの作成

webpack実行時のエントリポイントとなる「src/index.ts」と、親コンポーネントとなるApp.tsxを作成をします。
srcディレクトリを作成します。

$ mkdir src

srcディレクトリの中にファイルを二つ作成します。
(どうやらreactのv18以降はrender()は使えないみたいなので、createRoot()を使ってます。)

index.ts

import React from "react";
import { createRoot } from 'react-dom/client';
import App from "./app"

const ROOT_ID = "root" as const ;//次に作成するdist/index.htmlのルート要素のid

const rootElement = document.getElementById(ROOT_ID);

//rootElementは HTMLElement | null なので、nullの場合の処理を書かないと怒られます
if (!rootElement) throw new Error(`${ROOT_ID}の要素が見つかりません。`);

const root = createRoot(rootElement);
root.render(<App/>);


app.tsx

import React from 'react'

function App() {
  return (
    <h1>Hello World!!</h1>
  )
}
export default App


index.htmlを作成

webpack実行時の出力ファイルはdist/static/main.jsなので、distフォルダを作成し、
dist直下にindex.htmlを作ってあげます。

reactをレンダリングする部分にはindex.jsでも指定した、id="root"を設定します。
バンドルファイル(static/main.js)はhtml読み込み後に実行しなければならないので、
defer属性を付与することを忘れないようにしてください。
dist/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script defer src="static/main.js"></script>
        <title>掲示板</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>


webpackを実行

ここまで出来たらあとはwebpackを実行すれば、src配下のファイルがdist/static/main.jsにバンドルされます!
実行する際はnpm の場合パスが通っていないので、npxを使用します。
(npxで実行した場合には、node_modules/.bin配下から実行するファイルを選んで実行してくれます。)

$ npx webpack

npmで実行したい場合は、package.jsonファイルのscriptにビルドコマンドを追加し、実行します。
package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build":"webpack"
  },

$ npm run build


dist/static/main.jsが作成されていれば成功です。

index.htmlをブラウザで確認

エクスプローラからdist/index.htmlを実行してみましょう。

成功!!!
でも毎度毎度コマンドをたたいてブラウザを確認するのは不便ですよね。
ここからはローカルサーバーを使ってよりサクサクテストできる環境を作っていきます。

webpack-dev-serverをインストール

webpack-dev-serverを使えば、保存時に変更の差分のみバンドルファイルに即時反映され、効率よく開発を行うことができます。

webpack-dev-serverをインストールします。
※「webpack-devserver」という似て非なるパッケージもあるので、インストールする際は気を付けてください。

$ npm install --save-dev webpack-dev-server


続いて、webpack-dev-serverの設定を行っていきます。
webpack.config.jsでdevserverオプションを設定することで、webpack-dev-serverの起動時の挙動や、サーバーの設定が可能です。
webpack.config.js

    //webpack-dev-serverの設定
    devServer: {
        static: path.join(__dirname, '/dist'), //webpackの出力先を指定してあげてください
        open: true, //ブラウザを自動的に起動します
        port: 3000 //ポート番号を3000番に指定
    },


それではwebpack-dev-serverを起動してみましょう。

$npx webpack-dev-server

ブラウザが自動で起動し、http://localhost:3000で先ほどの画面が見れれば成功です。

webpack-dev-serverでホットリロードが効かない

webpack-dev-serverを起動した状態で、src配下のファイルに何かしらの変更を加えて保存してみましょう。
変更がブラウザに反映されていないと思います。

僕はこれに引っ掛かって何時間もハマっていました。

何時間もの格闘の末、
「webpack-dev-serverはオンメモリでバンドル結果を保持していて、
そのバンドル結果はデフォルトで表示する静的ファイル直下のmain.jsからアクセスできるようになっている
という事実が判明し、解決に至りました。

つまり、webpack-dev-serverのバンドル結果にアクセスするためのパスを変更してあげる必要があるということです。
この変更はwebpack.config.jsのoutput.publicPathから行います。

webpack.config.js

 output: {
        path: path.join(__dirname, '/dist/static'), 
        publicPath: '/static/', //staticディレクトリ直下のmain.jsからバンドル結果にアクセスできるように変更
        filename: 'main.js'
    },


これで、ホットリロードが正常に機能するはずです。

まとめ

今回は、create-react-appを使わずにtypescript+react開発の環境構築方法について解説いたしました。
webpack、typescript周りについて、まだまだ理解できていないことが多いなと反省するいいきっかけになりました。

関連記事

記事画像

2022年7月1日

Yakan

田舎育ちのアナログ人間ですが、システム作ってます。
趣味の制作はWebアプリ中心です。
仕事は業務系なのでC#メインで書いてます。

Yakan

田舎育ちのアナログ人間ですが、システム作ってます。
趣味の制作はWebアプリ中心です。
仕事は業務系なのでC#メインで書いてます。

© 2022 - 2023 Yakan.