diff --git a/packages/static/src/build/buildApp.ts b/packages/static/src/build/buildApp.ts index 03d2efe..5c1108f 100644 --- a/packages/static/src/build/buildApp.ts +++ b/packages/static/src/build/buildApp.ts @@ -51,15 +51,8 @@ export async function buildApp( } // Process all deferred components once across all entries. - // We pass a dummy empty stream since we handle per-entry RSC payloads separately. - const dummyStream = new ReadableStream({ - start(controller) { - controller.close(); - }, - }); const { components, idMapping } = await processRscComponents( deferRegistry.loadAll(), - dummyStream, options.rscPayloadDir, context, ); diff --git a/packages/static/src/build/rscProcessor.test.ts b/packages/static/src/build/rscProcessor.test.ts index 11d015e..1ef06c9 100644 --- a/packages/static/src/build/rscProcessor.test.ts +++ b/packages/static/src/build/rscProcessor.test.ts @@ -7,15 +7,6 @@ function id(rawId: string): string { return `${dir}/${rawId}`; } -function streamOf(content: string): ReadableStream { - return new ReadableStream({ - start(controller) { - controller.enqueue(new TextEncoder().encode(content)); - controller.close(); - }, - }); -} - async function* componentsOf( items: Array<{ id: string; data: string; name?: string }>, ) { @@ -30,18 +21,17 @@ function referencedIds(content: string): string[] { } describe("processRscComponents", () => { - it("replaces temp IDs with content hashes in app content", async () => { + it("maps temp IDs to content hashes", async () => { const a = id("temp-a"); const result = await processRscComponents( componentsOf([{ id: a, data: "content of a" }]), - streamOf(`app references ${a}`), dir, ); expect(result.components).toHaveLength(1); const finalId = result.components[0]!.finalId; expect(finalId).not.toBe(a); - expect(result.appRscContent).toBe(`app references ${finalId}`); + expect(result.idMapping.get(a)).toBe(finalId); }); it("finalizes nested references before hashing the referencing payload", async () => { @@ -52,7 +42,6 @@ describe("processRscComponents", () => { { id: parent, data: `parent references ${child}` }, { id: child, data: "child content" }, ]), - streamOf(`app references ${parent}`), dir, ); @@ -65,8 +54,8 @@ describe("processRscComponents", () => { expect(finalIds).toContain(ref); } } - expect(result.appRscContent).not.toContain("temp-"); - expect(finalIds).toContain(referencedIds(result.appRscContent)[0]); + expect(finalIds).toContain(result.idMapping.get(parent)); + expect(finalIds).toContain(result.idMapping.get(child)); }); it("resolves a diamond of references", async () => { @@ -81,14 +70,15 @@ describe("processRscComponents", () => { { id: right, data: `right references ${bottom}` }, { id: bottom, data: "bottom content" }, ]), - streamOf(`app references ${top}`), dir, ); for (const component of result.components) { expect(component.finalContent).not.toContain("temp-"); } - expect(result.appRscContent).not.toContain("temp-"); + for (const finalId of result.idMapping.values()) { + expect(finalId).not.toContain("temp-"); + } }); it("produces the same hashes regardless of temp IDs and order", async () => { @@ -100,21 +90,17 @@ describe("processRscComponents", () => { { id: child, data: "child content" }, ]; if (flip) items.reverse(); - const result = await processRscComponents( - componentsOf(items), - streamOf(`app references ${parent}`), - dir, - ); + const result = await processRscComponents(componentsOf(items), dir); return { finalIds: new Set(result.components.map((c) => c.finalId)), - appRscContent: result.appRscContent, + parentFinalId: result.idMapping.get(parent), }; } const first = await run("temp-parent-1", "temp-child-1", false); const second = await run("temp-parent-2", "temp-child-2", true); expect(first.finalIds).toEqual(second.finalIds); - expect(first.appRscContent).toBe(second.appRscContent); + expect(first.parentFinalId).toBe(second.parentFinalId); }); it("keeps temp IDs for components in cycles and warns", async () => { @@ -126,7 +112,6 @@ describe("processRscComponents", () => { { id: a, data: `a references ${b}` }, { id: b, data: `b references ${a}` }, ]), - streamOf(`app references ${a}`), dir, { warn }, ); @@ -134,6 +119,7 @@ describe("processRscComponents", () => { expect(warn).toHaveBeenCalledTimes(1); const finalIds = result.components.map((c) => c.finalId).sort(); expect(finalIds).toEqual([a, b]); - expect(result.appRscContent).toBe(`app references ${a}`); + expect(result.idMapping.get(a)).toBe(a); + expect(result.idMapping.get(b)).toBe(b); }); }); diff --git a/packages/static/src/build/rscProcessor.ts b/packages/static/src/build/rscProcessor.ts index aeb5c5d..79cdf5f 100644 --- a/packages/static/src/build/rscProcessor.ts +++ b/packages/static/src/build/rscProcessor.ts @@ -1,4 +1,3 @@ -import { drainStream } from "../util/drainStream"; import { getPayloadIDFor } from "../rsc/rscModule"; import { computeContentHash } from "./contentHash"; import { findReferencedIds, topologicalSort } from "./dependencyGraph"; @@ -11,7 +10,6 @@ export interface ProcessedComponent { export interface ProcessResult { components: ProcessedComponent[]; - appRscContent: string; idMapping: Map; } @@ -25,13 +23,11 @@ interface RawComponent { * Processes RSC components by replacing temporary UUIDs with content-based hashes. * * @param deferRegistryIterator - Iterator yielding components with { id, data } - * @param appRscStream - The main RSC stream * @param rscPayloadDir - Directory name used as a prefix for RSC payload IDs (e.g. "fun__rsc-payload") * @param context - Optional context for logging warnings */ export async function processRscComponents( deferRegistryIterator: AsyncIterable, - appRscStream: ReadableStream, rscPayloadDir: string, context?: { warn: (message: string) => void }, ): Promise { @@ -43,21 +39,17 @@ export async function processRscComponents( componentNames.set(id, name); } - // Step 2: Drain appRsc stream to string - let appRscContent = await drainStream(appRscStream); - // If no components, return early if (components.size === 0) { return { components: [], - appRscContent, idMapping: new Map(), }; } const allIds = new Set(components.keys()); - // Step 3: Build dependency graph + // Step 2: Build dependency graph // For each component, find which other component IDs appear in its content const dependencies = new Map>(); for (const [id, content] of components) { @@ -67,10 +59,10 @@ export async function processRscComponents( dependencies.set(id, refs); } - // Step 4: Topologically sort components + // Step 3: Topologically sort components const { sorted, inCycle } = topologicalSort(dependencies); - // Step 5: Handle cycles - warn and keep original temp IDs + // Step 4: Handle cycles - warn and keep original temp IDs const idMapping = new Map(); if (inCycle.length > 0) { @@ -82,7 +74,7 @@ export async function processRscComponents( } } - // Step 6: Process components in dependency order (dependencies before + // Step 5: Process components in dependency order (dependencies before // dependents), so that every referenced component's final ID is known // before the referencing content is hashed and frozen. const processedComponents: ProcessedComponent[] = []; @@ -129,16 +121,8 @@ export async function processRscComponents( }); } - // Step 7: Process appRsc - replace all temp IDs with final IDs - for (const [oldId, newId] of idMapping) { - if (oldId !== newId) { - appRscContent = appRscContent.replaceAll(oldId, newId); - } - } - return { components: processedComponents, - appRscContent, idMapping, }; }