Skip to content

Commit f4672f4

Browse files
committed
refactor(rsc): load client reference without vite preload
1 parent 6728273 commit f4672f4

13 files changed

Lines changed: 181 additions & 58 deletions

File tree

packages/react-server/src/features/client-component/browser.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ async function importWrapper(id: string) {
1616
}
1717

1818
export function initializeReactClientBrowser() {
19-
ReactClient.setRequireModule({ load: importWrapper });
19+
ReactClient.setRequireModule({
20+
load: (payload) => importWrapper(payload.id),
21+
});
2022
}

packages/react-server/src/features/client-component/ssr.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async function ssrImport(id: string) {
2424

2525
export function initializeReactClientSsr() {
2626
ReactClient.setRequireModule({
27-
load: ssrImport,
27+
load: (payload) => ssrImport(payload.id),
2828
});
2929
}
3030

packages/rsc/examples/basic-core/src/browser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { RscPayload } from "./rsc";
77
async function main() {
88
ReactClient.setRequireModule({
99
load(id) {
10-
return import(/* @vite-ignore */ id);
10+
return import(/* @vite-ignore */ id.id);
1111
},
1212
});
1313

packages/rsc/examples/basic-core/src/ssr.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export async function renderHtml({
1313
}: { url: URL; stream: ReadableStream; formState?: ReactFormState }) {
1414
ReactClient.setRequireModule({
1515
load(id) {
16-
return import(/* @vite-ignore */ id);
16+
return import(/* @vite-ignore */ id.id);
1717
},
1818
});
1919

packages/rsc/examples/basic/e2e/basic.test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from "node:fs";
1+
import { createHash } from "node:crypto";
22
import { type Page, expect, test } from "@playwright/test";
33
import {
44
createEditor,
@@ -70,13 +70,18 @@ testNoJs("module preload on ssr @build", async ({ page }) => {
7070
const srcs = await page
7171
.locator(`head >> link[rel="modulepreload"]`)
7272
.evaluateAll((elements) => elements.map((el) => el.getAttribute("href")));
73-
const viteManifest = JSON.parse(
74-
fs.readFileSync("dist/client/.vite/manifest.json", "utf-8"),
75-
);
76-
const file =
77-
(process.env.TEST_BASE ? "/custom-base/" : "/") +
78-
viteManifest["src/routes/client.tsx"].file;
79-
expect(srcs).toContain(file);
73+
const { default: manifest } = await import(
74+
"../dist/client/__vite_rsc_assets_manifest.js" as any
75+
);
76+
function hashString(v: string) {
77+
return createHash("sha256").update(v).digest().toString("hex").slice(0, 12);
78+
}
79+
const deps =
80+
manifest.clientReferenceManifest[hashString("src/routes/client.tsx")];
81+
const hrefs = deps.js.map(
82+
(href: string) => (process.env.TEST_BASE ? "/custom-base" : "") + href,
83+
);
84+
expect(srcs).toEqual(expect.arrayContaining(hrefs));
8085
});
8186

8287
test("server reference update @dev @js", async ({ page }) => {

packages/rsc/examples/react-router/e2e/basic.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from "node:fs";
1+
import { createHash } from "node:crypto";
22
import { expect, test } from "@playwright/test";
33
import {
44
createEditor,
@@ -42,11 +42,15 @@ testNoJs("ssr modulepreload @build", async ({ page }) => {
4242
const srcs = await page
4343
.locator(`head >> link[rel="modulepreload"]`)
4444
.evaluateAll((elements) => elements.map((el) => el.getAttribute("href")));
45-
const viteManifest = JSON.parse(
46-
fs.readFileSync("dist/client/.vite/manifest.json", "utf-8"),
45+
const { default: manifest } = await import(
46+
"../dist/client/__vite_rsc_assets_manifest.js" as any
4747
);
48-
const file = "/" + viteManifest["app/routes/home.client.tsx"].file;
49-
expect(srcs).toContain(file);
48+
function hashString(v: string) {
49+
return createHash("sha256").update(v).digest().toString("hex").slice(0, 12);
50+
}
51+
const deps =
52+
manifest.clientReferenceManifest[hashString("app/routes/home.client.tsx")];
53+
expect(srcs).toEqual(expect.arrayContaining(deps.js));
5054
});
5155

5256
test("client hmr @dev", async ({ page }) => {

packages/rsc/src/browser.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
import * as clientReferences from "virtual:vite-rsc/client-references";
21
import { setRequireModule } from "./core/browser";
32
import { withBase } from "./utils/base";
43

54
export * from "./react/browser";
65

6+
// @ts-ignore
7+
import { __vitePreload } from "virtual:vite-rsc/preload-helper";
8+
79
export function initialize(options?: {
810
onHmrReload?: () => void;
911
}): void {
1012
setRequireModule({
11-
load: async (id) => {
13+
load: async (payload) => {
14+
const id = payload.id;
1215
if (import.meta.env.DEV) {
1316
// @ts-ignore
1417
return __vite_rsc_raw_import__(withBase(id));
1518
} else {
16-
const import_ = clientReferences.default[id];
17-
if (!import_) {
18-
throw new Error(`client reference not found '${id}'`);
19-
}
20-
return import_();
19+
return __vitePreload(
20+
() => import(/* @vite-ignore */ withBase(id)),
21+
// base for deps is handled by `__vitePreload`
22+
// TODO: refactor base handling to be consistent?
23+
[...payload.js, ...payload.css].map((href) => href.slice(1)),
24+
);
2125
}
2226
},
2327
});

packages/rsc/src/core/browser.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { memoize } from "@hiogawa/utils";
2-
import { removeReferenceCacheTag, setInternalRequire } from "./shared";
2+
import type { ClientReferencePayload } from "./rsc";
3+
import { setInternalRequire } from "./shared";
34

45
let init = false;
56

67
export function setRequireModule(options: {
7-
load: (id: string) => Promise<unknown>;
8+
load: (payload: ClientReferencePayload) => Promise<unknown>;
89
}): void {
910
if (init) return;
1011
init = true;
1112

1213
const requireModule = memoize((id: string) => {
13-
return options.load(removeReferenceCacheTag(id));
14+
return options.load(JSON.parse(id));
1415
});
1516

1617
(globalThis as any).__vite_rsc_client_require__ = requireModule;

packages/rsc/src/core/rsc.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,18 @@ export function createServerManifest(): BundlerConfig {
5555
}
5656

5757
export function createClientManifest(): BundlerConfig {
58-
const cacheTag = import.meta.env.DEV ? createReferenceCacheTag() : "";
58+
const cacheTag = import.meta.env.DEV ? createReferenceCacheTag() : undefined;
5959

6060
return new Proxy(
6161
{},
6262
{
6363
get(_target, $$id, _receiver) {
6464
tinyassert(typeof $$id === "string");
65-
let [id, name] = $$id.split("#");
66-
tinyassert(id);
65+
let [key, name] = $$id.split("#");
66+
tinyassert(key);
6767
tinyassert(name);
6868
return {
69-
id: id + cacheTag,
69+
id: JSON.stringify({ id: key, cacheTag, ...manifest[key], key }),
7070
name,
7171
chunks: [],
7272
async: true,
@@ -75,3 +75,20 @@ export function createClientManifest(): BundlerConfig {
7575
},
7676
);
7777
}
78+
79+
export type ClientReferencePayload = {
80+
key?: string;
81+
id: string;
82+
js: string[];
83+
css: string[];
84+
};
85+
86+
export type ClientReferenceManifest = Record<string, ClientReferencePayload>;
87+
88+
let manifest: ClientReferenceManifest = {};
89+
90+
export function setClientReferenceManifest(
91+
manifest_: ClientReferenceManifest,
92+
): void {
93+
manifest = manifest_;
94+
}

packages/rsc/src/core/ssr.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { memoize } from "@hiogawa/utils";
22
import type { ServerConsumerManifest } from "../types";
3-
import { removeReferenceCacheTag, setInternalRequire } from "./shared";
3+
import type { ClientReferencePayload } from "./rsc";
4+
import { setInternalRequire } from "./shared";
45

56
let init = false;
67

78
export function setRequireModule(options: {
8-
load: (id: string) => unknown;
9+
load: (payload: ClientReferencePayload) => unknown;
910
}): void {
1011
if (init) return;
1112
init = true;
1213

1314
const requireModule = memoize((id: string) => {
14-
return options.load(removeReferenceCacheTag(id));
15+
return options.load(JSON.parse(id));
1516
});
1617

1718
const clientRequire = (id: string) => {

0 commit comments

Comments
 (0)