🙆‍♀️

自前のローカルサーバ(外部プロセス)で動く React 開発環境 を作りながら、webpack の設定に入門した

2021/07/29に公開約8,000字

Webフロントの開発にデータAPIとしてのモックサーバは必要不可欠ですが、Express やそもそも Node が得意じゃない人は自分の慣れた言語でモックサーバを書きたいこともあると思います。
特に protobuf とか、 dll なんかで提供される資源を利用したいとき、Node からアクセスするのは初心者にはハードルが高い。

ということで今回は自前のローカルサーバを使う React の開発環境を、OSに依存しないよう設定しました。
手作業でしたが想像以上にお手軽に動いたので共有します。

webpack 完全に理解した

達成したこと

  • webpack による JS, CSS ファイルのホットバンドル(ブラウザのリロードではない)
  • yarn start で全部起動するOS非依存設定 (ホットバンドルと自作サーバの立上げ)
  • React 用のloaderの設定。

外部プロセスの(並列)起動設定だけ知りたい方

yarn add yarn-run-all -D

npm-run-all --parallel のアライアスの run-p で npm script を並列実行することができます。

package.json
...
  "scripts": {
    "start": "run-p start:webpack start:server",
    "start:webpack": "webpack --watch",
    "start:server" : "yarn run-server",
    "run-server"   : "./server/run <Your Executable>"
  }
...

ミソは "start:server" に直接 bin を指定するのではなく、"run-server" と名前を付けてから、"yarn run-server" とすることで npm script にすることです。
run-all は npm script でないと処理できないため、普通に実行可能ファイルを登録すると失敗します。

やってみる

1.リポジトリの中でyarn init, git ignore する

yarn init -y
.gitignore
+ /node_modules/
+ /server/run

自作サーバのディレクトリが有る場合はお好みで ignore します。

2.webpack でバンドルしてみる

yarn add webpack webpack-cli --latest -D

Node のサーバは使わないのでwebpack-dev-serverは入れません。 -Dフラグで ビルド時に使わないモジュールとして指定できます。
以下で取り合えず動きます。

webpack.config.js
module.exports = {
  mode:"development"
};

確認。

yarn webpack

webpackはエントリポイントや出力先のデフォ値を持っているで動きます。
が、記述されていないのはストレスになるのでちゃんと明示します。

webpack.config.js
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
  }
};

"./src/index.js" をエントリーポイントにバンドルしたファイルを、 "./dist/bundle.js" に出力します。
動くか確認しましょう。

yarn webpack

3.yarn start でホットバンドルの設定

webpack には標準で watch 機能が付いています。起動時に --watch フラグを立てることで、バンドル後そのままソースを監視して変更を検知、自動的にバンドルしてくれます。
これを yarn start コマンドに登録します。

package.json
...
  "scripts": {
    "start": "webpack --watch"
  }
...

確認します。

yarn start

4#.同じように外部プロセスも起動しようとすると、失敗する

まずは単純に以下を試してみます。

package.json
...
  "scripts": {
    "start": "webpack --watch & ./server/server.exe<Your Executable>"
  }
...

これは webpack がプロセスをブロックしてしまいサーバが起動されません。 & はシェルに並列に見えるようバックグラウンド処理をお願いするのですが、 webpack --watch は明示的に終了させない限り走り続けるので、サーバが起動されません。

サーバ自体をノンブロッキングに書き換えて起動順を入れ替えたり、Unix なら GNU parallel, Windows なら start <program> で何とかなると思うのですが(未確認)、出来ることなら Node の設定のみで動き OS に依存しない方法を取りたいです。

4.yarn-run-all を使う

yarn add yarn-run-all -D

npm-run-all --parallel のアライアスの run-p で npm script を並列実行することができます。

package.json
...
  "scripts": {
    "start": "run-p start:webpack start:server",
    "start:webpack": "webpack --watch",
    "start:server" : "yarn run-server",
    "run-server"   : "./server/run <Your Executable>"
  }
...

ミソは "start:server" に直接 bin を指定するのではなく、"run-server" と名前を付けてから、"yarn run-server" とすることで npm script にすることです。
run-all は npm script でないと処理できないため、普通に実行可能ファイルを登録すると失敗します。

凄い単純ですが2時間ぐらいハマってました。

5.React がコンパイルされるようにする

長いです...調べれば出る情報ばかりなので慣れている方は飛ばして下さい。

入門ということで、動作確認しながら設定したいので取り合えずソースを書き換えます。

./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';

const App = ()=>{
  return(<h1>Hello 開発環境.</h1>);
}
ReactDOM.render(
  <App />,
  document.getElementById("root")
);

モジュールを足していきましょう。

yarn add react react-dom

この時点でyarn startしても、当然通りません。webpack が jsx を認識できないからです。
webpack が読み込む前に下処理をするフィルターのような機能、ローダーを使います。

yarn add babel-loader @babel/core -D
package.json
 ...
   "devDependencies": {
     ...
+    "@babel/core" : "^7.14.6", 
+    "babel-loader": "^8.2.2",

最新バージョンで設定していきます。
実は yarn add を行った場合、pakcage に記述されていない依存関係も勝手に解決してくれるので "babel-loader" だけaddしても(裏で "@babel/core" を使って)動くのですが、明示的なのは良い事なので "@babel/core" もちゃんと入れます。
で、webpack がちゃんとこのローダーを使うよう設定します。

./webpack.config.js
...
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
...

Babel に限らずですが Nodeのパッケージは汎用的になるよう設計されていて、どのファイルであれ読み込んで変換させるためには設定が必要です。この設定がまとまった @babel/preset-env を入れ、設定します。

yarn add @babel/preset-env -D
./babelrc
{
  "presets": ["@babel/preset-env"]
}

更に react のJSXを変換する設定も必要です。

yarn add @babel/preset-react -D
./babelrc
  "presets": ["@babel/preset-env", "@babel/preset-react"]

ここでバンドルすると...

yarn webpack

なんと通ります(!!)
「"babel-plugin-transform-react-jsx"は要らないの?」と思った方もいるかもしれませんが、どうやら "@babel/preset-react" の依存先として yarn が裏で解決してくれているみたいですね。良い時代に生まれたものです。
......「明示的なのは良いことだって言ってたじゃないか」という声が聞こえそうですが、まあこのぐらいなら良いかな。何がこのぐらいなのかは自分でも分かりません。
お好みで追加してください。

6.CSS を別ファイルへ書き出すよう設定する

現状ではwebpack に CSSを処理させようとすると失敗します。

Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

JSX でも見たように、 webpackはjavascript (用のAST? 内部の動作は分かりません) しか認識できないので、CSSファイルを(javascript として)読み取れるようにローダーと、その設定が必要です。

yarn add css-loader -D
./webpack.config.js
...
    rules: [
    ...
      {
        test: /\.css$/,
        use: {
          loader: "css-loader"
        }
      }

ここで yarn start して見ると分かるのですが、このままでは CSS が実際の画面に反映されません。理由は、css-loader はあくまで js ファイル上でインポートされたスタイルを認識できるようにするもので、dist.js からブラウザがスタイルとして認識できる形になっていないからです。

css-loader -> style-loader' で JS 上のデータとして認識したものをHTMLへ埋め込んだりも出来ますが、今回はMiniCssExtractPlugin`を使ってスクリプトとは別のファイルへバンドルします。

yarn add mini-css-extract-plugin -D
webpack.config.js
const MCEP = require('mini-css-extract-plugin');

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

filename:"style.css" にセットして初期化したMCEPのローダー使用して、css-loader -> MCEP.loader の順で webpack に処理させています。

これで、webpack によりソースコードが bundle.js, style.css へとまとめられるはずです。

yarn start

では同じ流れで Sass ファイルにも対応しましょう。ローダを末尾に追加することで、 sass-loadr が CSS ファイルへと変換し、その後は同様に css-loader -> MCEP.loader の順で webpack に流れていきます。

yarn add sass sass-loader -D
webpack.config.js
    ...
    {
      test: /\.css$/i,
      use: [MCEP.loader, 'css-loader']

ソースにscssファイルを追加して試してみましょう。

yarn start

7.Jest を React 用に設定する

テスト自体を書くわけではないのでおまけのようなものですが、Jest の設定もしておきます。

yarn add jest babel-jest react-test-renderer -D
./webpack.config.js
  ...
  "scripts":
    ...
    "test": "jest"
  ...

完成時設定ファイル

これで環境は完成です! あとは React と Jest の問題なので勉強しながら作っていきましょう

packege.json
{
  "name": "blog-front",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/yourpage/yourrepository",
  "license": "MIT",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.14.6",
    "@babel/preset-env": "^7.14.5",
    "@babel/preset-react": "^7.14.5",
    "babel-jest": "^27.0.2",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.2.6",
    "jest": "^27.0.4",
    "mini-css-extract-plugin": "^1.6.0",
    "react-test-renderer": "^17.0.2",
    "sass": "^1.35.1",
    "sass-loader": "^12.1.0",
    "webpack": "^5.39.0",
    "webpack-cli": "^4.7.2",
    "yarn-run-all": "^3.1.1"
  },
  "scripts": {
    "start": "run-p start:webpack start:server",
    "start:webpack": "webpack --watch",
    "start:server": "yarn run-server",
    "run-server": "./server/run",
    "test": "jest"
  }
}
./webpack.config.js
const MCEP = require('mini-css-extract-plugin');

module.exports = {
  mode:"development",
  plugins: [new MCEP({filename:"style.css"})],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.(sa|sc|c)ss$/,
        exclude: /node_modules/,
        use: [MCEP.loader, "css-loader", "sass-loader"]
      }
    ]
  }
}
./.babelrc
{
  "presets":["@babel/preset-env", "@babel/preset-react"]
}

Discussion

ログインするとコメントできます