Open14

`lazarv/react-server` の実装を雑に読む

NEKOYASANNEKOYASAN

HonoベースでRSCが利用出来るフレームワークをつくろうとしてるので、参考にさせてもらうために読みつつメモする

NEKOYASANNEKOYASAN

MicroFrontend周りは現状実装予定がないので後回しにする
File-system based router は実装方法がいくつかあるのでこれも後回し
use cache もまだNext.jsすらなんとも言えないバランス見極め期っぽい感じがしてるので後回し
外に露出する部分だけd.tsで型定義書いてそうなので若干実際にコードに落とし込むときに考える必要はありそう & bunとかいろいろ対応していてすげぇけどいったんNodeだけにするのでsys.mjsはあんまり深く見ないでよさそう

NEKOYASANNEKOYASAN

依存関係の最適化っぽく、externalしなくてもまぁ最悪なんとかなりそうな感じはするので実装自体は後回しでも良さそう

enforce: preが指定されてるのでalias確定後に実行されるplugin

resolveIdは依存関係をどう解決するかを決めてて、この後にexternalじゃなければloadが待ってる
resolveId内のthis.resolveはモジュールの情報を解決して返す関数
rsc/ssrのenvと、clientのenvで動作が違いそう

RSC向け

(
          (this.environment.name === "rsc" ||
            this.environment.name === "ssr") &&
          /\.[cm]?js$/.test(path) &&
          (bareImportRE.test(specifier) ||
            (specifier[0] !== "." && specifier[0] !== "/")) &&
          applyAlias(alias, specifier) === specifier &&
          !isModule(path) &&
          tryStat(path)?.isFile()
        )
  • (this.environment.name === "rsc" || this.environment.name === "ssr") && /\.[cm]?js$/.test(path): rscかssrの環境で、jsファイルのimportである
  • (bareImportRE.test(specifier) || (specifier[0] !== "." && specifier[0] !== "/")): bareImportRE (^(?![a-zA-Z]:)[\w@](?!.*:\/\/) に当てはまるか、./で始まらないimportである(多分依存関係のimportの解決であることをチェックしたい)
  • applyAlias(alias, specifier) === specifier: react関係をconditionでalias解決するので、それを解決してもspecifierと一致するか?
  • !isModule(path) && tryStat(path)?.isFile(): import先のファイルがESMのモジュールファイルではなく、かつファイルであることを確認してる

それ以降はファイルを読み取ってASTを確認してimport /exportがあればそのまま素通り、なければ{ externalize: specifier };を返す?
ここで返すべきは {...resolved, external:true}では??

Client向け

this.environment.name === "client" &&
          !this.environment.depsOptimizer?.isOptimizedDepFile(specifier) &&
          path &&
          /\.[cm]?js$/.test(path) &&
          (bareImportRE.test(specifier) ||
            (specifier[0] !== "." && specifier[0] !== "/")) &&
          applyAlias(alias, specifier) === specifier &&
          !isRootModule(path) &&
          tryStat(path)?.isFile()
  • this.environment.name === "client" && !this.environment.depsOptimizer?.isOptimizedDepFile(specifier) && path && /\.[cm]?js$/.test(path): 環境がclientで、OptimizedDepFileではなくて、pathが存在して、jsファイルであること
  • (bareImportRE.test(specifier) || (specifier[0] !== "." && specifier[0] !== "/")): bareImportRE (^(?![a-zA-Z]:)[\w@](?!.*:\/\/) に当てはまるか、./で始まらないimportである(多分依存関係のimportの解決であることをチェックしたい)
  • applyAlias(alias, specifier) === specifier: react関係をconditionでalias解決するので、それを解決してもspecifierと一致するか?
  • !isRootModule(path): ESMのModuleファイルではなく、かつPackageRootにあるモジュールじゃないこと?
  • tryStat(path)?.isFile(): ファイルであること

これらに当てはまるときにoptimizeする
そうじゃないときは、resolvedか、{ externalize: specifier }を返す

結構本当に{ externalize: specifier }が謎過ぎる

https://github.com/lazarv/react-server/blob/main/packages/react-server/lib/plugins/optimize-deps.mjs

NEKOYASANNEKOYASAN
    resolveId(id) {
      if (id === "virtual:react-server-eval.jsx") {
        return id;
      }
    }

virtual:react-server-eval.jsxのimportがあった場合のplugin

    async load(id) {
      if (id === "virtual:react-server-eval.jsx") {
        if (options.eval) {
          return options.eval;
        } else if (!process.env.CI && !process.stdin.isTTY) {
          let code = "";
          process.stdin.setEncoding("utf8");
          for await (const chunk of process.stdin) {
            code += chunk;
          }
          return code;
        }
        return "throw new Error('Root module not provided')";
      }
    }
  • options.evalがあったらそれをloadするように
  • CIではなく、Pipeで標準入力されている訳ではない
  • stdinされてる者を利用してloadする

どっちもなければRootModuleがない

https://github.com/lazarv/react-server/blob/main/packages/react-server/lib/plugins/react-server-eval.mjs

NEKOYASANNEKOYASAN

RSC関係のRuntime loadとかいろいろやるやつ
Dev環境での@hmrのコード

    if (id.endsWith("/@hmr")) {
        return `
          import RefreshRuntime from "/@react-refresh";
          RefreshRuntime.injectIntoGlobalHook(window);
          window.$RefreshReg$ = () => {};
          window.$RefreshSig$ = () => (type) => type;
          window.__vite_plugin_react_preamble_installed__ = true;
          console.log("Hot Module Replacement installed.");
          self.__react_server_hydrate_init__ = () => {
            if (typeof __react_server_hydrate__ !== "undefined") {
              import(/* @vite-ignore */ "${reactServerDir}/client/entry.client.jsx");
            }
          };
          self.__react_server_hydrate_init__();`;
      }

@__webpack_require__で終わってるモジュールのとき

else if (id.endsWith("/@__webpack_require__")) {
        return `
          const moduleCache = new Map();
          self.__webpack_require__ = function (id) {
          if (!moduleCache.has(id)) {
            if (/^https?\\:/.test(id)) {
              const url = new URL(id);
              url.pathname = "${
                config.base
                  ? `${config.base}/@fs/${cwd()}`.replace(/\/+/g, "/")
                  : `/@fs/${cwd()}`.replace(/\/+/g, "/")
              }" + url.pathname;
              const mod = import(/* @vite-ignore */ url.href);
              moduleCache.set(id, mod);
              return mod;
            }
          ${
            config.base
              ? `const mod = import(/* @vite-ignore */ new URL("${`${config.base}/@fs/${cwd()}/`.replace(/\/+/g, "/")}" + id, location.origin).href);`
              : `const mod = import(/* @vite-ignore */ new URL("${`/@fs/${cwd()}/`.replace(/\/+/g, "/")}" + id, location.origin).href);`
          }
          moduleCache.set(id, mod);
          return mod;
          }
          return moduleCache.get(id);
          };`;
      }

DevのReact周りのBootstrap Moduleとして使われるやつ

https://github.com/lazarv/react-server/blob/main/packages/react-server/lib/plugins/react-server-runtime.mjs