`lazarv/react-server` の実装を雑に読む
HonoベースでRSCが利用出来るフレームワークをつくろうとしてるので、参考にさせてもらうために読みつつメモする
Vercelに興味はないのでこのPackageは必要に駆られたら読む
こっちもいったんDeployツールのCoreっぽいのでいったん後回し
MicroFrontend周りは現状実装予定がないので後回しにする
File-system based router は実装方法がいくつかあるのでこれも後回し
use cache
もまだNext.jsすらなんとも言えないバランス見極め期っぽい感じがしてるので後回し
外に露出する部分だけd.tsで型定義書いてそうなので若干実際にコードに落とし込むときに考える必要はありそう & bunとかいろいろ対応していてすげぇけどいったんNodeだけにするのでsys.mjsはあんまり深く見ないでよさそう
Vite / Rollup Plugin系から見ていく
Viteの /@fs/
を使ってexport defaultされているものを直接のexportに変えてる?
react-server.config.jsonとかが別ファイルとして使えるので、それを読み込んでマージするやつ
依存関係の最適化っぽく、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 }
が謎過ぎる
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がない
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として使われるやつ
一番何のためにあるかがわかっていないやつ
多分Monorepo関係のリアルパスの解決用な感じはしつつ、実際なんなのかと言われると自信がない
bareImportREがworkspace:example系のpackageimportにもあたるのでそれっぽさを感じては居る
serverのindexとして利用されるモジュールの解決用のpluginっぽい
use cache
の解決用のやつなのでいったんskip