diff --git a/packages/next/client/app-index.tsx b/packages/next/client/app-index.tsx index bd3371dac76f..89d280b528f3 100644 --- a/packages/next/client/app-index.tsx +++ b/packages/next/client/app-index.tsx @@ -172,12 +172,12 @@ function useInitialServerResponse(cacheKey: string) { } const loadCssFromStreamData = (data: string) => { - const seg = data.split(':') - if (seg[0] === 'CSS') { - loadCss(seg.slice(1).join(':')) + if (data.startsWith('CSS:')) { + loadCss(data.slice(4)) } } + // TODO-APP: Refine the buffering code here to make it more correct. let buffer = '' const loadCssFromFlight = new TransformStream({ transform(chunk, controller) { @@ -185,12 +185,16 @@ function useInitialServerResponse(cacheKey: string) { buffer += data let index while ((index = buffer.indexOf('\n')) !== -1) { - const line = buffer.slice(0, index) + const line = buffer.slice(0, index + 1) buffer = buffer.slice(index + 1) - loadCssFromStreamData(line) + if (line.startsWith('CSS:')) { + loadCssFromStreamData(line) + } else { + controller.enqueue(new TextEncoder().encode(line)) + } } - if (!data.startsWith('CSS:')) { - controller.enqueue(chunk) + if (buffer && !buffer.startsWith('CSS:')) { + controller.enqueue(new TextEncoder().encode(buffer)) } }, flush() { diff --git a/packages/next/client/components/app-router.client.tsx b/packages/next/client/components/app-router.client.tsx index 7084ecefaf3e..b0df7f5f30f5 100644 --- a/packages/next/client/components/app-router.client.tsx +++ b/packages/next/client/components/app-router.client.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react' -import { createFromFetch } from 'next/dist/compiled/react-server-dom-webpack' +import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack' import { AppRouterContext, AppTreeContext, @@ -16,16 +16,66 @@ import { // LayoutSegmentsContext, } from './hooks-client-context' -function fetchFlight( - url: URL, - flightRouterStateData: string -): Promise { +async function loadCss(cssChunkInfoJson: string) { + const data = JSON.parse(cssChunkInfoJson) + await Promise.all( + data.chunks.map((chunkId: string) => { + // load css related chunks + return (self as any).__next_chunk_load__(chunkId) + }) + ) + // In development mode, import css in dev when it's wrapped by style loader. + // In production mode, css are standalone chunk that doesn't need to be imported. + if (data.id) { + ;(self as any).__next_require__(data.id) + } +} + +const loadCssFromStreamData = (data: string) => { + if (data.startsWith('CSS:')) { + loadCss(data.slice(4)) + } +} + +function fetchFlight(url: URL, flightRouterStateData: string): ReadableStream { const flightUrl = new URL(url) const searchParams = flightUrl.searchParams searchParams.append('__flight__', '1') searchParams.append('__flight_router_state_tree__', flightRouterStateData) - return fetch(flightUrl.toString()) + const { readable, writable } = new TransformStream() + + // TODO-APP: Refine the buffering code here to make it more correct. + let buffer = '' + const loadCssFromFlight = new TransformStream({ + transform(chunk, controller) { + 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)) + } + }, + flush() { + loadCssFromStreamData(buffer) + }, + }) + + fetch(flightUrl.toString()).then((res) => { + res.body?.pipeThrough(loadCssFromFlight).pipeTo(writable) + }) + + return readable } export function fetchServerResponse( @@ -33,7 +83,7 @@ export function fetchServerResponse( flightRouterState: FlightRouterState ): { readRoot: () => FlightData } { const flightRouterStateData = JSON.stringify(flightRouterState) - return createFromFetch(fetchFlight(url, flightRouterStateData)) + return createFromReadableStream(fetchFlight(url, flightRouterStateData)) } function ErrorOverlay({