From 821046bdda69cfacfe36ce7c14e704548c172c9b Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Tue, 28 May 2024 14:24:45 -0600 Subject: [PATCH] Revert "Fix broken HTML inlining of non UTF-8 decodable binary data from Flight payload (#65664)" This reverts commit a34d9098779f8a144627098478d7fbbc50dd99c4. --- packages/next/src/client/app-index.tsx | 21 +---- .../next/src/server/app-render/app-render.tsx | 2 +- .../use-flight-response.edge.ts | 53 ++++-------- packages/next/src/server/base-server.ts | 73 ++++------------- .../lib/incremental-cache/fetch-cache.ts | 5 +- .../incremental-cache/file-system-cache.ts | 80 ++++++++----------- packages/next/src/server/render-result.ts | 42 +--------- .../next/src/server/response-cache/types.ts | 26 +----- .../next/src/server/response-cache/utils.ts | 24 +----- .../server/stream-utils/stream-utils.edge.ts | 31 ------- test/e2e/app-dir/binary/app/client.js | 19 ----- test/e2e/app-dir/binary/app/layout.js | 12 --- test/e2e/app-dir/binary/app/page.js | 8 -- test/e2e/app-dir/binary/next.config.js | 6 -- test/e2e/app-dir/binary/rsc-binary.test.ts | 32 -------- 15 files changed, 75 insertions(+), 359 deletions(-) delete mode 100644 test/e2e/app-dir/binary/app/client.js delete mode 100644 test/e2e/app-dir/binary/app/layout.js delete mode 100644 test/e2e/app-dir/binary/app/page.js delete mode 100644 test/e2e/app-dir/binary/next.config.js delete mode 100644 test/e2e/app-dir/binary/rsc-binary.test.ts diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index 9058c233bf5d1..f59ee8507b412 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -43,7 +43,7 @@ const appElement: HTMLElement | Document | null = document const encoder = new TextEncoder() -let initialServerDataBuffer: (string | Uint8Array)[] | undefined = undefined +let initialServerDataBuffer: string[] | undefined = undefined let initialServerDataWriter: ReadableStreamDefaultController | undefined = undefined let initialServerDataLoaded = false @@ -56,7 +56,6 @@ function nextServerDataCallback( | [isBootStrap: 0] | [isNotBootstrap: 1, responsePartial: string] | [isFormState: 2, formState: any] - | [isBinary: 3, responseBase64Partial: string] ): void { if (seg[0] === 0) { initialServerDataBuffer = [] @@ -71,22 +70,6 @@ function nextServerDataCallback( } } else if (seg[0] === 2) { initialFormStateData = seg[1] - } else if (seg[0] === 3) { - if (!initialServerDataBuffer) - throw new Error('Unexpected server data: missing bootstrap script.') - - // Decode the base64 string back to binary data. - const binaryString = atob(seg[1]) - const decodedChunk = new Uint8Array(binaryString.length) - for (var i = 0; i < binaryString.length; i++) { - decodedChunk[i] = binaryString.charCodeAt(i) - } - - if (initialServerDataWriter) { - initialServerDataWriter.enqueue(decodedChunk) - } else { - initialServerDataBuffer.push(decodedChunk) - } } } @@ -101,7 +84,7 @@ function nextServerDataCallback( function nextServerDataRegisterWriter(ctr: ReadableStreamDefaultController) { if (initialServerDataBuffer) { initialServerDataBuffer.forEach((val) => { - ctr.enqueue(typeof val === 'string' ? encoder.encode(val) : val) + ctr.enqueue(encoder.encode(val)) }) if (initialServerDataLoaded && !initialServerDataFlushed) { ctr.close() diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 654658e38bb12..1fed6681ae5a2 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -418,7 +418,7 @@ function createFlightDataResolver(ctx: AppRenderContext) { // Generate the flight data and as soon as it can, convert it into a string. const promise = generateFlight(ctx) .then(async (result) => ({ - flightData: await result.toUnchunkedBuffer(true), + flightData: await result.toUnchunkedString(true), })) // Otherwise if it errored, return the error. .catch((err) => ({ err })) diff --git a/packages/next/src/server/app-render/use-flight-response/use-flight-response.edge.ts b/packages/next/src/server/app-render/use-flight-response/use-flight-response.edge.ts index 5792b247a46a4..d743e5b93cac2 100644 --- a/packages/next/src/server/app-render/use-flight-response/use-flight-response.edge.ts +++ b/packages/next/src/server/app-render/use-flight-response/use-flight-response.edge.ts @@ -10,7 +10,6 @@ const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge' const INLINE_FLIGHT_PAYLOAD_BOOTSTRAP = 0 const INLINE_FLIGHT_PAYLOAD_DATA = 1 const INLINE_FLIGHT_PAYLOAD_FORM_STATE = 2 -const INLINE_FLIGHT_PAYLOAD_BINARY = 3 const flightResponses = new WeakMap< Readable | BinaryStreamOf, @@ -101,8 +100,10 @@ export function createInlinedDataReadableStream( ? `` + `${scriptStart}self.__next_f.push(${htmlEscapeJsonString( + JSON.stringify([INLINE_FLIGHT_PAYLOAD_DATA, chunkAsString]) + )})` ) ) } diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index efdfd1a10315f..48a5e45e64022 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -13,8 +13,6 @@ import type { ParsedUrlQuery } from 'querystring' import type { RenderOptsPartial as PagesRenderOptsPartial } from './render' import type { RenderOptsPartial as AppRenderOptsPartial } from './app-render/types' import type { - CachedAppPageValue, - CachedPageValue, ResponseCacheBase, ResponseCacheEntry, ResponseGenerator, @@ -2545,29 +2543,16 @@ export default abstract class Server< return null } - if (isAppPath) { - return { - value: { - kind: 'APP_PAGE', - html: result, - headers, - rscData: metadata.flightData, - postponed: metadata.postponed, - status: res.statusCode, - } satisfies CachedAppPageValue, - revalidate: metadata.revalidate, - } - } - // We now have a valid HTML result that we can return to the user. return { value: { kind: 'PAGE', html: result, - pageData: metadata.pageData, + pageData: metadata.pageData ?? metadata.flightData, + postponed: metadata.postponed, headers, - status: res.statusCode, - } satisfies CachedPageValue, + status: isAppPath ? res.statusCode : undefined, + }, revalidate: metadata.revalidate, } } @@ -2680,6 +2665,7 @@ export default abstract class Server< value: { kind: 'PAGE', html: RenderResult.fromStatic(html), + postponed: undefined, status: undefined, headers: undefined, pageData: {}, @@ -2748,7 +2734,7 @@ export default abstract class Server< } const didPostpone = - cacheEntry.value?.kind === 'APP_PAGE' && + cacheEntry.value?.kind === 'PAGE' && typeof cacheEntry.value.postponed === 'string' if ( @@ -2915,11 +2901,7 @@ export default abstract class Server< } else if (isAppPath) { // If the request has a postponed state and it's a resume request we // should error. - if ( - cachedData.kind === 'APP_PAGE' && - cachedData.postponed && - minimalPostponed - ) { + if (cachedData.postponed && minimalPostponed) { throw new Error( 'Invariant: postponed state should not be present on a resume request' ) @@ -2967,11 +2949,7 @@ export default abstract class Server< } // Mark that the request did postpone if this is a data request. - if ( - cachedData.kind === 'APP_PAGE' && - cachedData.postponed && - isRSCRequest - ) { + if (cachedData.postponed && isRSCRequest) { res.setHeader(NEXT_DID_POSTPONE_HEADER, '1') } @@ -2982,15 +2960,8 @@ export default abstract class Server< if (isDataReq && !isPreviewMode) { // If this is a dynamic RSC request, then stream the response. if (isDynamicRSCRequest) { - if (cachedData.kind !== 'APP_PAGE') { - console.error({ url: req.url, pathname }, cachedData) - throw new Error( - `Invariant: expected cache data kind of APP_PAGE got ${cachedData.kind}` - ) - } - - if (cachedData.rscData) { - throw new Error('Invariant: Expected rscData to be undefined') + if (cachedData.pageData) { + throw new Error('Invariant: Expected pageData to be undefined') } if (cachedData.postponed) { @@ -3009,15 +2980,9 @@ export default abstract class Server< } } - if (cachedData.kind !== 'APP_PAGE') { - throw new Error( - `Invariant: expected cached data to be APP_PAGE got ${cachedData.kind}` - ) - } - - if (!Buffer.isBuffer(cachedData.rscData)) { + if (typeof cachedData.pageData !== 'string') { throw new Error( - `Invariant: expected rscData to be a Buffer, got ${typeof cachedData.rscData}` + `Invariant: expected pageData to be a string, got ${typeof cachedData.pageData}` ) } @@ -3025,7 +2990,7 @@ export default abstract class Server< // data. return { type: 'rsc', - body: RenderResult.fromStatic(cachedData.rscData), + body: RenderResult.fromStatic(cachedData.pageData), revalidate: cacheEntry.revalidate, } } @@ -3036,10 +3001,7 @@ export default abstract class Server< // If there's no postponed state, we should just serve the HTML. This // should also be the case for a resume request because it's completed // as a server render (rather than a static render). - if ( - !(cachedData.kind === 'APP_PAGE' && cachedData.postponed) || - this.minimalMode - ) { + if (!cachedData.postponed || this.minimalMode) { return { type: 'html', body, @@ -3068,7 +3030,7 @@ export default abstract class Server< throw new Error('Invariant: expected a result to be returned') } - if (result.value?.kind !== 'APP_PAGE') { + if (result.value?.kind !== 'PAGE') { throw new Error( `Invariant: expected a page response, got ${result.value?.kind}` ) @@ -3094,11 +3056,6 @@ export default abstract class Server< revalidate: 0, } } else if (isDataReq) { - if (cachedData.kind !== 'PAGE') { - throw new Error( - `Invariant: expected cached data to be PAGE got ${cachedData.kind}` - ) - } return { type: 'json', body: RenderResult.fromStatic(JSON.stringify(cachedData.pageData)), diff --git a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts index 55d77c0f228fc..6ecb7048eda90 100644 --- a/packages/next/src/server/lib/incremental-cache/fetch-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/fetch-cache.ts @@ -109,10 +109,7 @@ export default class FetchCache implements CacheHandler { } // rough estimate of size of cache value return ( - value.html.length + - (JSON.stringify( - value.kind === 'APP_PAGE' ? value.rscData : value.pageData - )?.length || 0) + value.html.length + (JSON.stringify(value.pageData)?.length || 0) ) }, }) diff --git a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts index 379917ea2ef2a..2e8400da74e7a 100644 --- a/packages/next/src/server/lib/incremental-cache/file-system-cache.ts +++ b/packages/next/src/server/lib/incremental-cache/file-system-cache.ts @@ -77,10 +77,7 @@ export default class FileSystemCache implements CacheHandler { } // rough estimate of size of cache value return ( - value.html.length + - (JSON.stringify( - value.kind === 'APP_PAGE' ? value.rscData : value.pageData - )?.length || 0) + value.html.length + (JSON.stringify(value.pageData)?.length || 0) ) }, }) @@ -253,6 +250,23 @@ export default class FileSystemCache implements CacheHandler { } } } else { + const pageData = isAppPath + ? await this.fs.readFile( + this.getFilePath( + `${key}${ + this.isAppPPREnabled ? RSC_PREFETCH_SUFFIX : RSC_SUFFIX + }`, + 'app' + ), + 'utf8' + ) + : JSON.parse( + await this.fs.readFile( + this.getFilePath(`${key}${NEXT_DATA_SUFFIX}`, 'pages'), + 'utf8' + ) + ) + let meta: RouteMetadata | undefined if (isAppPath) { @@ -266,44 +280,16 @@ export default class FileSystemCache implements CacheHandler { } catch {} } - if (isAppPath) { - const rscData = await this.fs.readFile( - this.getFilePath( - `${key}${ - this.isAppPPREnabled ? RSC_PREFETCH_SUFFIX : RSC_SUFFIX - }`, - 'app' - ) - ) - data = { - lastModified: mtime.getTime(), - value: { - kind: 'APP_PAGE', - html: fileData, - rscData, - postponed: meta?.postponed, - headers: meta?.headers, - status: meta?.status, - }, - } - } else { - const pageData = JSON.parse( - await this.fs.readFile( - this.getFilePath(`${key}${NEXT_DATA_SUFFIX}`, 'pages'), - 'utf8' - ) - ) - - data = { - lastModified: mtime.getTime(), - value: { - kind: 'PAGE', - html: fileData, - pageData, - headers: meta?.headers, - status: meta?.status, - }, - } + data = { + lastModified: mtime.getTime(), + value: { + kind: 'PAGE', + html: fileData, + pageData, + postponed: meta?.postponed, + headers: meta?.headers, + status: meta?.status, + }, } } @@ -315,7 +301,7 @@ export default class FileSystemCache implements CacheHandler { } } - if (data?.value?.kind === 'APP_PAGE' || data?.value?.kind === 'PAGE') { + if (data?.value?.kind === 'PAGE') { let cacheTags: undefined | string[] const tagsHeader = data.value.headers?.[NEXT_CACHE_TAGS_HEADER] @@ -399,8 +385,8 @@ export default class FileSystemCache implements CacheHandler { return } - if (data?.kind === 'PAGE' || data?.kind === 'APP_PAGE') { - const isAppPath = 'rscData' in data + if (data?.kind === 'PAGE') { + const isAppPath = typeof data.pageData === 'string' const htmlPath = this.getFilePath( `${key}.html`, isAppPath ? 'app' : 'pages' @@ -419,14 +405,14 @@ export default class FileSystemCache implements CacheHandler { }`, isAppPath ? 'app' : 'pages' ), - isAppPath ? data.rscData : JSON.stringify(data.pageData) + isAppPath ? data.pageData : JSON.stringify(data.pageData) ) if (data.headers || data.status) { const meta: RouteMetadata = { headers: data.headers, status: data.status, - postponed: isAppPath ? data.postponed : undefined, + postponed: data.postponed, } await this.fs.writeFile( diff --git a/packages/next/src/server/render-result.ts b/packages/next/src/server/render-result.ts index 161689222b727..5c793bce27e14 100644 --- a/packages/next/src/server/render-result.ts +++ b/packages/next/src/server/render-result.ts @@ -2,20 +2,14 @@ import type { OutgoingHttpHeaders, ServerResponse } from 'http' import type { Revalidate } from './lib/revalidate' import type { FetchMetrics } from './base-http' -import { - chainStreams, - streamFromBuffer, - streamFromString, - streamToBuffer, - streamToString, -} from './stream-utils' +import { chainStreams, streamFromString, streamToString } from './stream-utils' import { isAbortError, pipeToNodeResponse } from './pipe-readable' import type { Readable, Writable } from 'stream' type ContentTypeOption = string | undefined export type AppPageRenderResultMetadata = { - flightData?: Uint8Array + flightData?: string revalidate?: Revalidate staticBailoutInfo?: { stack?: string @@ -55,7 +49,6 @@ export type RenderResultResponse = | ReadableStream[] | ReadableStream | string - | Uint8Array | null export type RenderResultOptions< @@ -95,7 +88,7 @@ export default class RenderResult< * @param value the static response value * @returns a new RenderResult instance */ - public static fromStatic(value: string | Buffer) { + public static fromStatic(value: string) { return new RenderResult(value, { metadata: {} }) } @@ -131,28 +124,6 @@ export default class RenderResult< return typeof this.response !== 'string' } - public toUnchunkedBuffer(stream?: false): Uint8Array - public toUnchunkedBuffer(stream: true): Promise - public toUnchunkedBuffer(stream = false): Promise | Uint8Array { - if (this.response === null) { - throw new Error('Invariant: null responses cannot be unchunked') - } - - if (typeof this.response !== 'string') { - if (!stream) { - throw new Error( - 'Invariant: dynamic responses cannot be unchunked. This is a bug in Next.js' - ) - } - - return streamToBuffer(this.readable) - } - - const encoder = new TextEncoder() - - return encoder.encode(this.response) - } - /** * Returns the response if it is a string. If the page was dynamic, this will * return a promise if the `stream` option is true, or it will throw an error. @@ -192,10 +163,6 @@ export default class RenderResult< throw new Error('Invariant: static responses cannot be streamed') } - if (this.response instanceof Uint8Array) { - return streamFromBuffer(this.response) - } - // If the response is an array of streams, then chain them together. if (Array.isArray(this.response)) { return chainStreams(...this.response) @@ -224,9 +191,6 @@ export default class RenderResult< responses = [streamFromString(this.response)] } else if (Array.isArray(this.response)) { responses = this.response - } else if (this.response instanceof Uint8Array) { - // @ts-ignore - responses = [streamFromBuffer(this.response)] } else { // @ts-ignore responses = [this.response] diff --git a/packages/next/src/server/response-cache/types.ts b/packages/next/src/server/response-cache/types.ts index 9f971825c3273..e9ca3c31b1c6c 100644 --- a/packages/next/src/server/response-cache/types.ts +++ b/packages/next/src/server/response-cache/types.ts @@ -40,22 +40,12 @@ export interface CachedRedirectValue { props: Object } -export interface CachedAppPageValue { - kind: 'APP_PAGE' - // this needs to be a RenderResult so since renderResponse - // expects that type instead of a string - html: RenderResult - rscData: Uint8Array | undefined - status: number | undefined - postponed: string | undefined - headers: OutgoingHttpHeaders | undefined -} - export interface CachedPageValue { kind: 'PAGE' // this needs to be a RenderResult so since renderResponse // expects that type instead of a string html: RenderResult + postponed: string | undefined pageData: Object status: number | undefined headers: OutgoingHttpHeaders | undefined @@ -79,23 +69,13 @@ export interface CachedImageValue { isStale?: boolean } -export interface IncrementalCachedAppPageValue { - kind: 'APP_PAGE' - // this needs to be a string since the cache expects to store - // the string value - html: string - rscData: Uint8Array | undefined - headers: OutgoingHttpHeaders | undefined - postponed: string | undefined - status: number | undefined -} - export interface IncrementalCachedPageValue { kind: 'PAGE' // this needs to be a string since the cache expects to store // the string value html: string pageData: Object + postponed: string | undefined headers: OutgoingHttpHeaders | undefined status: number | undefined } @@ -112,7 +92,6 @@ export type IncrementalCacheEntry = { export type IncrementalCacheValue = | CachedRedirectValue | IncrementalCachedPageValue - | IncrementalCachedAppPageValue | CachedImageValue | CachedFetchValue | CachedRouteValue @@ -120,7 +99,6 @@ export type IncrementalCacheValue = export type ResponseCacheValue = | CachedRedirectValue | CachedPageValue - | CachedAppPageValue | CachedImageValue | CachedRouteValue diff --git a/packages/next/src/server/response-cache/utils.ts b/packages/next/src/server/response-cache/utils.ts index 18ec64c1dd052..174d6fa619a4a 100644 --- a/packages/next/src/server/response-cache/utils.ts +++ b/packages/next/src/server/response-cache/utils.ts @@ -12,20 +12,12 @@ export async function fromResponseCacheEntry( ? { kind: 'PAGE', html: await cacheEntry.value.html.toUnchunkedString(true), + postponed: cacheEntry.value.postponed, pageData: cacheEntry.value.pageData, headers: cacheEntry.value.headers, status: cacheEntry.value.status, } - : cacheEntry.value?.kind === 'APP_PAGE' - ? { - kind: 'APP_PAGE', - html: await cacheEntry.value.html.toUnchunkedString(true), - postponed: cacheEntry.value.postponed, - rscData: cacheEntry.value.rscData, - headers: cacheEntry.value.headers, - status: cacheEntry.value.status, - } - : cacheEntry.value, + : cacheEntry.value, } } @@ -50,18 +42,10 @@ export async function toResponseCacheEntry( kind: 'PAGE', html: RenderResult.fromStatic(response.value.html), pageData: response.value.pageData, + postponed: response.value.postponed, headers: response.value.headers, status: response.value.status, } - : response.value?.kind === 'APP_PAGE' - ? { - kind: 'APP_PAGE', - html: RenderResult.fromStatic(response.value.html), - rscData: response.value.rscData, - headers: response.value.headers, - status: response.value.status, - postponed: response.value.postponed, - } - : response.value, + : response.value, } } diff --git a/packages/next/src/server/stream-utils/stream-utils.edge.ts b/packages/next/src/server/stream-utils/stream-utils.edge.ts index ff107099ac168..b77d122a32c93 100644 --- a/packages/next/src/server/stream-utils/stream-utils.edge.ts +++ b/packages/next/src/server/stream-utils/stream-utils.edge.ts @@ -84,37 +84,6 @@ export function streamFromString(str: string): ReadableStream { }) } -export function streamFromBuffer( - chunk: Uint8Array -): ReadableStream { - return new ReadableStream({ - start(controller) { - controller.enqueue(chunk) - controller.close() - }, - }) -} - -export async function streamToBuffer( - stream: ReadableStream -): Promise { - let chunks = [] - let byteLength = 0 - // @ts-expect-error - for await (const chunk of stream) { - chunks.push(chunk) - byteLength += chunk.length - } - - const buffer: Uint8Array = new Uint8Array(byteLength) - for (let i = 0, byteOffset = 0; i < chunks.length; i++) { - buffer.set(chunks[i], byteOffset) - byteOffset += chunks[i].byteLength - } - - return buffer -} - export async function streamToString( stream: ReadableStream ): Promise { diff --git a/test/e2e/app-dir/binary/app/client.js b/test/e2e/app-dir/binary/app/client.js deleted file mode 100644 index 37f29ac669dc4..0000000000000 --- a/test/e2e/app-dir/binary/app/client.js +++ /dev/null @@ -1,19 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' - -export function Client({ binary, arbitrary }) { - const [hydrated, setHydrated] = useState(false) - - useEffect(() => { - setHydrated(true) - }, []) - - return ( - <> -
utf8 binary: {new TextDecoder().decode(binary)}
-
arbitrary binary: {String(arbitrary)}
-
hydrated: {String(hydrated)}
- - ) -} diff --git a/test/e2e/app-dir/binary/app/layout.js b/test/e2e/app-dir/binary/app/layout.js deleted file mode 100644 index 8525f5f8c0b2a..0000000000000 --- a/test/e2e/app-dir/binary/app/layout.js +++ /dev/null @@ -1,12 +0,0 @@ -export const metadata = { - title: 'Next.js', - description: 'Generated by Next.js', -} - -export default function RootLayout({ children }) { - return ( - - {children} - - ) -} diff --git a/test/e2e/app-dir/binary/app/page.js b/test/e2e/app-dir/binary/app/page.js deleted file mode 100644 index 5bbd22eedea98..0000000000000 --- a/test/e2e/app-dir/binary/app/page.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Client } from './client' - -export default function Page() { - const binaryData = new Uint8Array([104, 101, 108, 108, 111]) - const nonUtf8BinaryData = new Uint8Array([0xff, 0, 1, 2, 3]) - - return -} diff --git a/test/e2e/app-dir/binary/next.config.js b/test/e2e/app-dir/binary/next.config.js deleted file mode 100644 index 5b7ed7e24f002..0000000000000 --- a/test/e2e/app-dir/binary/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - experimental: { - // This ensures that we're running the experimental React. - taint: true, - }, -} diff --git a/test/e2e/app-dir/binary/rsc-binary.test.ts b/test/e2e/app-dir/binary/rsc-binary.test.ts deleted file mode 100644 index 4f29a0a3d5942..0000000000000 --- a/test/e2e/app-dir/binary/rsc-binary.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { nextTestSetup } from 'e2e-utils' -import { check } from 'next-test-utils' - -describe('RSC binary serialization', () => { - const { next, skipped } = nextTestSetup({ - files: __dirname, - skipDeployment: true, - dependencies: { - react: '19.0.0-beta-4508873393-20240430', - 'react-dom': '19.0.0-beta-4508873393-20240430', - 'server-only': 'latest', - }, - }) - if (skipped) return - - afterEach(async () => { - await next.stop() - }) - - it('should correctly encode/decode binaries and hydrate', async function () { - const browser = await next.browser('/') - await check(async () => { - const content = await browser.elementByCss('body').text() - - return content.includes('utf8 binary: hello') && - content.includes('arbitrary binary: 255,0,1,2,3') && - content.includes('hydrated: true') - ? 'success' - : 'fail' - }, 'success') - }) -})