@@ -7,6 +7,7 @@ import { loadEnv, normalizePath } from 'vite';
7
7
import { getRequest , setResponse } from '../../../exports/node/index.js' ;
8
8
import { installPolyfills } from '../../../exports/node/polyfills.js' ;
9
9
import { SVELTE_KIT_ASSETS } from '../../../constants.js' ;
10
+ import { not_found } from '../utils.js' ;
10
11
11
12
/** @typedef {import('http').IncomingMessage } Req */
12
13
/** @typedef {import('http').ServerResponse } Res */
@@ -21,6 +22,7 @@ export async function preview(vite, vite_config, svelte_config) {
21
22
installPolyfills ( ) ;
22
23
23
24
const { paths } = svelte_config . kit ;
25
+ const base = paths . base ;
24
26
const assets = paths . assets ? SVELTE_KIT_ASSETS : paths . base ;
25
27
26
28
const protocol = vite_config . preview . https ? 'https' : 'http' ;
@@ -49,79 +51,131 @@ export async function preview(vite, vite_config, svelte_config) {
49
51
} ) ;
50
52
51
53
return ( ) => {
52
- // prerendered dependencies
54
+ // Remove the base middleware. It screws with the URL.
55
+ // It also only lets through requests beginning with the base path, so that requests beginning
56
+ // with the assets URL never reach us. We could serve assets separately before the base
57
+ // middleware, but we'd need that to occur after the compression and cors middlewares, so would
58
+ // need to insert it manually into the stack, which would be at least as bad as doing this.
59
+ for ( let i = vite . middlewares . stack . length - 1 ; i > 0 ; i -- ) {
60
+ // @ts -expect-error using internals
61
+ if ( vite . middlewares . stack [ i ] . handle . name === 'viteBaseMiddleware' ) {
62
+ vite . middlewares . stack . splice ( i , 1 ) ;
63
+ }
64
+ }
65
+
66
+ // generated client assets and the contents of `static`
53
67
vite . middlewares . use (
54
- mutable ( join ( svelte_config . kit . outDir , 'output/prerendered/dependencies' ) )
68
+ scoped (
69
+ assets ,
70
+ sirv ( join ( svelte_config . kit . outDir , 'output/client' ) , {
71
+ setHeaders : ( res , pathname ) => {
72
+ // only apply to immutable directory, not e.g. version.json
73
+ if ( pathname . startsWith ( `/${ svelte_config . kit . appDir } /immutable` ) ) {
74
+ res . setHeader ( 'cache-control' , 'public,max-age=31536000,immutable' ) ;
75
+ }
76
+ }
77
+ } )
78
+ )
55
79
) ;
56
80
57
- // prerendered pages (we can't just use sirv because we need to
58
- // preserve the correct trailingSlash behaviour)
59
81
vite . middlewares . use ( ( req , res , next ) => {
60
- let if_none_match_value = req . headers [ 'if-none-match' ] ;
61
-
62
- if ( if_none_match_value ?. startsWith ( 'W/"' ) ) {
63
- if_none_match_value = if_none_match_value . substring ( 2 ) ;
64
- }
65
-
66
- if ( if_none_match_value === etag ) {
67
- res . statusCode = 304 ;
82
+ const original_url = /** @type {string } */ ( req . url ) ;
83
+ const { pathname, search } = new URL ( original_url , 'http://dummy' ) ;
84
+
85
+ // if `paths.base === '/a/b/c`, then the root route is `/a/b/c/`,
86
+ // regardless of the `trailingSlash` route option
87
+ if ( base . length > 1 && pathname === base ) {
88
+ let location = base + '/' ;
89
+ if ( search ) location += search ;
90
+ res . writeHead ( 307 , {
91
+ location
92
+ } ) ;
68
93
res . end ( ) ;
69
94
return ;
70
95
}
71
96
72
- const { pathname, search } = new URL ( /** @type {string } */ ( req . url ) , 'http://dummy' ) ;
97
+ if ( pathname . startsWith ( base ) ) {
98
+ next ( ) ;
99
+ } else {
100
+ res . statusCode = 404 ;
101
+ not_found ( req , res , base ) ;
102
+ }
103
+ } ) ;
73
104
74
- let filename = normalizePath (
75
- join ( svelte_config . kit . outDir , 'output/prerendered/pages' + pathname )
76
- ) ;
77
- let prerendered = is_file ( filename ) ;
105
+ // prerendered dependencies
106
+ vite . middlewares . use (
107
+ scoped ( base , mutable ( join ( svelte_config . kit . outDir , 'output/prerendered/dependencies' ) ) )
108
+ ) ;
78
109
79
- if ( ! prerendered ) {
80
- const has_trailing_slash = pathname . endsWith ( '/' ) ;
81
- const html_filename = `${ filename } ${ has_trailing_slash ? 'index.html' : '.html' } ` ;
110
+ // prerendered pages (we can't just use sirv because we need to
111
+ // preserve the correct trailingSlash behaviour)
112
+ vite . middlewares . use (
113
+ scoped ( base , ( req , res , next ) => {
114
+ let if_none_match_value = req . headers [ 'if-none-match' ] ;
82
115
83
- /** @type {string | undefined } */
84
- let redirect ;
116
+ if ( if_none_match_value ?. startsWith ( 'W/"' ) ) {
117
+ if_none_match_value = if_none_match_value . substring ( 2 ) ;
118
+ }
85
119
86
- if ( is_file ( html_filename ) ) {
87
- filename = html_filename ;
88
- prerendered = true ;
89
- } else if ( has_trailing_slash ) {
90
- if ( is_file ( filename . slice ( 0 , - 1 ) + '.html' ) ) {
91
- redirect = pathname . slice ( 0 , - 1 ) ;
92
- }
93
- } else if ( is_file ( filename + '/index.html' ) ) {
94
- redirect = pathname + '/' ;
120
+ if ( if_none_match_value === etag ) {
121
+ res . statusCode = 304 ;
122
+ res . end ( ) ;
123
+ return ;
95
124
}
96
125
97
- if ( redirect ) {
98
- if ( search ) redirect += search ;
99
- res . writeHead ( 307 , {
100
- location : redirect
101
- } ) ;
126
+ const { pathname, search } = new URL ( /** @type {string } */ ( req . url ) , 'http://dummy' ) ;
127
+
128
+ let filename = normalizePath (
129
+ join ( svelte_config . kit . outDir , 'output/prerendered/pages' + pathname )
130
+ ) ;
131
+ let prerendered = is_file ( filename ) ;
132
+
133
+ if ( ! prerendered ) {
134
+ const has_trailing_slash = pathname . endsWith ( '/' ) ;
135
+ const html_filename = `${ filename } ${ has_trailing_slash ? 'index.html' : '.html' } ` ;
136
+
137
+ /** @type {string | undefined } */
138
+ let redirect ;
139
+
140
+ if ( is_file ( html_filename ) ) {
141
+ filename = html_filename ;
142
+ prerendered = true ;
143
+ } else if ( has_trailing_slash ) {
144
+ if ( is_file ( filename . slice ( 0 , - 1 ) + '.html' ) ) {
145
+ redirect = pathname . slice ( 0 , - 1 ) ;
146
+ }
147
+ } else if ( is_file ( filename + '/index.html' ) ) {
148
+ redirect = pathname + '/' ;
149
+ }
102
150
103
- res . end ( ) ;
151
+ if ( redirect ) {
152
+ if ( search ) redirect += search ;
153
+ res . writeHead ( 307 , {
154
+ location : redirect
155
+ } ) ;
104
156
105
- return ;
157
+ res . end ( ) ;
158
+
159
+ return ;
160
+ }
106
161
}
107
- }
108
162
109
- if ( prerendered ) {
110
- res . writeHead ( 200 , {
111
- 'content-type' : lookup ( pathname ) || 'text/html' ,
112
- etag
113
- } ) ;
163
+ if ( prerendered ) {
164
+ res . writeHead ( 200 , {
165
+ 'content-type' : lookup ( pathname ) || 'text/html' ,
166
+ etag
167
+ } ) ;
114
168
115
- fs . createReadStream ( filename ) . pipe ( res ) ;
116
- } else {
117
- next ( ) ;
118
- }
119
- } ) ;
169
+ fs . createReadStream ( filename ) . pipe ( res ) ;
170
+ } else {
171
+ next ( ) ;
172
+ }
173
+ } )
174
+ ) ;
120
175
121
176
// SSR
122
177
vite . middlewares . use ( async ( req , res ) => {
123
178
const host = req . headers [ 'host' ] ;
124
- req . url = req . originalUrl ;
125
179
126
180
const request = await getRequest ( {
127
181
base : `${ protocol } ://${ host } ` ,
@@ -155,6 +209,28 @@ const mutable = (dir) =>
155
209
} )
156
210
: ( _req , _res , next ) => next ( ) ;
157
211
212
+ /**
213
+ * @param {string } scope
214
+ * @param {Handler } handler
215
+ * @returns {Handler }
216
+ */
217
+ function scoped ( scope , handler ) {
218
+ if ( scope === '' ) return handler ;
219
+
220
+ return ( req , res , next ) => {
221
+ if ( req . url ?. startsWith ( scope ) ) {
222
+ const original_url = req . url ;
223
+ req . url = req . url . slice ( scope . length ) ;
224
+ handler ( req , res , ( ) => {
225
+ req . url = original_url ;
226
+ next ( ) ;
227
+ } ) ;
228
+ } else {
229
+ next ( ) ;
230
+ }
231
+ } ;
232
+ }
233
+
158
234
/** @param {string } path */
159
235
function is_file ( path ) {
160
236
return fs . existsSync ( path ) && ! fs . statSync ( path ) . isDirectory ( ) ;
0 commit comments