webpack / react / typescriptを使ったフロント環境をgithub-pagesに公開するまで

## 目的

自分の携わっているサービスでreactに触れる機会があり、そこで得た知見を少しずつまとめようと思いました。どんなものを作成していたのか後で振り返りやすいようにgithub-pagesで公開して、web上から触れる設定を作ることを考えています。

この記事では、そのためのベースとするWebpack/React/TypeScript を設定して行きます。
記事のゴールとしては、React/TypeScriptを利用して書いたコードを、webpackでコンパイルしたものを出力してgithub-pagesに公開できることを目指します。


## 環境/前提条件

今回利用する主要なツールのバージョンは下記の通りです。
筆者はmacOSでvagrant+dockerの環境で作業しています。
vagrant+dockerの環境構築に関しては[こちら](https://qiita.com/akiumikin/items/ea3302d1a746dc263750)

|ツール名|バージョンなど|
|---|---|
|react|17.0.1|
|typescript|4.1.3|
|webpack|5.12.2|

すでにnpmのインストールまでが済んでいることが前提となります。
筆者のnpm/nodeのバージョンはそれぞれ 6.14.10 / v14.15.4 を利用しました。

## 流れ

- npm initでpackage.jsonを作成する
- npmでwebpack/React/TypeScriptをインストール
- webpack用の設定
- TypeScript用の設定
- index.htmlとReactを使ったsrcファイルの作成
- webpack-dev-serverの起動とwebpackのbuildの実行
- github-pagesへの公開

## ディレクトリ構成

今回作成するファイル群のディレクトリ構成は下記となります。

```
root --- docs ※1
      |   ├- index.html
      |   └- index.js
      ├- src
      |   └- typescripts ※2
      |       └- main.tsx
      ├- node_modules
      ├- package.json
      ├- package-lock.json
      ├- index.html ※3
      ├- tsconfig.json
      ├- webpack.common.js
      ├- webpack.dev.js
      ├- webpack.prod.js
```
※1 webpackのファイルの出力先、本来ならdistとするがgithub-pagesではサブディレクトリとして利用できるのがdocsだけだったので、docsで設定
※2 今後に自分が利用する際にこの階層にstylesheetsなども配置するためこの構成としています。
※3 このファイルはindex.htmlのテンプレートファイルです。webpackから出力されたindex.htmlはこれを基に作られます。


## 内容

### npm initでpackage.jsonを作成する


### npmでwebpack/React/TypeScriptをインストール

下記のコマンドで必要なツール/プラグインをインストールします。

```shell
npm i -D webpack webpack-cli clean-webpack-plugin webpack-dev-server html-webpack-plugin typescript ts-loader
```

オプションの`-D`は`--save-dev`のエイリアスらしく、開発環境内へのインストールとして扱われます。
インストールする内容に関して簡易的な説明としては下記となります。

|ツール名|バージョンなど|
|---|---|
|webpack|webpackの本体です。作成したReact/TypeScriptのコードをまとめるモジュールバンドラです。|
|webpack-cli|webpackの操作をするためのコマンドセットです。|
|clean-webpack-plugin|webpackをbuildして出力ファイルを作った際に古いファイルや使ってないファイルを削除するプラグインです。|
|webpack-dev-server|ローカル上でwebpackを使って開発をする際に利用する開発環境向けのwebサーバーです。|
|html-webpack-plugin|webpackでのbuild時にindexとなるhtmlファイルも出力するためのプラグインです。これがない場合には出力ファイルはjsのみとなりページとして表示できません|
|typescript|TypeScriptの本体です。|
|ts-loader|webpackでtypescriptをコンパイルするために必要なloaderです。|


次に下記のコマンドでインストールを実行します。

```shell
npm i -S react react-dom @types/react @types/react-dom
```
|ツール名|バージョンなど|
|---|---|
|react|Reactの本体です。|
|react-dom|ReactでのDOM操作に利用され基本的にはreactとペアでインストールを行います。|
|@types/react、@types/react-dom|上記２件のTypeScriptの型定義ファイルです。|


### webpack用の設定

まず、webpack用のjsファイルを作成します。
ローカル上ではwebpack-dev-serverを行い、本番サーバーやgithub-pagesではコンパイル後のファイルだけあればいいので、それぞれの環境によって設定が異なります。そのため、下記の三つにファイルを分割して作成をします。

|ツール名|バージョンなど|
|---|---|
|webpack.common.js|共通設定の記載用のファイル|
|webpack.dev.js|開発環境用のファイル、webpack-dev-serverの設定が入ります。|
|webpack.prod.js|本番環境用のファイル、productionという設定を入れるだけですが、デフォルトの動きでコードの圧縮などを行うようになります。|

下記ではそれぞれのファイルの内容の説明をした後に


#### webpack.common.js

下記の内容で作成します。

```js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')

const htmlPlugin = new HtmlWebpackPlugin({
  template: "./index.html",
  filename: "index.html"
});

module.exports = {
  entry: './src/typescripts/index.tsx',
  plugins: [
    new CleanWebpackPlugin(),
    htmlPlugin,
  ],
  module: {
    rules: [
      {
        test: /\.tsx?$|\.ts?$/,
        use: [{
          loader: 'ts-loader',
        }],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
    ],
  },
  resolve: {
    modules: [
      'node_modules',
      path.resolve('./src/typescripts'),
    ],
    alias: {
      tsRoot: path.resolve('./src/typescripts'),
    },
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  output: {
    path: path.resolve(__dirname, './docs'),
    filename: 'index.js',
  },
};
```

|設定項目||
|---|---|
|entry|エントリーポイントの設定です。ここで設定したファイルを起点にwebpackがコンパイルをします|
|plugins|追加するプラグインです。|
|modules|コンパイルする対象のファイルごとにコンパイルに利用するローダーを設定します。|
|resolve|コンパイルの対象の設定などを行います。今回の作業の中では利用しませんが、エイリアスの設定も行って、React/Typescriptの中で絶対パスのような記載をするために利用します。|
|output|コンパイルしたファイルの出力先の設定|


#### webpack.dev.js

```js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    host: '0.0.0.0',
    port: 8000,
    disableHostCheck: true,
    historyApiFallback: true,
    contentBase: './docs'
  }
});
```

#### webpack.prod.js

```js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production',
});
```

#### package.jsonにscriptを追加する
webpack-dev-serverの起動とbuildのために下記のscriptsをpackage.jsonに追記します。

watchとbuildを追記

```json
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "watch": "webpack serve --open --config webpack.dev.js",
  "build": "webpack --config webpack.prod.js"
},
```

### TypeScript用の設定

下記のファイルを作成します。
デフォルトの構成まではコマンドラインからも作成可能です。

outDirやBaseUrl,pathsなどをこれまでの他の設定に合わせて修正します。

```
{
  "compilerOptions": {
    /* Basic Options */
    "target": "es6",      /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "jsx": "react",       /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    "sourceMap": true,    /* Generates corresponding '.map' file. */
    "outDir": "docs",  /* Redirect output structure to the directory. */

    /* 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. */

    /* Module Resolution Options */
    "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    "baseUrl": "./",            /* Base directory to resolve non-absolute module names. */
    "paths": {                  /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
      "tsRoot/*": ["./src/typescripts/*"],
      "tsRoot": ["./src/typescripts"],
    },
    "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

    /* Advanced Options */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}
```

### index.htmlとReactを使ったsrcファイルの作成

下記のファイルをそれぞれ作成します。

- root > index.html

```html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Front Component Sample ver1.0</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
  </head>

  <body>
    <div id='app'></div>
  </body>
</html>
```

- root > typescripts > index.tsx

```tsx
/** imports */
import * as React from 'react'
import * as ReactDOM from 'react-dom'

/** DOMへのrender */
// <div id='app'/> のコンポーネントが必要なことに注意
ReactDOM.render(
  <>render test</>,
  document.getElementById('app')
);
```

index.htmlファイルの `<div id='app'></div>` の部分にreactで書いた「render test」の文字を表示するだけのファイルです。
このファイルを使ってwebpack-dev-serverとwebpackのbuildの確認をします。


### webpack-dev-serverの起動とwebpackのbuildの実行

#### webpack-dev-server
rootディレクトリにcdコマンドで移動して下記のコマンドを実行します

```shell
npm run watch
```

package.jsonに登録されたscriptが実行され、webpack-dev-serverが立ち上がります。
ブラウザで `http://localhost:8000/index.html`にアクセスして「render testと表示されるか確認して表示されればOKです。

#### webpackのbuild

rootディレクトリにcdコマンドで移動して下記のコマンドを実行します

```shell
mkdir docs
npm run build
```

root以下のdocsディレクトリにindex.htmlとindex.jsが作られていればOKです。

### github-pagesへの公開

作成した内容をgithubにpushした後に、github-pagesの設定をします。

リポジトリにアクセスして下記の画面に遷移します。

`settings > options`

下の方にGithub Pagesの項目があるのでそこのSourceで対象のブランチとフォルダをdocsに設定します。
更新後にGithub Pagesの項目を見るとURLが発行されているので、アクセスして画面が表示されればOKです。

github-pagesにアクセスするまでに多少時間がかかるようなので注意してください。