🚀

Node.js学習記録[1] クライアントコード配信の実装

に公開

前提

個人用アプリ開発のため、node.js/ReactでWebアプリを開発しようとしている。コンポーネントのレンダリングやハイドレーションがどうやって実現されているのかわかっていないので、実装しながら学びたい。

問題

onClick={//}と書いたコードをserver.tsで実行してもイベントハンドラが動かない。

原因特定

server.tsでのレンダリングとは別に、client側で実行するスクリプトでイベントハンドラを登録する必要がある。

対応

server.tsに、clientスクリプトへのリクエストを処理するコードを追加する必要がある。

http
  .createServer(async (req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) => {
    // 静的ファイルの提供
    if (req.url === "/styles.css") {
      const cssPath = path.join(__dirname, "../dist/styles.css");
      const css = fs.readFileSync(cssPath, "utf-8");
      res.setHeader("Content-Type", "text/css");
      res.end(css);
      return;
    }
    // ここを追加
    if (req.url === "/client.js") {
      const clientPath = path.join(__dirname, "../dist/client.js");
      const client = fs.readFileSync(clientPath, "utf-8");
      res.setHeader("Content-Type", "application/javascript");
      res.end(client);
      return;
    }

    const videoController = new VideoController();
    const videos = await videoController.getVideos();
    // Appは、サーバーで処理したデータがどのように配置されるかの構造情報
    // renderToStringは文字列にしている
    const html = renderToString(<App videos={videos} />);
    // クライアントサイドでも使えるようにinitialDataに含める()
    const fullHtml = `
      <!DOCTYPE html>
      <html>
        <!-- ... -->
        <body>
          <div id="root">${html}</div>
          <script>
            window.__INITIAL_DATA__ = ${JSON.stringify(videos)};
          </script>
          <script src="/client.js"></script>
        </body>
      </html>
    `;
    res.setHeader("Content-Type", "text/html");
    res.end(fullHtml);
  })
  .listen(3000, () => console.log("http://localhost:3000"));

クライアント側のコードを呼び出すために、url

残っている課題

Failed to resolve module specifier "react". Relative references must start with either "/", "./", or "../".

client.tsxでReactをインポートしている。そのためブラウザでReactのimport文を処理できずにエラーになる。
対処: scriptタグでreactのインポートを明示的に指定する

新たに出た問題

client.tsで利用しているコンポーネントファイルも、リクエストに応じて配信しなければいけないことがわかった。つまり、client.tsで利用するファイルの数*その依存関係の数だけパスを管理しなければならないことになる。そのため、バンドリングが必要。比較すると以下のようになる。

ベースライン

ブラウザ: 「Reactが必要」→ サーバーにリクエスト → ダウンロード
ブラウザ: 「VideoListが必要」→ サーバーにリクエスト → ダウンロード

バンドル後:

ブラウザ: 「bundle.jsをダウンロード」→ React + VideoList すべて含まれている
ブラウザ: すべて手元にある → 実行開始

よく考えてみれば当然だが、ぶつかるまで気づかなかった。

新たな知識

クライアントコードが必要になるたびにリクエストを飛ばすのでは、ファイル数だけリクエストが嵩張るため現実的ではない。

これを解決するためにViteやWebpackが存在する。

Discussion