@@ -121,6 +121,7 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
121
121
// The base of the URL is unused but required to parse the URL.
122
122
const pathname = pathnameWithoutBasePath ( req . url , server . config . base ) ;
123
123
const extension = extname ( pathname ) ;
124
+ const pathnameHasTrailingSlash = pathname [ pathname . length - 1 ] === '/' ;
124
125
125
126
// Rewrite all build assets to a vite raw fs URL
126
127
const assetSourcePath = assets . get ( pathname ) ;
@@ -141,12 +142,11 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
141
142
// HTML fallbacking
142
143
// This matches what happens in the vite html fallback middleware.
143
144
// ref: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L9
144
- const htmlAssetSourcePath =
145
- pathname [ pathname . length - 1 ] === '/'
146
- ? // Trailing slash check for `index.html`.
147
- assets . get ( pathname + 'index.html' )
148
- : // Non-trailing slash check for fallback `.html`
149
- assets . get ( pathname + '.html' ) ;
145
+ const htmlAssetSourcePath = pathnameHasTrailingSlash
146
+ ? // Trailing slash check for `index.html`.
147
+ assets . get ( pathname + 'index.html' )
148
+ : // Non-trailing slash check for fallback `.html`
149
+ assets . get ( pathname + '.html' ) ;
150
150
151
151
if ( htmlAssetSourcePath ) {
152
152
req . url = `${ server . config . base } @fs/${ encodeURI ( htmlAssetSourcePath ) } ` ;
@@ -175,6 +175,19 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
175
175
}
176
176
}
177
177
178
+ // If the path has no trailing slash and it matches a servable directory redirect to the same path with slash.
179
+ // This matches the default express static behaviour.
180
+ // See: https://github.com/expressjs/serve-static/blob/89fc94567fae632718a2157206c52654680e9d01/index.js#L182
181
+ if ( ! pathnameHasTrailingSlash ) {
182
+ for ( const assetPath of assets . keys ( ) ) {
183
+ if ( pathname === assetPath . substring ( 0 , assetPath . lastIndexOf ( '/' ) ) ) {
184
+ redirect ( res , req . url + '/' ) ;
185
+
186
+ return ;
187
+ }
188
+ }
189
+ }
190
+
178
191
next ( ) ;
179
192
} ) ;
180
193
@@ -362,3 +375,26 @@ function lookupMimeTypeFromRequest(url: string): string | undefined {
362
375
363
376
return extension && lookupMimeType ( extension ) ;
364
377
}
378
+
379
+ function redirect ( res : ServerResponse , location : string ) : void {
380
+ const htmlDocument = `
381
+ <!DOCTYPE html>
382
+ <html lang="en">
383
+ <head>
384
+ <meta charset="utf-8">
385
+ <title>Redirecting</title>
386
+ </head>
387
+ <body>
388
+ <pre>Redirecting to <a href="${ location } ">${ location } </a></pre>
389
+ </body>
390
+ </html>` ;
391
+
392
+ // send redirect response
393
+ res . statusCode = 301 ;
394
+ res . setHeader ( 'Content-Type' , 'text/html; charset=UTF-8' ) ;
395
+ res . setHeader ( 'Content-Length' , Buffer . byteLength ( htmlDocument ) ) ;
396
+ res . setHeader ( 'Content-Security-Policy' , "default-src 'none'" ) ;
397
+ res . setHeader ( 'X-Content-Type-Options' , 'nosniff' ) ;
398
+ res . setHeader ( 'Location' , location ) ;
399
+ res . end ( htmlDocument ) ;
400
+ }
0 commit comments