RSCの出力コードを読む
2年ぐらい RSC キャッチアップしてなかったので素朴に動かすところから。
$ npx create-next-app@latest rsc-research
$ cd rsc-research
CSS 周りを剥がしてほぼ最小状態にする
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default function Home() {
return (
<main>
<h1>Home</h1>
</main>
)
}
Server Component を足してみる
export default function Foo() {
return <div>Foo</div>
}
page.tsx からマウントする
import Foo from "./Foo";
export default function Home() {
return (
<main>
<h1>Home</h1>
<Foo />
</main>
)
}
next dev で動作確認。動いた。
追記: remix と混同していた。 .server の拡張子はいらない
ここから出力コードを読んでいく。一旦読みやすいように next を dev ビルドにする
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: {
dev: true
}
}
module.exports = nextConfig
ビルド
$ npx next buid
.next 以下の出力から Foo のラベルがどこに吐かれてるか確認する。
$ ag -l Foo .next/
.next/trace
.next/server/app/index.html
.next/server/app/page.js
.next/server/app/index.rsc
それぞれの中身を確認すると、ここにあった。
var jsx_runtime_ = __webpack_require__(6786);
;// CONCATENATED MODULE: ./src/app/Foo.server.tsx
function Foo() {
return /*#__PURE__*/ jsx_runtime_.jsx("div", {
children: "Foo"
});
}
;// CONCATENATED MODULE: ./src/app/page.tsx
function Home() {
return /*#__PURE__*/ (0,jsx_runtime_.jsxs)("main", {
children: [
/*#__PURE__*/ jsx_runtime_.jsx("h1", {
children: "Home"
}),
/*#__PURE__*/ jsx_runtime_.jsx(Foo, {})
]
});
}
ここにだけ Foo の実装が存在している
もうちょっとサーバーコンポーネントにしかできないことをやってみる
// should not be bundled in the client bundle
const keyboardCat = 'esoahisaidatshdioeasthdietshoa';
async function getAsyncValue( ) {
await new Promise(r => setTimeout(r, 100));
return keyboardCat.length;
}
export default async function Foo() {
const value = await getAsyncValue();
return <div>Foo: {value}</div>
}
- 従来の React ではできなかった async な関数コンポーネント
- クライアントからは keyboardCat の文字列の中身を知ることはできず、その文字列長だけがわかるとする
出力場所は特に変わらず
$ ag --ignore .next/trace -l Foo .next/
.next/trace
.next/server/app/index.html
.next/server/app/page.js
.next/server/app/index.rsc
中身を確認。
// EXTERNAL MODULE: external "next/dist/compiled/react/jsx-runtime"
var jsx_runtime_ = __webpack_require__(6786);
;// CONCATENATED MODULE: ./src/app/Foo.server.tsx
// should not be bundled in the client bundle
const keyboardCat = "esoahisaidatshdioeasthdietshoa";
async function getAsyncValue() {
await new Promise((r)=>setTimeout(r, 100));
return keyboardCat.length;
}
async function Foo() {
const value = await getAsyncValue();
return /*#__PURE__*/ (0,jsx_runtime_.jsxs)("div", {
children: [
"Foo: ",
value
]
});
}
;// CONCATENATED MODULE: ./src/app/page.tsx
function Home() {
return /*#__PURE__*/ (0,jsx_runtime_.jsxs)("main", {
children: [
/*#__PURE__*/ jsx_runtime_.jsx("h1", {
children: "Home"
}),
/*#__PURE__*/ jsx_runtime_.jsx(Foo, {})
]
});
}
特に非同期コンポーネントが加工されてるわけではなく、そのまま jsx_runtime_ に渡されている。これは誰が render している?
一応 keyboardCat の文字列が .next/server 以外には漏れていないことを確認
$ ag --ignore .next/trace -l esoahisaidatshdioeasthdietshoa .next/
.next/server/app/page.js
おk
.next/server の下で recat-dom で grep するとこの辺が引っかかる
"use strict";
module.exports = require("next/dist/compiled/react-dom/server-rendering-stub");
react-dom/server-rendering-stub というものを呼んでいるっぽい。ここからは react-dom 側のソースコードを読んだほうがよさそう。
これっぽい。
// on the server we just call the callback because there is
// not update mechanism. Really this should not be called on the
// server but since the semantics are generally clear enough we
// can provide this trivial implementation.
function batchedUpdates<A, R>(fn: A => R, a: A): R {
return fn(a);
}
export {batchedUpdates as unstable_batchedUpdates};
これは本当に stub で、1回しか実行しないから他のものに処理が委譲されている?
コッチが本体だろうか
"use strict";
module.exports = require("next/dist/compiled/react-server-dom-webpack/client");
っぽい。
この記事見つけたので先に読む https://zenn.dev/uhyo/books/rsc-without-nextjs
雑に理解した。先回りして上記のリポジトリのこのコードを手元で動かしてみる https://github.com/uhyo/rsc-without-nextjs/tree/step6
RSC のライブラリとしての実体はやはり react-server-dom-webpack らしい。直接の依存ではなく、next パッケージとして prubuid されている。
$ ag --ignore .next/trace -l react-server-dom-webpack .next/ node_modules/
.next/cache/next-server.js.nft.json
.next/server/app/page.js
.next/server/app/page.js.nft.json
.next/server/chunks/758.js
.next/static/chunks/698-1321e6d13d35448d.js
.next/next-server.js.nft.json
node_modules/next/dist/esm/server/app-render/use-flight-response.js
node_modules/next/dist/esm/server/app-render/entry-base.js
node_modules/next/dist/esm/server/app-render/create-server-components-renderer.js
node_modules/next/dist/esm/server/app-render/action-handler.js
node_modules/next/dist/esm/server/require-hook.js
node_modules/next/dist/esm/build/webpack/plugins/nextjs-require-cache-hot-reloader.js
node_modules/next/dist/esm/build/webpack/loaders/next-flight-loader/action-client-wrapper.js
node_modules/next/dist/esm/build/webpack/loaders/next-flight-loader/module-proxy.js
node_modules/next/dist/esm/build/webpack-config.js
node_modules/next/dist/esm/client/components/router-reducer/fetch-server-response.js
node_modules/next/dist/esm/client/components/router-reducer/reducers/server-action-reducer.js
node_modules/next/dist/esm/client/components/react-dev-overlay/internal/helpers/group-stack-frames-by-framework.js
node_modules/next/dist/esm/client/app-index.js
node_modules/next/dist/server/app-render/use-flight-response.js
node_modules/next/dist/server/app-render/entry-base.js
node_modules/next/dist/server/app-render/create-server-components-renderer.js
node_modules/next/dist/server/app-render/action-handler.js
node_modules/next/dist/server/require-hook.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/node-register.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/server.edge.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/client.edge.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/server.node.unbundled.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/server.browser.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/index.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/client.node.unbundled.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/client.browser.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/package.json
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/client.node.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/server.node.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/plugin.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.node.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.browser.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-node-register.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.browser.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.node.unbundled.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.node.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.node.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.edge.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.node.unbundled.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.node.unbundled.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-plugin.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.node.development.js
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.edge.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/node-register.js
node_modules/next/dist/compiled/react-server-dom-webpack/server.edge.js
node_modules/next/dist/compiled/react-server-dom-webpack/client.edge.js
node_modules/next/dist/compiled/react-server-dom-webpack/server.node.unbundled.js
node_modules/next/dist/compiled/react-server-dom-webpack/server.browser.js
node_modules/next/dist/compiled/react-server-dom-webpack/index.js
node_modules/next/dist/compiled/react-server-dom-webpack/client.node.unbundled.js
node_modules/next/dist/compiled/react-server-dom-webpack/client.browser.js
node_modules/next/dist/compiled/react-server-dom-webpack/package.json
node_modules/next/dist/compiled/react-server-dom-webpack/server.node.js
node_modules/next/dist/compiled/react-server-dom-webpack/plugin.js
node_modules/next/dist/compiled/react-server-dom-webpack/client.node.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-node-register.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.browser.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.unbundled.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.unbundled.production.min.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-plugin.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.node.development.js
node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.edge.development.js
node_modules/next/dist/build/webpack/plugins/nextjs-require-cache-hot-reloader.js
node_modules/next/dist/build/webpack/loaders/next-flight-loader/module-proxy.js
node_modules/next/dist/build/webpack/loaders/next-flight-loader/action-client-wrapper.js
node_modules/next/dist/build/webpack-config.js
node_modules/next/dist/client/components/router-reducer/fetch-server-response.js
node_modules/next/dist/client/components/router-reducer/reducers/server-action-reducer.js
node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/group-stack-frames-by-framework.js
node_modules/next/dist/client/app-index.js
見る限り -experimental 版もある。
何か next 用に改変してるわけではなく、サーバーとクライアント用で事前ビルドしたものを直接コミットしてるっぽい。ある種のビルド高速化。
next内で使われている最新のバージョンを確認
一旦 rsc-without-nextjs が半年前なのでこのバージョンに上げて手元で動かしてみる
動かせないのでコード読んでた。
内部プロトコルはたぶんこの辺。
ServerActions を使ってみる
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
}
'use server';
const keyboardCat = 'euoaueoaueoauoeathitnosaiseoadiu';
export async function asyncAction() {
return keyboardCat.length;
}
これをクライアントから呼ぶ。
'use client';
import { useCallback } from 'react';
import { asyncAction } from './actions';
export default function ClientComponent() {
const onClick = useCallback(() => {
(async () => {
const value = await asyncAction();
console.log('value', value);
})();
}, []);
return (
<button
onClick={onClick}
>
Call server function
</button>
)
}
page.tsx から呼ぶ
import ClientComponent from "./Client";
import Foo from "./Foo";
export default function Home() {
return (
<main>
<h1>Home</h1>
<Foo />
<ClientComponent />
</main>
)
}
動いた
クライアントに漏れてないことを確認
$ ag --ignore .next/trace -l euoaueoaueoauoeathitnosaiseoadiu .next/
.next/server/app/page.js