diff --git a/packages/vite/src/rsc/rscBuildClient.ts b/packages/vite/src/rsc/rscBuildClient.ts index 4e633023e01d..709ad08231d6 100644 --- a/packages/vite/src/rsc/rscBuildClient.ts +++ b/packages/vite/src/rsc/rscBuildClient.ts @@ -44,7 +44,7 @@ export async function rscBuildClient(clientEntryFiles: Record) { input: { // @MARK: temporary hack to find the entry client so we can get the // index.css bundle but we don't actually want this on an rsc page! - 'rwjs-client-entry': rwPaths.web.entryClient, + __rwjs__client_entry: rwPaths.web.entryClient, // we need this, so that the output contains rsc-specific bundles // for the client-only components. They get loaded once the page is // rendered diff --git a/packages/vite/src/rsc/rscBuildForSsr.ts b/packages/vite/src/rsc/rscBuildForSsr.ts index ea76fdbb4e18..345051f092fd 100644 --- a/packages/vite/src/rsc/rscBuildForSsr.ts +++ b/packages/vite/src/rsc/rscBuildForSsr.ts @@ -64,7 +64,7 @@ export async function rscBuildForSsr({ // index.css bundle but we don't actually want this on an rsc page! // TODO (RSC): Look into if we can remove this (and perhaps instead // use entry.server) - 'rwjs-client-entry': rwPaths.web.entryClient, + __rwjs__client_entry: rwPaths.web.entryClient, 'entry.server': rwPaths.web.entryServer, // we need this, so that the output contains rsc-specific bundles // for the client-only components. They get loaded once the page is diff --git a/packages/vite/src/runFeServer.ts b/packages/vite/src/runFeServer.ts index 271630115e43..feeb1c51ca7b 100644 --- a/packages/vite/src/runFeServer.ts +++ b/packages/vite/src/runFeServer.ts @@ -79,15 +79,29 @@ export async function runFeServer() { await import(clientBuildManifestUrl, { with: { type: 'json' } }) ).default - // @MARK: Surely there's a better way than this! - const clientEntry = Object.values(clientBuildManifest).find( - (manifestItem) => { - // For RSC builds, we pass in many Vite entries, so we need to find it differently. - return rscEnabled - ? manifestItem.file.includes('rwjs-client-entry-') - : manifestItem.isEntry - }, - ) + // Even though `entry.server.tsx` is the main entry point for SSR, we still + // need to read the client build manifest and find `entry.client.tsx` to get + // the correct links to insert for the initial CSS files that will eventually + // be rendered when the finalized html output is being streamed to the + // browser. We also need it to tell React what JS bundle contains + // `hydrateRoot` when it'll eventually get to hydrating things in the browser + // + // So, `clientEntry` is used to find the initial JS bundle to load in the + // browser and also to discover CSS files that will be needed to render the + // initial page. + // + // In addition to all the above the discovered CSS files are also passed to + // all middleware that have been registered + const clientEntry = rscEnabled + ? clientBuildManifest['entry.client.tsx'] || + clientBuildManifest['entry.client.jsx'] + : Object.values(clientBuildManifest).find( + (manifestItem) => manifestItem.isEntry, + ) + + if (!clientEntry) { + throw new Error('Could not find client entry in build manifest') + } // @MARK: In prod, we create it once up front! const middlewareRouter = await createMiddlewareRouter() @@ -111,10 +125,6 @@ export async function runFeServer() { }) } - if (!clientEntry) { - throw new Error('Could not find client entry in build manifest') - } - // 1. Use static handler for assets // For CF workers, we'd need an equivalent of this app.use( @@ -166,15 +176,13 @@ export async function runFeServer() { // the static assets over any application routing. app.use(express.static(rwPaths.web.distClient, { index: false })) - const clientEntryPath = '/' + clientEntry.file - const getStylesheetLinks = rscEnabled ? getRscStylesheetLinkGenerator(clientEntry.css) : () => clientEntry.css || [] const routeHandler = await createReactStreamingHandler({ routes: Object.values(routeManifest), - clientEntryPath, + clientEntryPath: clientEntry.file, getStylesheetLinks, getMiddlewareRouter: async () => middlewareRouter, }) diff --git a/packages/vite/src/streaming/createReactStreamingHandler.ts b/packages/vite/src/streaming/createReactStreamingHandler.ts index 1cce91fff8f5..0c65475383fa 100644 --- a/packages/vite/src/streaming/createReactStreamingHandler.ts +++ b/packages/vite/src/streaming/createReactStreamingHandler.ts @@ -92,13 +92,19 @@ export const createReactStreamingHandler = async ( route.pathDefinition, currentUrl.pathname, ) + if (match) { currentRoute = route parsedParams = rest + break } } + // Using a function to get the CSS links because we need to wait for the + // vite dev server to analyze the module graph + const cssLinks = getStylesheetLinks(currentRoute) + // ~~~ Middleware Handling ~~~ if (middlewareRouter) { const matchedMw = middlewareRouter.find(req.method as HTTPMethod, req.url) @@ -107,7 +113,7 @@ export const createReactStreamingHandler = async ( matchedMw?.handler as Middleware | undefined, { route: currentRoute, - cssPaths: getStylesheetLinks(currentRoute), + cssPaths: cssLinks, params: matchedMw?.params, viteDevServer, }, @@ -172,20 +178,15 @@ export const createReactStreamingHandler = async ( metaTags = routeHookOutput.meta - // @MARK @TODO(RSC_DC): the entry path for RSC will be different, - // because we don't want to inject a full bundle, just a slice of it - // I'm not sure what though.... - const jsBundles = [ - clientEntryPath, // @NOTE: must have slash in front - currentRoute.bundle && '/' + currentRoute.bundle, - ].filter(Boolean) as string[] + // TODO (RSC): Do we really want to inject the full bundle here, or is + // there a smaller slice of it we can inject? + const jsBundles = ['/' + clientEntryPath] + if (currentRoute.bundle) { + jsBundles.push('/' + currentRoute.bundle) + } const isSeoCrawler = isbot(req.headers.get('user-agent') || '') - // Using a function to get the CSS links because we need to wait for the - // vite dev server to analyze the module graph - const cssLinks = getStylesheetLinks(currentRoute) - const reactResponse = await reactRenderToStreamResponse( mwResponse, { diff --git a/packages/vite/src/streaming/streamHelpers.ts b/packages/vite/src/streaming/streamHelpers.ts index 6732ad804f88..27002f0c9446 100644 --- a/packages/vite/src/streaming/streamHelpers.ts +++ b/packages/vite/src/streaming/streamHelpers.ts @@ -262,6 +262,7 @@ export async function reactRenderToStreamResponse( clearTimeout(timeoutHandle) } } + function applyStreamTransforms( reactStream: ReactDOMServerReadableStream, transformsToApply: (TransformStream | false)[],