From 7067bc27de4a31c8bf53103a9e854513c0dbbb22 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sun, 11 Sep 2022 21:24:46 +0200 Subject: [PATCH 1/6] Add failing case for location throw --- .../loading-bug/[categorySlug]/loading.tsx | 3 +++ .../[categorySlug]/page.server.tsx | 25 +++++++++++++++++++ test/e2e/app-dir/app/tsconfig.json | 20 +++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.tsx create mode 100644 test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx create mode 100644 test/e2e/app-dir/app/tsconfig.json diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.tsx b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.tsx new file mode 100644 index 000000000000000..f1ca6af341511b2 --- /dev/null +++ b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return

Loading...

+} diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx new file mode 100644 index 000000000000000..0ea7605933e3d86 --- /dev/null +++ b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx @@ -0,0 +1,25 @@ +// @ts-ignore +import { experimental_use as use } from 'react' + +const fetchCategory = async (categorySlug: string): Promise<{}> => { + // artificial delay + await new Promise((resolve) => setTimeout(resolve, 3000)) + + return categorySlug + 'abc' +} + +export default function Page({ + params, +}: { + params: { [key: string]: string } +}) { + const category = use(fetchCategory(params.categorySlug)) + + return ( + <> +
+
{category}
+
+ + ) +} diff --git a/test/e2e/app-dir/app/tsconfig.json b/test/e2e/app-dir/app/tsconfig.json new file mode 100644 index 000000000000000..1563f3e878573de --- /dev/null +++ b/test/e2e/app-dir/app/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} From 43f924387be151fec74d4e2d758ee089d5dcddbf Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sun, 11 Sep 2022 21:31:17 +0200 Subject: [PATCH 2/6] Simplify --- .../app/app/loading-bug/[categorySlug]/page.server.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx index 0ea7605933e3d86..5727e6fe3e1aa28 100644 --- a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx +++ b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx @@ -15,11 +15,5 @@ export default function Page({ }) { const category = use(fetchCategory(params.categorySlug)) - return ( - <> -
-
{category}
-
- - ) + return <>{category} } From b22bbf10faba4084ccebea8ff058a3bb3bcd08a0 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 12 Sep 2022 12:08:08 +0200 Subject: [PATCH 3/6] Fix sharing of context between requests. Optimize inline flight data --- packages/next/client/app-index.tsx | 8 ++- packages/next/server/app-render.tsx | 95 +++++++++++++++-------------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index a1631e60685a0a3..84fa2cb088d5ce9 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -56,7 +56,9 @@ let initialServerDataWriter: ReadableStreamDefaultController | undefined = let initialServerDataLoaded = false let initialServerDataFlushed = false -function nextServerDataCallback(seg: [number, string, string]) { +function nextServerDataCallback( + seg: [isBootStrap: 0] | [isNotBootstrap: 1, responsePartial: string] +): void { if (seg[0] === 0) { initialServerDataBuffer = [] } else { @@ -64,9 +66,9 @@ function nextServerDataCallback(seg: [number, string, string]) { throw new Error('Unexpected server data: missing bootstrap script.') if (initialServerDataWriter) { - initialServerDataWriter.enqueue(encoder.encode(seg[2])) + initialServerDataWriter.enqueue(encoder.encode(seg[1])) } else { - initialServerDataBuffer.push(seg[2]) + initialServerDataBuffer.push(seg[1]) } } } diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 7fe57797fb4c157..c32cc4cfde53f4e 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -54,8 +54,6 @@ function interopDefault(mod: any) { return mod.default || mod } -const rscCache = new Map() - // Shadowing check does not work with TypeScript enums // eslint-disable-next-line no-shadow const enum RecordStatus { @@ -137,54 +135,58 @@ function useFlightResponse( cachePrefix: string, req: ReadableStream, serverComponentManifest: any, + flightResponseRef: { + current: ReturnType | null + }, nonce?: string ) { + if (flightResponseRef.current) { + return flightResponseRef.current + } const id = cachePrefix + ',' + (React as any).useId() - let entry = rscCache.get(id) - if (!entry) { - const [renderStream, forwardStream] = readableStreamTee(req) - entry = createFromReadableStream(renderStream, { - moduleMap: serverComponentManifest.__ssr_module_mapping__, - }) - rscCache.set(id, entry) - - let bootstrapped = false - // We only attach CSS chunks to the inlined data. - const forwardReader = forwardStream.getReader() - const writer = writable.getWriter() - const startScriptTag = nonce - ? `` - ) + + const [renderStream, forwardStream] = readableStreamTee(req) + flightResponseRef.current = createFromReadableStream(renderStream, { + moduleMap: serverComponentManifest.__ssr_module_mapping__, + }) + + let bootstrapped = false + // We only attach CSS chunks to the inlined data. + const forwardReader = forwardStream.getReader() + const writer = writable.getWriter() + const startScriptTag = nonce + ? `` ) - } - if (done) { - rscCache.delete(id) - writer.close() - } else { - const responsePartial = decodeText(value) - const scripts = `${startScriptTag}(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString( - JSON.stringify([1, id, responsePartial]) - )})` - - writer.write(encodeText(scripts)) - process() - } - }) - } - process() + ) + } + if (done) { + flightResponseRef.current = null + writer.close() + } else { + const responsePartial = decodeText(value) + const scripts = `${startScriptTag}(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString( + JSON.stringify([1, responsePartial]) + )})` + + writer.write(encodeText(scripts)) + process() + } + }) } - return entry + process() + + return flightResponseRef.current } /** @@ -240,6 +242,8 @@ function createServerComponentRenderer( return RSCStream } + const flightResponseRef = { current: null } + const writable = transformStream.writable return function ServerComponentWrapper() { const reqStream = createRSCStream() @@ -248,6 +252,7 @@ function createServerComponentRenderer( cachePrefix, reqStream, serverComponentManifest, + flightResponseRef, nonce ) return response.readRoot() From 90c284fc6452e4b423fca3f7000304d19d8c1037 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 12 Sep 2022 12:10:49 +0200 Subject: [PATCH 4/6] Remove ts from test --- .../{loading.tsx => loading.js} | 0 .../{page.server.tsx => page.server.js} | 8 ++------ test/e2e/app-dir/app/tsconfig.json | 20 ------------------- 3 files changed, 2 insertions(+), 26 deletions(-) rename test/e2e/app-dir/app/app/loading-bug/[categorySlug]/{loading.tsx => loading.js} (100%) rename test/e2e/app-dir/app/app/loading-bug/[categorySlug]/{page.server.tsx => page.server.js} (63%) delete mode 100644 test/e2e/app-dir/app/tsconfig.json diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.tsx b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.js similarity index 100% rename from test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.tsx rename to test/e2e/app-dir/app/app/loading-bug/[categorySlug]/loading.js diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js similarity index 63% rename from test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx rename to test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js index 5727e6fe3e1aa28..dbeeb5ba216c9bb 100644 --- a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.tsx +++ b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js @@ -1,18 +1,14 @@ // @ts-ignore import { experimental_use as use } from 'react' -const fetchCategory = async (categorySlug: string): Promise<{}> => { +const fetchCategory = async (categorySlug) => { // artificial delay await new Promise((resolve) => setTimeout(resolve, 3000)) return categorySlug + 'abc' } -export default function Page({ - params, -}: { - params: { [key: string]: string } -}) { +export default function Page({ params }) { const category = use(fetchCategory(params.categorySlug)) return <>{category} diff --git a/test/e2e/app-dir/app/tsconfig.json b/test/e2e/app-dir/app/tsconfig.json deleted file mode 100644 index 1563f3e878573de..000000000000000 --- a/test/e2e/app-dir/app/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "incremental": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve" - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] -} From d394165e314b152f61b1f30c3f1a4fc5aaca73b5 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 12 Sep 2022 13:00:22 +0200 Subject: [PATCH 5/6] Fix lint --- packages/next/server/app-render.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index c32cc4cfde53f4e..c24206ba2420561 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -132,7 +132,6 @@ function preloadDataFetchingRecord( */ function useFlightResponse( writable: WritableStream, - cachePrefix: string, req: ReadableStream, serverComponentManifest: any, flightResponseRef: { @@ -143,7 +142,6 @@ function useFlightResponse( if (flightResponseRef.current) { return flightResponseRef.current } - const id = cachePrefix + ',' + (React as any).useId() const [renderStream, forwardStream] = readableStreamTee(req) flightResponseRef.current = createFromReadableStream(renderStream, { @@ -202,12 +200,10 @@ function createServerComponentRenderer( } }, { - cachePrefix, transformStream, serverComponentManifest, serverContexts, }: { - cachePrefix: string transformStream: TransformStream serverComponentManifest: NonNullable serverContexts: Array< @@ -249,7 +245,6 @@ function createServerComponentRenderer( const reqStream = createRSCStream() const response = useFlightResponse( writable, - cachePrefix, reqStream, serverComponentManifest, flightResponseRef, @@ -1115,7 +1110,6 @@ export async function renderToHTMLOrFlight( }, ComponentMod, { - cachePrefix: initialCanonicalUrl, transformStream: serverComponentsInlinedTransformStream, serverComponentManifest, serverContexts, From 1df1b458c605c63553fafc11ee5d359a01b03f57 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 12 Sep 2022 13:50:31 +0200 Subject: [PATCH 6/6] Add test for failing case --- .../app/loading-bug/[categorySlug]/page.server.js | 2 +- test/e2e/app-dir/index.test.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js index dbeeb5ba216c9bb..9352955a50433cc 100644 --- a/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js +++ b/test/e2e/app-dir/app/app/loading-bug/[categorySlug]/page.server.js @@ -11,5 +11,5 @@ const fetchCategory = async (categorySlug) => { export default function Page({ params }) { const category = use(fetchCategory(params.categorySlug)) - return <>{category} + return
{category}
} diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index 4f98910d63c3a7f..5ff7ab372e43245 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -1417,6 +1417,21 @@ describe('app dir', () => { expect(errors).toInclude('Error during SSR') }) }) + + describe('known bugs', () => { + it('should not share flight data between requests', async () => { + const fetches = await Promise.all( + [...new Array(5)].map(() => + renderViaHTTP(next.url, '/loading-bug/electronics') + ) + ) + + for (const text of fetches) { + const $ = cheerio.load(text) + expect($('#category-id').text()).toBe('electronicsabc') + } + }) + }) } describe('without assetPrefix', () => {