11/// <reference types="astro/client" />
2-
2+ import path , { relative } from 'node:path' ;
3+ import url from 'node:url' ;
4+ import { readdir } from 'node:fs/promises' ;
35import cluster from 'node:cluster' ;
46import os from 'node:os' ;
57import type { SSRManifest } from 'astro' ;
@@ -81,16 +83,25 @@ function handler(
8183
8284 const app = new App ( manifest ) ;
8385
84- return ( req : Request , server : Server < undefined > ) : Promise < Response > => {
86+ // The dist may be copied somewhere after building.
87+ // The build environment's full client path (options.client) can't be relied on in production.
88+ // `resolveClientDir()` finds the full path to the client directory in the current environment.
89+ const clientDir = resolveClientDir ( options ) ;
90+
91+ const clientAssetsPromise = getStaticAssets ( clientDir ) ;
92+ let clientAssets : Awaited < typeof clientAssetsPromise > | undefined ;
93+
94+ return async ( req : Request , server : Server < undefined > ) : Promise < Response > => {
8595 const routeData = app . match ( req ) ;
8696 if ( ! routeData ) {
8797 const url = new URL ( req . url ) ;
88-
89- const manifestAssetExists = manifest . assets . has ( url . pathname ) ;
98+ const staticAssetExists = ( clientAssets ??= await clientAssetsPromise ) . has (
99+ url . pathname ,
100+ ) ;
90101
91102 // If the manifest asset doesn't exist, or the request url ends with a slash
92103 // we should serve the index.html file from the respective directory.
93- if ( ! manifestAssetExists || req . url . endsWith ( '/' ) ) {
104+ if ( ! staticAssetExists || req . url . endsWith ( '/' ) ) {
94105 const localPath = new URL (
95106 `./${ app . removeBase ( url . pathname ) } /index.html` ,
96107 clientRoot ,
@@ -99,7 +110,7 @@ function handler(
99110 }
100111
101112 // Otherwise we attempt to serve the static asset from the client directory.
102- if ( manifestAssetExists ) {
113+ if ( staticAssetExists ) {
103114 const localPath = new URL ( app . removeBase ( url . pathname ) , clientRoot ) ;
104115 return serveStaticFile ( url . pathname , localPath , clientRoot , options ) ;
105116 }
@@ -112,3 +123,50 @@ function handler(
112123 } ) ;
113124 } ;
114125}
126+
127+ async function getStaticAssets ( clientDir : string ) : Promise < Set < string > > {
128+ const dirEntries = await readdir ( clientDir , { withFileTypes : true , recursive : true } ) ;
129+ const publicPath = new Set < string > ( ) ;
130+ for ( const entry of dirEntries ) {
131+ if ( entry . isFile ( ) == false ) continue ;
132+ publicPath . add (
133+ prependForwardSlash ( path . relative ( clientDir , entry . parentPath ) + '/' + entry . name ) ,
134+ ) ;
135+ }
136+ return publicPath ;
137+ }
138+
139+ /**
140+ * From https://github.com/withastro/adapters/blob/@astrojs/node@9.0.0/packages/node/src/serve-static.ts#L109-L125
141+ *
142+ * Copyright of withastro/adapters contributors, Reproduced under MIT License
143+ */
144+ // @ts -expect-error client and server fields are always present
145+ function resolveClientDir ( options : InternalOptions ) : string {
146+ const clientURLRaw = new URL ( options . client ) ;
147+ const serverURLRaw = new URL ( options . server ) ;
148+ const rel = path . relative (
149+ url . fileURLToPath ( serverURLRaw ) ,
150+ url . fileURLToPath ( clientURLRaw ) ,
151+ ) ;
152+
153+ // Walk up the parent folders until you find the one that is the root of the server entry folder. This is how we find the client folder relatively.
154+ const serverFolder = path . basename ( options . server ) ;
155+ let serverEntryFolderURL = path . dirname ( import . meta. url ) ;
156+ while ( ! serverEntryFolderURL . endsWith ( serverFolder ) ) {
157+ serverEntryFolderURL = path . dirname ( serverEntryFolderURL ) ;
158+ }
159+
160+ const serverEntryURL = serverEntryFolderURL + '/entry.mjs' ;
161+ const clientURL = new URL ( appendForwardSlash ( rel ) , serverEntryURL ) ;
162+ const client = url . fileURLToPath ( clientURL ) ;
163+ return client ;
164+ }
165+
166+ function prependForwardSlash ( pth : string ) : string {
167+ return pth . startsWith ( '/' ) ? pth : '/' + pth ;
168+ }
169+
170+ function appendForwardSlash ( pth : string ) : string {
171+ return pth . endsWith ( '/' ) ? pth : pth + '/' ;
172+ }
0 commit comments