react-scripts の dev server を fastify(-webpack-hmr) で動かす

とりあえずやってみたらパッと見動いた、くらいの内容で、ただのメモ書きです。

API server と create-react-apps で生成した Vue app を dev server で同居させるには、 本来的には dev server からの proxy でなんとかする するのがスジっぽい。

でも、たとえば本番は build した内容を static contents として (API server と同居させて) serve するのに、開発環境は proxy する (しかも dev server のほうが表に立つ) のはどうなんだという気がしなくもない。

Next.js なら GitHub - fastify/fastify-nextjs: React server side rendering support for Fastify with Next を使えば fastify から serve できるっぽいんだけど (これが hot reloading に対応しているかは不明)、 create-react-app (が利用する react-scripts dev server) ではどうすればいいのかわからない。

なので、 なんとかならんかなーと思ってやってみた。

create-react-app/start.js at main · facebook/create-react-app · GitHub をコピペしつつ GitHub - lependu/fastify-webpack-hmr: Webpack hot module reloading for Fastify をくみこんでみただけです。

react-scripts の dev server、デフォルトで react-dev-utils の create-react-app/webpackHotDevClient.js at main · facebook/create-react-app · GitHub を利用してるっぽいんだけど、それを利用するのはうまくいかなかった。

なのでこれだとエラー画面とかでないんじゃないかな…… (2021-09-17 追記: ビルドエラーはちゃんとでました)

TypeScript で書きたかったんだけど、 型定義ないやつあったり @types が古かったりしたんで、とりあえず (もとの JS のまま) コピペ。

process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

const fs = require('fs');
const {
  choosePort,
  createCompiler,
  prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const paths = require('react-scripts/config/paths');
const configFactory = require('react-scripts/config/webpack.config');
const webpack = require('webpack');
const fastify = require('fastify');
const hmr = require('fastify-webpack-hmr');

async function main() {
  const useYarn = fs.existsSync(paths.yarnLockFile);

  const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
  const HOST = process.env.HOST || '0.0.0.0';

  const port = await choosePort(HOST, DEFAULT_PORT);
  if (port === null) {
    return;
  }

  const config = configFactory('development');
  const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
  const appName = require(paths.appPackageJson).name;

  const useTypeScript = fs.existsSync(paths.appTsConfig);
  const urls = prepareUrls(
    protocol,
    HOST,
    port,
    paths.publicUrlOrPath.slice(0, -1)
  );

  config.entry = [
    'webpack-hot-middleware/client?reload=true&timeout=1000',
    config.entry,
  ];
  config.plugins.push(new webpack.HotModuleReplacementPlugin());

  const compiler = createCompiler({
    appName,
    config,
    urls,
    useYarn,
    useTypeScript,
    webpack,
  });

  const server = fastify({
    logger: true,
  });

  server.register(hmr, { compiler, webpackDev: {}, webpackHot: {} });

  server.listen(port, HOST);
}

main();

2021-09-17 追記

React Router: Declarative Routing for React.js はうまくいかなかった (/ 以外の route に遷移して reload すると 404 になってしまう)。 シンプルに解決するのは難しいっぽい (Support for React Router Routes · Issue #8 · lependu/fastify-webpack-hmr · GitHub) ので、

  • どうせ dev server なのであきらめる
    • 別段これでいいと思う
  • HashRouter を使う

しかなさそう。