diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index 2e51e8277210..b7ca2b3d227a 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -169,7 +169,10 @@ function createLoadFlightCssStream(callback?: () => Promise) { const loadCssFromStreamData = (data: string) => { if (data.startsWith('CSS')) { const cssJson = data.slice(4).trim() - if (!loadedCss.has(cssJson)) cssLoadingPromises.push(loadCss(cssJson)) + if (!loadedCss.has(cssJson)) { + loadedCss.add(cssJson) + cssLoadingPromises.push(loadCss(cssJson)) + } } } @@ -177,23 +180,26 @@ function createLoadFlightCssStream(callback?: () => Promise) { let buffer = '' const loadCssFromFlight = new TransformStream({ transform(chunk, controller) { + const process = (buf: string) => { + if (buf) { + if (buf.startsWith('CSS:')) { + loadCssFromStreamData(buf) + } else { + controller.enqueue(new TextEncoder().encode(buf)) + } + } + } + const data = new TextDecoder().decode(chunk) buffer += data let index while ((index = buffer.indexOf('\n')) !== -1) { const line = buffer.slice(0, index + 1) buffer = buffer.slice(index + 1) - if (line.startsWith('CSS:')) { - loadCssFromStreamData(line) - } else { - controller.enqueue(new TextEncoder().encode(line)) - } - } - - if (buffer && !buffer.startsWith('CSS:')) { - controller.enqueue(new TextEncoder().encode(buffer)) - buffer = '' + process(line) } + process(buffer) + buffer = '' }, }) @@ -262,9 +268,7 @@ function RSCComponent(props: any) { return } -export async function hydrate(opts?: { - onFlightCssLoaded?: () => Promise -}) { +export function hydrate(opts?: { onFlightCssLoaded?: () => Promise }) { renderReactElement(appElement!, () => ( diff --git a/packages/next/client/components/app-router.client.tsx b/packages/next/client/components/app-router.client.tsx index 9b571472108e..cd9d5b06a239 100644 --- a/packages/next/client/components/app-router.client.tsx +++ b/packages/next/client/components/app-router.client.tsx @@ -49,26 +49,26 @@ function fetchFlight(url: URL, flightRouterStateData: string): ReadableStream { let buffer = '' const loadCssFromFlight = new TransformStream({ transform(chunk, controller) { + const process = (buf: string) => { + if (buf) { + if (buf.startsWith('CSS:')) { + loadCssFromStreamData(buf) + } else { + controller.enqueue(new TextEncoder().encode(buf)) + } + } + } + const data = new TextDecoder().decode(chunk) buffer += data let index while ((index = buffer.indexOf('\n')) !== -1) { const line = buffer.slice(0, index + 1) buffer = buffer.slice(index + 1) - - if (line.startsWith('CSS:')) { - loadCssFromStreamData(line) - } else { - controller.enqueue(new TextEncoder().encode(line)) - } - } - if (buffer && !buffer.startsWith('CSS:')) { - controller.enqueue(new TextEncoder().encode(buffer)) - buffer = '' + process(line) } - }, - flush() { - loadCssFromStreamData(buffer) + process(buffer) + buffer = '' }, }) diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index bd5109398f94..993495b9e6e6 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -191,7 +191,11 @@ function createServerComponentRenderer( globalThis.__next_chunk_load__ = () => Promise.resolve() } - const cssFlightData = getCssFlightData(ComponentMod, serverComponentManifest) + const cssFlightData = getCssFlightData( + ComponentMod, + serverComponentManifest, + dev + ) let RSCStream: ReadableStream const createRSCStream = () => { @@ -346,7 +350,11 @@ function getCSSInlinedLinkTags( ) } -function getCssFlightData(ComponentMod: any, serverComponentManifest: any) { +function getCssFlightData( + ComponentMod: any, + serverComponentManifest: any, + dev: boolean +) { const importedServerCSSFiles: string[] = ComponentMod.__client__?.__next_rsc_css__ || [] @@ -354,6 +362,11 @@ function getCssFlightData(ComponentMod: any, serverComponentManifest: any) { (css) => serverComponentManifest[css].default ) + if (dev) { + // Keep `id` in dev mode css flight to require the css module + return cssFiles.map((css) => `CSS:${JSON.stringify(css)}`).join('\n') + '\n' + } + // Multiple css chunks could be merged into one by mini-css-extract-plugin, // we use a set here to dedupe the css chunks in production. const cssSet: Set = cssFiles.reduce((res, css) => { @@ -781,7 +794,8 @@ export async function renderToHTML( const cssFlightData = getCssFlightData( ComponentMod, - serverComponentManifest + serverComponentManifest, + dev ) const flightData: FlightData = [ // TODO-APP: change walk to output without '' diff --git a/packages/next/server/node-web-streams-helper.ts b/packages/next/server/node-web-streams-helper.ts index 6502173af9ae..769df24adefd 100644 --- a/packages/next/server/node-web-streams-helper.ts +++ b/packages/next/server/node-web-streams-helper.ts @@ -231,9 +231,9 @@ export function createPrefixStream( let prefixFlushed = false return new TransformStream({ transform(chunk, controller) { - if (!prefixFlushed && prefix) { - prefixFlushed = true + if (!prefixFlushed) { controller.enqueue(encodeText(prefix)) + prefixFlushed = true } controller.enqueue(chunk) },