From 070cdc7e85924386d3873dbf3af331a4b75fa7b8 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Sat, 31 Oct 2026 01:53:09 +0100 Subject: [PATCH] refactor: drop the recorded client outDir now that app builds read memory Since #514 and #518, a server build in the same process as the client build reads the build id, the server-function modules and Vite's manifest from memory. The recorded client output directory could then only lead to a stale file, so readers fall back to the configured outDir and the dist/client convention only. --- src/index.ts | 14 ++++-------- src/server-functions/index.ts | 41 +++++++++++++++-------------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/index.ts b/src/index.ts index 39334e2..69944be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1584,7 +1584,6 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { // Set in configResolved when a worker-targeted server build would carry // the plugin's `@solidjs/web/storage` import; emitted once from buildStart. let workerTargetWarning = false; - let clientOutDir: string | null = null; // The client environment's resolved build options, for the configured // entry input. Read off the resolved config so the SSR half of a // two-invocation build (`vite build --ssr`) still knows the client's @@ -1610,7 +1609,7 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { // writeBundle keeps the manifest in memory, along with the file it wrote. // Two-invocation builds (`vite build --outDir dist/client` then // `vite build --ssr`) run in separate processes, so the SSR process reads - // the file from the recorded outDir or the `dist/client` convention. + // the file from the `dist/client` convention. let clientManifest: { source: string; file: string } | null = null; // Server environments that read `clientManifest`. Once one has written its // bundle, the file leaves the client output when start mode turned the @@ -1618,12 +1617,8 @@ export default function solidPlugin(options: Partial = {}): Plugin[] { const clientManifestReaders = new Set(); let ownsClientManifest = false; function clientManifestPath(): string | null { - for (const dir of [clientOutDir, 'dist/client']) { - if (!dir) continue; - const manifestPath = path.resolve(projectRoot, dir, '.vite/manifest.json'); - if (existsSync(manifestPath)) return manifestPath; - } - return null; + const manifestPath = path.resolve(projectRoot, 'dist/client/.vite/manifest.json'); + return existsSync(manifestPath) ? manifestPath : null; } // Asset URLs in JSX (src/asset-urls.ts): a `/name` public-directory URL @@ -2650,9 +2645,8 @@ export default manifest;`; return stripped === null ? null : { code: stripped, map: null }; }, - generateBundle(outputOptions, bundle) { + generateBundle(_outputOptions, bundle) { if (!isBuild || !isClientBuild(this)) return; - clientOutDir = outputOptions.dir ?? null; // Reclassify emitted lazy facade chunks in the raw bundle (not just the // serialized manifest read back later) so downstream plugins inspecting // the bundle don't mistake them for application entries. Must precede diff --git a/src/server-functions/index.ts b/src/server-functions/index.ts index 0f3e53f..531ec0d 100644 --- a/src/server-functions/index.ts +++ b/src/server-functions/index.ts @@ -221,15 +221,14 @@ export interface PersistedServerFunctionManifest { } /** - * Reads the client build's file from the first of `clientDirs` that has one - * (see `clientOutDirs` in createServerFunctionsPlugins), then the - * `dist/client` convention. + * Reads the client build's file from the client environment's configured + * `outDir`, then the `dist/client` convention. */ function readPersistedFile( root: string, - clientDirs: (string | null)[], + clientOutDir: string | null, ): string[] | PersistedServerFunctionManifest | undefined { - for (const dir of [...clientDirs, 'dist/client']) { + for (const dir of [clientOutDir, 'dist/client']) { if (!dir) continue; const file = path.resolve(root, dir, PERSISTED_MANIFEST_PATH); if (!existsSync(file)) continue; @@ -242,8 +241,8 @@ function readPersistedFile( return undefined; } -function readPersistedManifest(root: string, clientDirs: (string | null)[]): Set { - const parsed = readPersistedFile(root, clientDirs); +function readPersistedManifest(root: string, clientOutDir: string | null): Set { + const parsed = readPersistedFile(root, clientOutDir); if (!parsed) return new Set(); // Builds from before `functions` existed wrote a bare module list. const entries = Array.isArray(parsed) ? parsed : parsed.modules; @@ -252,8 +251,8 @@ function readPersistedManifest(root: string, clientDirs: (string | null)[]): Set ); } -function readPersistedBuildId(root: string, clientDirs: (string | null)[]): string | undefined { - const parsed = readPersistedFile(root, clientDirs); +function readPersistedBuildId(root: string, clientOutDir: string | null): string | undefined { + const parsed = readPersistedFile(root, clientOutDir); return parsed && !Array.isArray(parsed) ? parsed.buildId : undefined; } @@ -473,14 +472,10 @@ export function createServerFunctionsPlugins( let isBuild = false; let isSsrBuild = false; let outDir = 'dist'; - // Where the client build writes the persisted manifest, for the server - // build to read it back. Recorded by the client build's writeBundle (the - // real output directory: builder mode shares this closure with the server - // build), else the client environment's configured outDir; readers fall - // back to the `dist/client` convention after both. - let writtenClientOutDir: string | null = null; + // Where a server build in another process looks for the persisted + // manifest: the client environment's configured outDir, then the + // `dist/client` convention. In one process it reads memory instead. let configuredClientOutDir: string | null = null; - const clientOutDirs = () => [writtenClientOutDir, configuredClientOutDir]; // Set once this plugin instance has built the client (an app build in one // process, nitro's included). The server build then uses this build's id // and discoveries from memory: a persisted file can be stale, or live in a @@ -628,7 +623,7 @@ export function createServerFunctionsPlugins( isBuild && stampsBuild ? builtClient ? buildId - : readPersistedBuildId(root, clientOutDirs()) + : readPersistedBuildId(root, configuredClientOutDir) : undefined; // Server components ride the frame-stream wire protocol: the transform // serves a function's component result as streamed HTML instead of data. @@ -1007,7 +1002,7 @@ export function createServerFunctionsPlugins( // Classic two-invocation build: pick up the modules the client // build discovered so the server manifest registers them even when // the SSR module graph never imports them. - for (const entry of readPersistedManifest(root, clientOutDirs())) { + for (const entry of readPersistedManifest(root, configuredClientOutDir)) { manifest.modules.server.add(entry); } } @@ -1028,9 +1023,6 @@ export function createServerFunctionsPlugins( const consumer = ctx.environment?.config?.consumer; const isClient = consumer ? consumer === 'client' : !isSsrBuild; if (isBuild && isClient) { - // The directory this build actually wrote, which a host may have - // moved away from the top-level `build.outDir`. - writtenClientOutDir = outputOptions.dir ?? outDir; builtClient = true; const json = persistedManifestJson( root, @@ -1038,7 +1030,10 @@ export function createServerFunctionsPlugins( manifest.clientFunctions, buildId, ); - clientPersisted = { json, file: writePersistedManifest(root, writtenClientOutDir, json) }; + // The directory this build actually wrote, which a host may have + // moved away from the top-level `build.outDir`. + const file = writePersistedManifest(root, outputOptions.dir ?? outDir, json); + clientPersisted = { json, file }; } else if ( isBuild && clientPersisted && @@ -1071,7 +1066,7 @@ export function createServerFunctionsPlugins( // `vite build` with the environments API) all environment // configs resolve before the client build has written the file, // but this load runs once the SSR environment builds — after it. - for (const entry of readPersistedManifest(root, clientOutDirs())) { + for (const entry of readPersistedManifest(root, configuredClientOutDir)) { manifest.modules.server.add(entry); } }