create-react-appを使わずReactアプリを作る(勉強手帳)

React開発を勉強するにあたって、2022年はcreate-react-appを使用して、まずはreactに慣れることを目的に進めて来ました。

次のステップとして2つの課題を上げます。

  • create-react-appを使わない開発環境
  • TypeScriptでのコーディング

今回はその手始めとして、create-react-appを使わない手順というものを理解したいと思います。

この投稿はあくまで、個人用の勉強のメモを主目的にしてますので、見づらい点や誤解釈という部分はご了承ください。

create-react-appとは

Node.jsを導入済みであれば実行可能なパッケージの1つと認識しています。
CLIとしてはnpm(install)、npx、yarnでの実行可能です。

位置付けとしては、Facebookが提供しているCLIツールで、あくまでReactアプリの雛形を自動で用意してくれるというものです。
つまり、これを使わなくても、Reactアプリは開発できるということです。

今回、create-react-appを使わないことをテーマにした背景は、いろいろ調べていると目にするキーワード「個人開発としては使っても、サービス提供として使うのは一般的ではない」「設定が隠蔽されるため、後々調整が効かなくなる」ということを多く目にします。

確かにReactアプリを作れるようにはなったものの、どういう仕組みで動いているのか根本的な部分での理解は薄いと自覚しています。それはcreate-react-appという便利なツールに頼っているからに他なりません。

Reactアプリを本当に理解する上では、create-react-appを使わず、仕組みを理解した上での環境構築ができるようになりたいと考えました。

create-react-appでTypeScriptのプロジェクトを作る場合

以下のコマンドになります。

npm create-react-app testapp --template typescript

Reactアプリに必要な構成品を理解する

まずやることはnpmの初期化

まずは所定の場所にアプリ用のフォルダを作成し、その中でnpmの初期化を行います。

mkdir testapp
cd testapp
npm init -y

3行目の「npm init」は「このフォルダ配下をnpm(Node Package Manager)で管理しますよ」という準備/宣言です。

「npm init」コマンドを実行すると、対話形式で以下の内容が聞かれます。

  1. package name:このアプリケーションの名前。defaultではフォルダ名になる。
  2. version:このアプリケーションのバージョン。defaultでは1.0.0になる。
  3. description:このアプリケーションの概要。
  4. entry point:Reactアプリの実行時に起点となるソースファイルという解釈。defaultではindex.jsになる。
  5. test command:不明
  6. git repository:githubに保存する際のリポジトリを作るかどうか?
  7. keywords:npm公開時に使用されるキーワード?(npm公開を理解していないのでPend)
  8. author:npm公開時に必要となる作者情報?(npm公開を理解していないのでPend)
  9. license:npm公開時の権利情報?(npm公開を理解していないのでPend)

とりあえずエンター押下で進めればdefault値で完遂できるようなのでOKです。
また「npm init -y」にしても、全てdefault値で完遂してくれます。

「npm init」が終わると、フォルダ直下に「package.json」が出来上がります。

これはcreate-react-appで作っていても見覚えがあリますが、今時点での中身を確認すると、上記1〜9の対話内容で記述されていることが確認できます。(-yの場合はdefault値)

package.jsonの設定は後ほど行います。(後述

webpackをインストール

React開発をやっていて常々目にするキーワードが「webpack」です。
似たような言葉でWebpacker」というのがありますが、別物なので注意が必要です。

Webpackerとは

Ruby on Railsで使われるGemパッケージで、目的はwebpackと同じです。

webpackとは

webpackを調べると出てくるキーワードが「モジュールバンドラ」です。
言葉の意味を調べて、また別なわからない言葉が出てくるとしんどいです。

「モジュール」はJavaScriptなどのソースファイル、「バンドラ」は束ねるとか纏めるとかの意味になりますので、ソースファイルを束ねてくれる機能という理解です。

開発過程で、複数に分かれているソースファイルを1つのファイルにまとめてくれるということです。

あくまで効率的な形(余計なスペースを省いて、1つのファイルに纏めるイメージ)にするのであって、機械語翻訳とかそういうのではないです。

バンドル、ビルド、コンパイル

「ビルド」は他の言語で開発を経験していると、どうしても機械言語への置き換えをイメージしてしまいますが、React開発における「ビルド」はバンドルと同じくwebpackで「バンドル(纏める、束ねる)」するのと同じ意味になります。

React開発で「コンパイル」はあまり使わない気もしますが、同じ意味と捉えて良い気がします。

webpackの構成品

webpackとwebpack-cli

まずはwebpack本体が必要です。
ただし、本体があってもそれを操作するための手段がないと意味がないので、別で提供されているコマンドラインインタフェース(CLI)であるwebpack-cliも合わせてインストールします。

webpack-dev-server

webpackでの開発サーバーという位置付けです。
開発環境で実行する際には必要です。

運用環境では、webpackでビルド(バンドル)された結果の成果物で動くので、webpackを介さない実行環境ということになります。従ってwebpack-dev-serverの出番はなく、通常のwebサーバ(nginx、Apache等)で動きます。

clean-webpack-plugin

webpackでビルドする際の出力先ディレクトリを掃除してくれる機能です。
必須アイテムではないので、今のところ使ったことはありません。

毎回出力先を手動で削除するようにしていれば不要です。

html-webpack-plugin

まだ詳細未確認ですが、ビルド時にhtmlを別ファイルで出力する目的で使用するようです。
React開発時にhtmlファイルを含めたことがなかったので、使ったことがありません。

html-loader

これもReactソースにhtmlを含める場合に使う機能のようです。
React開発時にhtmlファイルを含めたことがなかったので、使ったことがありません。

webpackのインストール

いろいろなサイトを見て回り、概ね以上の構成品がwebpackとして導入されているようです。

ただ、目的に応じてのところもありますので、今回は最低構成という意味でwebpack、webpack-cli、webpack-dev-serverを導入したいと思います。

npm install -D webpack webpack-cli webpack-dev-server

オプションの「-D」は「--save-dev」と同じです。
「--save-dev」オプションを付けたインストールは、package.jsonのdevDependenciesセクションに追記されます。
devDependenciesは開発時のみ必要なパッケージを示し、対してdependencies(たぶん何もセクション名がない)は実行時に必要なパッケージを示します。

webpack関連は開発時(開発中〜運用モジュールを作成するまで)に必要なパッケージなので、「-D」または「--save-dev」オプションを付けてインストールします。

webpackの初期化

npx webpack-cli init

このコマンドを実行すると、「@webpack-cli/generators」のインストール実施を聞かれるので「Y」で続行します。
以降は対話形式での入力を行い、結果webpack.config.jsが作成されます。

  1. 次のJSソリューションのうち、使用したいものはどれですか? → TypeScript
  2. webpack-dev-serverを使用しますか? → Y
  3. バンドル用のHTMLファイルを簡素化したいですか? → Y
  4. PWAサポートを追加しますか? → n
  5. 次のCSSソリューションのうち、使用したいものはどれですか? → SASS
  6. プロジェクトでSASSとともにCSSスタイルを使用しますか? → Y
  7. プロジェクトでPostCSSを使用しますか? → N
  8. すべてのファイルのCSSを抽出しますか? → Yes
  9. 生成された構成をフォーマットするためにpretterをインストールしますか? → Y
  10. パッケージマネージャを選択 → npm
  11. package.jsonが既にあります。上書きしますか? → N

ちなみに「npx」は「Node Package Executer」の略で、Nodeパッケージの実行を意味するコマンドです。
ローカルインストールしたパッケージを実行する他、インストールされていない場合は一時的にインストール、実行、アンインストールのプロセスにより実質インストールせずにそのパッケージを実行してくれます。

「webpack-cli init」を実行すると、対話の結果でhtml-webpack-plugin等のプラグインも入ってしまうようなので、今回は手動でwebpack.config.jsを作成します。(後述

Babelをインストール

Babelとは

ECMAScript2015(ES2015 または ES6 と呼ばれる)以降のJavaScriptコードを、それ以前の古いJavaScriptコードに変換してくれるJavaScriptコンパイラとかトランスパイラと呼ばれるツールです。
JavaScriptの歴史を見るとES2015の前後で大きく機能追加が行われており、ES2015に対応していないブラウザで動かすためにはBabelでのトランスパイラが必要ということになります。

Babelの構成品

babel-loader

Babelを使用したローダー。トランスコンパイラの実施者だと思えばわかりやすいです。
実施者なので、トランスコンパイラするための道具が必要で、その道具はこの後の記述になります。

@babel/core

Babelの本体。

@babel/preset-env

Babelのプリセット。
ちなみにpresetは「設定値などを前もって調整すること」を意味します。
Babelを使用する上での標準的な設定値というように認識しています。

@babel/preset-react

BabelでReactを扱うためのプリセット。

@babel/runtime

Babelのランタイム。

@babel/plugin-transform-runtime

Babelのランタイムのコードを再利用してサイズを小さくするプラグイン。

Babelのインストール

まず、トランスパイラの実施者であるbabel-loaderは必要として、そのbabel-loaderの最低限の依存関係は@babel/coreと@babel/preset-envのようです。それとReactを使う場合を考慮して@babel/preset-reactも必要です。最低限のインストールは以下のようになると思います。

npm install -D @babel/core @babel/preset-env babel-loader
npm install -D @babel/preset-react

ただ、そもそもIE11終了後の現時点で、Babelが必要かどうかという判断が発生します。

今回は一旦、旧ブラウザを対象外として、Babelをインストールしない形で話を進めます。

(参考メモ)webpackとBabelの必要性とES Modulesの関係

今回プレーンのReact環境をテーマに勉強しているが、その1つとして「webpack」「Babel」への理解も挙げています。
そんな中で、近年のIE11終了等により、そもそも「webpack」「Babel」の必要性が問われる記事を目にするようになりました。
この辺は別に勉強が必要な部分ではあるので、ここでは覚書程度にしますが、「webpack」「Babel」が必要な背景としては、従来ブラウザが同時接続数制限を持っていたり、「ES Modules」非対応だったりという背景があったからで、近年のブラウザはこの辺を対応して来ているので、「webpack」「Babel」の必要性が見直されているということだと認識しました。
ただ、この今活躍している各種フレームワークは「webpack」で依存関係を解決する仕様となっていたりとあるため、すぐに「webpack」撤廃はないのかなという印象でした。

Reactをインストール

Reactの動きの理解

従来のWebページは、ブラウザがHTMLを解析して、ドキュメント構造をオブジェクト化します。これをDOM(Document Object Model)と言います。オブジェクト化されたDOMの構成要素をDOMオブジェクトと言い、DOMオブジェクトはJavaScriptからダイレクトに操作できるのが特徴です。
つまりDOMを書き換えることにより、画面の表示が変わるという仕組みになります。

一方で、DOMの書き換えは、DOMの再構築を意味しており、都度画面描画が遅くなるという欠点を持っていました。

Reactにおいては仮想DOMという仕組みを設けることによって、この欠点を解消しています。

仮想DOMはプログラム的に作成された仮のDOM(JavaScriptのオブジェクト)であり、状態の変更はまず仮想DOMに反映されます。
仮想DOMへの変更によって、実際のDOMとの差分が発生した場合は、差分検出処理(可能な限り最小限の変更)により差分のみがDOMに反映されるので画面描画の高速化につながっています。

Reactの構成品

react

reactの主要ライブラリ。仮想DOMを操作するためのライブラリ。

react-dom

Reactを成立する上での必須ライブラリ。仮想DOMからDOMに反映させるためのライブラリ。

react-router-dom

Reactアプリで画面遷移するために必要なパッケージです。
本当にシンプルな構成のアプリであれば不要です。

Reactのインストール

まず必須となるのはreactとreact-domですので最低限は以下の通りです。

npm install react react-dom

あとは制作するアプリの要件に合わせて、react-router-dom等を追加すればOKです。

(参考)MaterialUIを使う場合

現時点でMaterialUIがreactの最新バージョンに対応していなかったので、MaterialUIを使いたい場合は1つ前のバージョン指定でインストールしておく必要があります。(別ブログを参照)

npm install react@17.0.2 react-dom@17.0.2
(参考)npm installのオプション

-D と --save-dev は同義で、開発環境でしか使用しないという意味です。
--save は古い記述で、package.jsonに追記することを明示する意味ですが、現在のnpm installではdefaultでpackage.jsonに追記されるので明記は不要です。
また、-g のオプションをつけるとグローバルインストールの意味になり、すべてのフォルダから参照可能なインストールになります。逆にこのオプションを付けなければローカルインストールなので、当該フォルダ配下が適用スコープになります。

TypeScriptをインストール

今まではピュアなJavaScriptでコーディングして来ましたが、今後はTypeScriptでのコーディングも合わせて学習していきます。

TypeScriptのインストール

npm install -D typescript ts-loader

TypeScript自体の他に、webpackでTypeScriptをトランスパイルするためのローダー(ts-loader)と、各TypeScriptの型情報もインストール

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

TypeScriptの初期化

webpackの初期化を行った際に、「tsconfig.json」が作成されていますが、一旦削除して、以下のコマンドで再作成します。

npx tsc --init

「tsc init」でtsconfig.json」を作成すると、コメントブロック含みで大量のコードが出力され、把握しきれないため、今回はcreate-react-appで出力されるものと同じ設定にする。(後述)

他に付帯するパッケージ(任意?)

今回は一旦スルーしますが、情報を集めている際に、あった方が良いよレベルで見かけたものです。
コードフォーマッターは1人開発ではあまり必要性はないかもしれません。複数名で開発する際にコーディングにバラつきがあると可読性が低下するため、それを抑制するためにルール制限するツールです。

eslint(イーエスリント)

リンター。
create-react-appだと標準搭載。
構文をチェックしてくれるツール。

pretter

コードフォーマッター。
create-react-appでは搭載されていない。(後付け可能?)
.js.ts.css.less.scss.vue.json コードをフォーマットするためのツール

今回用意した環境の設定ファイル(再確認)

package.json

当該プロジェクト(このアプリフォルダ)をnpm(Node Package Manager)で管理するための設定です。

npm init直後だと以下のようになっています。

{
  "name": "{アプリ名 or アプリフォルダ名}",
  "version": "1.0.0",
  "description": "{概要}",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [{npm公開時に使用されるキーワード}],
  "author": "{npm公開時に必要となる作者情報}",
  "license": "{npm公開時の権利情報}"
}

最低限の設定としては以下のように変更します。

{
  "name": "{アプリ名 or アプリフォルダ名}",
  "version": "1.0.0",
  "description": "{概要}",
  "main": "index.js",
  "scripts": {
    "start":  "webpack-dev-server --mode development",
    "build":  "webpack --mode production"
  },
  "keywords": [{npm公開時に使用されるキーワード}],
  "author": "{npm公開時に必要となる作者情報}",
  "license": "{npm公開時の権利情報}"
}

変更内容としては、1行目はコマンドラインで「npm start」と入力されたときに、「webpack-dev-server」を「development」モードで実行しなさいという意味です。2行目はコマンドラインで「npm build」と入力されたときに、「webpack」を「production」モードで実行しなさいという意味です。

webpack.config.js

webpack.config.jsのサンプル
(参考)https://naokeyzmt.com/rogue/not-create-react-app/

// pathモジュールの読み込み
const path = require("path");
 
module.exports = {
  // モードを開発モードにする
  mode: "development",
  // 入力ファイル設定
  entry: [path.resolve(__dirname, "./src/index.tsx")],
  // 出力ファイル設定
  output: {
    // 出力されるファイル名
    filename: "bundle.js",
    // 出力先ディレクトリ
    path: path.resolve(__dirname, "dist")
  },
 
  // モジュール設定
  module: {
    rules: [
      {
        // ts-loaderの設定
        test: /\.(js|ts|tsx)?$/,
        use: "ts-loader",
        exclude: /node_modules/
      },
    ]
  },
  // モジュール解決
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
 
  // 開発モード設定
  devtool: "source-map",
  devServer: {
    contentBase: "./dist"
    static: {
      directory: path.resolve(__dirname, 'dist'),
    },
  }
};

取消線で引いている「contentBase」の指定は、実行(npm start)したときに、エラーとなりました。
旧バージョンの記述とのことで、今のバージョンでは「static: { 〜}」が正しい記述らしいです。
記述を変更してエラーが解消しました。

mode

webpackのモード指定します。
"development"の場合は、再ビルド時間を短縮するなどの設定が有効になります。(開発中に向いている)
"production"の場合は、ファイルの圧縮やモジュールの最適化などの設定が有効になります。(本番環境構築時に向いている)

entry

エントリーポイント(このアプリケーションの起点)を指定します。

output

出力するファイル名やの出力先等の出力に関する情報を指定します。

備忘録

webpack.common.js、webpack.dev.js、webpack.prod.jsに分割することが推奨されているらしいのですが、今回はスルーします。

tsconfig.json

tsconfig.jsonのサンプル
※create-react-appのTypeScriptオプションで作成した場合の設定値

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

取消線で引いている「noEmit」の指定は、実行(npm start)したときに、エラーとなりました。
この指定を外すとエラーが解消するという記事がありましたので、その通りにしたところ、エラーが解消されました。

「noEmit: true」は、ファイルを出力しないようにする設定とのことで、ファイル(index.js)が出力されないためwebpackでエラーとなるようです。

フォルダ構成とソースファイル

各設定ファイルと整合するようにフォルダ構成を作ります。

testapp
> dist
   > index.html
> node_modules
> src
   > components
      > App.tsx
   > index.tsx
> package-lock.json
> package.json
>tsconfig.json
>webpack.config.js

ソースファイルを別途用意します。

(dist/index.html)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>pure-react</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>
(src/components/App.tsx)

import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <>
        <div>
          <h1>Hello TS-React!</h1>
          <h1>こんにちは</h1>
          <blockquote>
            <p>2023/01/02</p>
          </blockquote>
        </div>
      </>
    )
  }
}
export default App;
(src/index.tsx)

import React from 'react';
import ReactDom from 'react-dom';

// コンポーネント読み込み
import App from './components/App';

ReactDom.render(<App />, document.getElementById("root"));

実行結果

テスト実行

npm start

ブラウザでページを開くことができました。

なお、webpack-dev-serverでの実行では、ファイル出力は伴わないので、dist配下は何も変わっていません。

ビルド

npm run build

エラーもなく「compiled successfully」になりました。

dist配下には、bundle.js、bundle.js.LICENSE.txt、bundle.js.map、が出力されていますので、まずは成功だと思います。

振り返り

お正月中にのんびり3日ほどかけて勉強してみました。

インターネット上の記事の他に、kindle本「webpack 実践入門 第2版:webpackの基礎をしっかり理解して使いこなす」も非常に参考になりました。

以前よりは、React環境に対する知識はつきましたが、今回スルーした点も何点かありますので、そこはおいおいという感じです。

気になっている点①

あらためて、index.htmlとindex.jsの関係性が理解できていません。

これはReactのルールなのか、同じ名前でのhtml、js(tsx)という以外の紐付きはないように思うのですが、なぜか連動して動いてくれます。

気になっている点②

create-react-appでプロジェクトを作った場合、buildすると「build」ディレクトリが作成されて、その下にindex.htmlも含めてのビルド成果物が作成されます。
index.htmlは開いてみると改行がなくなっているので、バンドルされた結果だと思っています。

今回の環境では予め「dist」フォルダを用意して、その下にindex.htmlを置いており、ビルド結果でもindex.htmlはそのままの状態です。

おそらくはwebpack.config.jsの設定で以下のようなことをすれば良いのだと思いますが、今回は未確認です。

  1. devServerのコンテンツのルートディレクトリ指定を「dist」以外にする。
  2. index.htmlを上記1のパスに配置する。
  3. 上記2のindex.htmlもバンドル対象になるように設定する。(html-webpack-plugin?)

create-react-appの簡易版?react-scriptの活用

いろいろ調べている中で、react-scriptを使うという手もあるよという感じの記事も見かけました。

react-scriptはcreate-react-appでも使われているので、言わば折衷案的な感じになるのだと思います。

最後に

今回は理解を深める目的で、create-react-appを使わないプロジェクトの作り方を勉強してみました。

ただ、今のところcreate-react-appでは出来ないなという事情は発生していないので、当面はcreate-react-appのお世話になると思います。

おわり

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です