@sveltejs/kit 1.0.0-next.469 → 1.0.0-next.471
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/core/env.js +11 -2
- package/src/core/prerender/prerender.js +12 -6
- package/src/core/sync/create_manifest_data/index.js +20 -0
- package/src/exports/vite/build/build_server.js +7 -1
- package/src/exports/vite/dev/index.js +23 -14
- package/src/exports/vite/index.js +10 -14
- package/src/exports/vite/utils.js +42 -26
- package/src/runtime/server/index.js +6 -2
- package/src/runtime/server/page/fetch.js +168 -170
- package/src/runtime/server/page/index.js +2 -1
- package/src/runtime/server/page/render.js +6 -1
- package/src/runtime/server/page/respond_with_error.js +2 -1
- package/src/runtime/server/page/serialize_data.js +32 -17
- package/src/runtime/server/page/types.d.ts +4 -8
- package/src/utils/routing.js +0 -20
- package/types/index.d.ts +5 -4
- package/types/internal.d.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltejs/kit",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.471",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/sveltejs/kit",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"homepage": "https://kit.svelte.dev",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@sveltejs/vite-plugin-svelte": "^1.0.
|
|
13
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.5",
|
|
14
14
|
"cookie": "^0.5.0",
|
|
15
15
|
"devalue": "^3.1.2",
|
|
16
16
|
"kleur": "^4.1.4",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"svelte-preprocess": "^4.10.6",
|
|
39
39
|
"typescript": "^4.8.2",
|
|
40
40
|
"uvu": "^0.5.3",
|
|
41
|
-
"vite": "^3.1.0
|
|
41
|
+
"vite": "^3.1.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"svelte": "^3.44.0",
|
|
45
|
-
"vite": "^3.1.0
|
|
45
|
+
"vite": "^3.1.0"
|
|
46
46
|
},
|
|
47
47
|
"bin": {
|
|
48
48
|
"svelte-kit": "svelte-kit.js"
|
package/src/core/env.js
CHANGED
|
@@ -24,8 +24,17 @@ export function create_static_module(id, env) {
|
|
|
24
24
|
return GENERATED_COMMENT + declarations.join('\n\n');
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* @param {'public' | 'private'} type
|
|
29
|
+
* @param {Record<string, string> | undefined} dev_values If in a development mode, values to pre-populate the module with.
|
|
30
|
+
*/
|
|
31
|
+
export function create_dynamic_module(type, dev_values) {
|
|
32
|
+
if (dev_values) {
|
|
33
|
+
const objectKeys = Object.entries(dev_values).map(
|
|
34
|
+
([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`
|
|
35
|
+
);
|
|
36
|
+
return `const env = {\n${objectKeys.join(',\n')}\n}\n\nexport { env }`;
|
|
37
|
+
}
|
|
29
38
|
return `export { env } from '${runtime_base}/env-${type}.js';`;
|
|
30
39
|
}
|
|
31
40
|
|
|
@@ -237,10 +237,11 @@ export async function prerender() {
|
|
|
237
237
|
const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
|
|
238
238
|
const decoded_dependency_path = decodeURI(encoded_dependency_path);
|
|
239
239
|
|
|
240
|
-
const
|
|
240
|
+
const headers = Object.fromEntries(result.response.headers);
|
|
241
241
|
|
|
242
|
+
const prerender = headers['x-sveltekit-prerender'];
|
|
242
243
|
if (prerender) {
|
|
243
|
-
const route_id =
|
|
244
|
+
const route_id = headers['x-sveltekit-routeid'];
|
|
244
245
|
const existing_value = prerender_map.get(route_id);
|
|
245
246
|
if (existing_value !== 'auto') {
|
|
246
247
|
prerender_map.set(route_id, prerender === 'true' ? true : 'auto');
|
|
@@ -259,7 +260,10 @@ export async function prerender() {
|
|
|
259
260
|
);
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
|
|
263
|
+
// avoid triggering `filterSerializeResponseHeaders` guard
|
|
264
|
+
const headers = Object.fromEntries(response.headers);
|
|
265
|
+
|
|
266
|
+
if (config.prerender.crawl && headers['content-type'] === 'text/html') {
|
|
263
267
|
for (const href of crawl(body.toString())) {
|
|
264
268
|
if (href.startsWith('data:') || href.startsWith('#')) continue;
|
|
265
269
|
|
|
@@ -288,7 +292,9 @@ export async function prerender() {
|
|
|
288
292
|
*/
|
|
289
293
|
function save(category, response, body, decoded, encoded, referrer, referenceType) {
|
|
290
294
|
const response_type = Math.floor(response.status / 100);
|
|
291
|
-
const
|
|
295
|
+
const headers = Object.fromEntries(response.headers);
|
|
296
|
+
|
|
297
|
+
const type = headers['content-type'];
|
|
292
298
|
const is_html = response_type === REDIRECT || type === 'text/html';
|
|
293
299
|
|
|
294
300
|
const file = output_filename(decoded, is_html);
|
|
@@ -297,7 +303,7 @@ export async function prerender() {
|
|
|
297
303
|
if (written.has(file)) return;
|
|
298
304
|
|
|
299
305
|
if (response_type === REDIRECT) {
|
|
300
|
-
const location =
|
|
306
|
+
const location = headers['location'];
|
|
301
307
|
|
|
302
308
|
if (location) {
|
|
303
309
|
const resolved = resolve(encoded, location);
|
|
@@ -305,7 +311,7 @@ export async function prerender() {
|
|
|
305
311
|
enqueue(decoded, decodeURI(resolved), resolved);
|
|
306
312
|
}
|
|
307
313
|
|
|
308
|
-
if (!
|
|
314
|
+
if (!headers['x-sveltekit-normalize']) {
|
|
309
315
|
mkdirp(dirname(dest));
|
|
310
316
|
|
|
311
317
|
log.warn(`${response.status} ${decoded} -> ${location}`);
|
|
@@ -103,6 +103,14 @@ function create_routes_and_nodes(cwd, config, fallback) {
|
|
|
103
103
|
* @param {import('types').RouteData | null} parent
|
|
104
104
|
*/
|
|
105
105
|
const walk = (depth, id, segment, parent) => {
|
|
106
|
+
if (/\]\[/.test(id)) {
|
|
107
|
+
throw new Error(`Invalid route ${id} — parameters must be separated`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (count_occurrences('[', id) !== count_occurrences(']', id)) {
|
|
111
|
+
throw new Error(`Invalid route ${id} — brackets are unbalanced`);
|
|
112
|
+
}
|
|
113
|
+
|
|
106
114
|
const { pattern, names, types } = parse_route_id(id);
|
|
107
115
|
|
|
108
116
|
const segments = id.split('/');
|
|
@@ -450,3 +458,15 @@ function list_files(dir) {
|
|
|
450
458
|
|
|
451
459
|
return files;
|
|
452
460
|
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* @param {string} needle
|
|
464
|
+
* @param {string} haystack
|
|
465
|
+
*/
|
|
466
|
+
function count_occurrences(needle, haystack) {
|
|
467
|
+
let count = 0;
|
|
468
|
+
for (let i = 0; i < haystack.length; i += 1) {
|
|
469
|
+
if (haystack[i] === needle) count += 1;
|
|
470
|
+
}
|
|
471
|
+
return count;
|
|
472
|
+
}
|
|
@@ -110,10 +110,16 @@ export class Server {
|
|
|
110
110
|
|
|
111
111
|
if (!this.options.hooks) {
|
|
112
112
|
const module = await import(${s(hooks)});
|
|
113
|
+
|
|
114
|
+
// TODO remove this for 1.0
|
|
115
|
+
if (module.externalFetch) {
|
|
116
|
+
throw new Error('externalFetch has been removed — use handleFetch instead. See https://github.com/sveltejs/kit/pull/6565 for details');
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
this.options.hooks = {
|
|
114
120
|
handle: module.handle || (({ event, resolve }) => resolve(event)),
|
|
115
121
|
handleError: module.handleError || (({ error }) => console.error(error.stack)),
|
|
116
|
-
|
|
122
|
+
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request))
|
|
117
123
|
};
|
|
118
124
|
}
|
|
119
125
|
}
|
|
@@ -11,8 +11,9 @@ import { load_error_page, load_template } from '../../../core/config/index.js';
|
|
|
11
11
|
import { SVELTE_KIT_ASSETS } from '../../../constants.js';
|
|
12
12
|
import * as sync from '../../../core/sync/sync.js';
|
|
13
13
|
import { get_mime_lookup, runtime_base, runtime_prefix } from '../../../core/utils.js';
|
|
14
|
-
import {
|
|
14
|
+
import { prevent_illegal_vite_imports, resolve_entry } from '../utils.js';
|
|
15
15
|
import { compact } from '../../../utils/array.js';
|
|
16
|
+
import { normalizePath } from 'vite';
|
|
16
17
|
|
|
17
18
|
// Vite doesn't expose this so we just copy the list for now
|
|
18
19
|
// https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96
|
|
@@ -24,10 +25,9 @@ const cwd = process.cwd();
|
|
|
24
25
|
* @param {import('vite').ViteDevServer} vite
|
|
25
26
|
* @param {import('vite').ResolvedConfig} vite_config
|
|
26
27
|
* @param {import('types').ValidatedConfig} svelte_config
|
|
27
|
-
* @param {Set<string>} illegal_imports
|
|
28
28
|
* @return {Promise<Promise<() => void>>}
|
|
29
29
|
*/
|
|
30
|
-
export async function dev(vite, vite_config, svelte_config
|
|
30
|
+
export async function dev(vite, vite_config, svelte_config) {
|
|
31
31
|
installPolyfills();
|
|
32
32
|
|
|
33
33
|
sync.init(svelte_config, vite_config.mode);
|
|
@@ -90,7 +90,11 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
90
90
|
module_nodes.push(module_node);
|
|
91
91
|
result.file = url.endsWith('.svelte') ? url : url + '?import'; // TODO what is this for?
|
|
92
92
|
|
|
93
|
-
prevent_illegal_vite_imports(
|
|
93
|
+
prevent_illegal_vite_imports(
|
|
94
|
+
module_node,
|
|
95
|
+
normalizePath(svelte_config.kit.files.lib),
|
|
96
|
+
extensions
|
|
97
|
+
);
|
|
94
98
|
|
|
95
99
|
return module.default;
|
|
96
100
|
};
|
|
@@ -103,7 +107,11 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
103
107
|
|
|
104
108
|
result.shared = module;
|
|
105
109
|
|
|
106
|
-
prevent_illegal_vite_imports(
|
|
110
|
+
prevent_illegal_vite_imports(
|
|
111
|
+
module_node,
|
|
112
|
+
normalizePath(svelte_config.kit.files.lib),
|
|
113
|
+
extensions
|
|
114
|
+
);
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
if (node.server) {
|
|
@@ -269,13 +277,6 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
269
277
|
}
|
|
270
278
|
});
|
|
271
279
|
|
|
272
|
-
const { set_private_env } = await vite.ssrLoadModule(`${runtime_base}/env-private.js`);
|
|
273
|
-
const { set_public_env } = await vite.ssrLoadModule(`${runtime_base}/env-public.js`);
|
|
274
|
-
|
|
275
|
-
const env = get_env(svelte_config.kit.env, vite_config.mode);
|
|
276
|
-
set_private_env(env.private);
|
|
277
|
-
set_public_env(env.public);
|
|
278
|
-
|
|
279
280
|
return () => {
|
|
280
281
|
const serve_static_middleware = vite.middlewares.stack.find(
|
|
281
282
|
(middleware) =>
|
|
@@ -317,6 +318,14 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
317
318
|
|
|
318
319
|
const handle = user_hooks.handle || (({ event, resolve }) => resolve(event));
|
|
319
320
|
|
|
321
|
+
// TODO remove for 1.0
|
|
322
|
+
// @ts-expect-error
|
|
323
|
+
if (user_hooks.externalFetch) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
'externalFetch has been removed — use handleFetch instead. See https://github.com/sveltejs/kit/pull/6565 for details'
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
320
329
|
/** @type {import('types').Hooks} */
|
|
321
330
|
const hooks = {
|
|
322
331
|
handle,
|
|
@@ -331,7 +340,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
331
340
|
console.error(colors.gray(error.stack));
|
|
332
341
|
}
|
|
333
342
|
}),
|
|
334
|
-
|
|
343
|
+
handleFetch: user_hooks.handleFetch || (({ request, fetch }) => fetch(request))
|
|
335
344
|
};
|
|
336
345
|
|
|
337
346
|
if (/** @type {any} */ (hooks).getContext) {
|
|
@@ -411,7 +420,7 @@ export async function dev(vite, vite_config, svelte_config, illegal_imports) {
|
|
|
411
420
|
base: svelte_config.kit.paths.base,
|
|
412
421
|
assets
|
|
413
422
|
},
|
|
414
|
-
public_env:
|
|
423
|
+
public_env: {},
|
|
415
424
|
read: (file) => fs.readFileSync(path.join(svelte_config.kit.files.assets, file)),
|
|
416
425
|
root,
|
|
417
426
|
app_template: ({ head, body, assets, nonce }) => {
|
|
@@ -103,9 +103,6 @@ function kit() {
|
|
|
103
103
|
/** @type {import('types').BuildData} */
|
|
104
104
|
let build_data;
|
|
105
105
|
|
|
106
|
-
/** @type {Set<string>} */
|
|
107
|
-
let illegal_imports;
|
|
108
|
-
|
|
109
106
|
/** @type {string | undefined} */
|
|
110
107
|
let deferred_warning;
|
|
111
108
|
|
|
@@ -212,13 +209,6 @@ function kit() {
|
|
|
212
209
|
client_out_dir: `${svelte_config.kit.outDir}/output/client`
|
|
213
210
|
};
|
|
214
211
|
|
|
215
|
-
illegal_imports = new Set([
|
|
216
|
-
'/@id/__x00__$env/dynamic/private', //dev
|
|
217
|
-
'\0$env/dynamic/private', // prod
|
|
218
|
-
'/@id/__x00__$env/static/private', // dev
|
|
219
|
-
'\0$env/static/private' // prod
|
|
220
|
-
]);
|
|
221
|
-
|
|
222
212
|
if (is_build) {
|
|
223
213
|
manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
|
|
224
214
|
|
|
@@ -299,9 +289,15 @@ function kit() {
|
|
|
299
289
|
case '\0$env/static/public':
|
|
300
290
|
return create_static_module('$env/static/public', env.public);
|
|
301
291
|
case '\0$env/dynamic/private':
|
|
302
|
-
return create_dynamic_module(
|
|
292
|
+
return create_dynamic_module(
|
|
293
|
+
'private',
|
|
294
|
+
vite_config_env.command === 'serve' ? env.private : undefined
|
|
295
|
+
);
|
|
303
296
|
case '\0$env/dynamic/public':
|
|
304
|
-
return create_dynamic_module(
|
|
297
|
+
return create_dynamic_module(
|
|
298
|
+
'public',
|
|
299
|
+
vite_config_env.command === 'serve' ? env.public : undefined
|
|
300
|
+
);
|
|
305
301
|
}
|
|
306
302
|
},
|
|
307
303
|
|
|
@@ -355,7 +351,7 @@ function kit() {
|
|
|
355
351
|
prevent_illegal_rollup_imports(
|
|
356
352
|
this.getModuleInfo.bind(this),
|
|
357
353
|
module_node,
|
|
358
|
-
|
|
354
|
+
vite.normalizePath(svelte_config.kit.files.lib)
|
|
359
355
|
);
|
|
360
356
|
}
|
|
361
357
|
});
|
|
@@ -511,7 +507,7 @@ function kit() {
|
|
|
511
507
|
if (deferred_warning) console.error('\n' + deferred_warning);
|
|
512
508
|
};
|
|
513
509
|
|
|
514
|
-
return await dev(vite, vite_config, svelte_config
|
|
510
|
+
return await dev(vite, vite_config, svelte_config);
|
|
515
511
|
},
|
|
516
512
|
|
|
517
513
|
/**
|
|
@@ -4,6 +4,26 @@ import { loadConfigFromFile, loadEnv, normalizePath } from 'vite';
|
|
|
4
4
|
import { runtime_directory } from '../../core/utils.js';
|
|
5
5
|
import { posixify } from '../../utils/filesystem.js';
|
|
6
6
|
|
|
7
|
+
const illegal_imports = new Set([
|
|
8
|
+
'/@id/__x00__$env/dynamic/private', //dev
|
|
9
|
+
'\0$env/dynamic/private', // prod
|
|
10
|
+
'/@id/__x00__$env/static/private', // dev
|
|
11
|
+
'\0$env/static/private' // prod
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
/** @param {string} id */
|
|
15
|
+
function is_illegal(id) {
|
|
16
|
+
if (illegal_imports.has(id)) return true;
|
|
17
|
+
|
|
18
|
+
// files outside the project root are ignored
|
|
19
|
+
if (!id.startsWith(normalizePath(process.cwd()))) return false;
|
|
20
|
+
|
|
21
|
+
// so are files inside node_modules
|
|
22
|
+
if (id.startsWith(normalizePath(node_modules_dir))) return false;
|
|
23
|
+
|
|
24
|
+
return /.*\.server\..+/.test(path.basename(id));
|
|
25
|
+
}
|
|
26
|
+
|
|
7
27
|
/**
|
|
8
28
|
* @param {import('vite').ResolvedConfig} config
|
|
9
29
|
* @param {import('vite').ConfigEnv} config_env
|
|
@@ -183,8 +203,9 @@ function repeat(str, times) {
|
|
|
183
203
|
/**
|
|
184
204
|
* Create a formatted error for an illegal import.
|
|
185
205
|
* @param {Array<{name: string, dynamic: boolean}>} stack
|
|
206
|
+
* @param {string} lib_dir
|
|
186
207
|
*/
|
|
187
|
-
function format_illegal_import_chain(stack) {
|
|
208
|
+
function format_illegal_import_chain(stack, lib_dir) {
|
|
188
209
|
const dev_virtual_prefix = '/@id/__x00__';
|
|
189
210
|
const prod_virtual_prefix = '\0';
|
|
190
211
|
|
|
@@ -195,6 +216,9 @@ function format_illegal_import_chain(stack) {
|
|
|
195
216
|
if (file.name.startsWith(prod_virtual_prefix)) {
|
|
196
217
|
return { ...file, name: file.name.replace(prod_virtual_prefix, '') };
|
|
197
218
|
}
|
|
219
|
+
if (file.name.startsWith(lib_dir)) {
|
|
220
|
+
return { ...file, name: file.name.replace(lib_dir, '$lib') };
|
|
221
|
+
}
|
|
198
222
|
|
|
199
223
|
return { ...file, name: path.relative(process.cwd(), file.name) };
|
|
200
224
|
});
|
|
@@ -211,6 +235,8 @@ function format_illegal_import_chain(stack) {
|
|
|
211
235
|
return `Cannot import ${stack.at(-1)?.name} into client-side code:\n${pyramid}`;
|
|
212
236
|
}
|
|
213
237
|
|
|
238
|
+
const node_modules_dir = path.resolve(process.cwd(), 'node_modules');
|
|
239
|
+
|
|
214
240
|
/**
|
|
215
241
|
* Load environment variables from process.env and .env files
|
|
216
242
|
* @param {import('types').ValidatedKitConfig['env']} env_config
|
|
@@ -228,11 +254,11 @@ export function get_env(env_config, mode) {
|
|
|
228
254
|
/**
|
|
229
255
|
* @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
|
|
230
256
|
* @param {import('rollup').ModuleInfo} node
|
|
231
|
-
* @param {
|
|
257
|
+
* @param {string} lib_dir
|
|
232
258
|
*/
|
|
233
|
-
export function prevent_illegal_rollup_imports(node_getter, node,
|
|
234
|
-
const chain = find_illegal_rollup_imports(node_getter, node, false
|
|
235
|
-
if (chain) throw new Error(format_illegal_import_chain(chain));
|
|
259
|
+
export function prevent_illegal_rollup_imports(node_getter, node, lib_dir) {
|
|
260
|
+
const chain = find_illegal_rollup_imports(node_getter, node, false);
|
|
261
|
+
if (chain) throw new Error(format_illegal_import_chain(chain, lib_dir));
|
|
236
262
|
}
|
|
237
263
|
|
|
238
264
|
const query_pattern = /\?.*$/s;
|
|
@@ -246,36 +272,27 @@ function remove_query_from_path(path) {
|
|
|
246
272
|
* @param {(id: string) => import('rollup').ModuleInfo | null} node_getter
|
|
247
273
|
* @param {import('rollup').ModuleInfo} node
|
|
248
274
|
* @param {boolean} dynamic
|
|
249
|
-
* @param {Set<string>} illegal_imports Illegal module IDs -- be sure to call vite.normalizePath!
|
|
250
275
|
* @param {Set<string>} seen
|
|
251
276
|
* @returns {Array<import('types').ImportNode> | null}
|
|
252
277
|
*/
|
|
253
|
-
const find_illegal_rollup_imports = (
|
|
254
|
-
node_getter,
|
|
255
|
-
node,
|
|
256
|
-
dynamic,
|
|
257
|
-
illegal_imports,
|
|
258
|
-
seen = new Set()
|
|
259
|
-
) => {
|
|
278
|
+
const find_illegal_rollup_imports = (node_getter, node, dynamic, seen = new Set()) => {
|
|
260
279
|
const name = remove_query_from_path(normalizePath(node.id));
|
|
261
280
|
if (seen.has(name)) return null;
|
|
262
281
|
seen.add(name);
|
|
263
282
|
|
|
264
|
-
if (
|
|
283
|
+
if (is_illegal(name)) {
|
|
265
284
|
return [{ name, dynamic }];
|
|
266
285
|
}
|
|
267
286
|
|
|
268
287
|
for (const id of node.importedIds) {
|
|
269
288
|
const child = node_getter(id);
|
|
270
|
-
const chain =
|
|
271
|
-
child && find_illegal_rollup_imports(node_getter, child, false, illegal_imports, seen);
|
|
289
|
+
const chain = child && find_illegal_rollup_imports(node_getter, child, false, seen);
|
|
272
290
|
if (chain) return [{ name, dynamic }, ...chain];
|
|
273
291
|
}
|
|
274
292
|
|
|
275
293
|
for (const id of node.dynamicallyImportedIds) {
|
|
276
294
|
const child = node_getter(id);
|
|
277
|
-
const chain =
|
|
278
|
-
child && find_illegal_rollup_imports(node_getter, child, true, illegal_imports, seen);
|
|
295
|
+
const chain = child && find_illegal_rollup_imports(node_getter, child, true, seen);
|
|
279
296
|
if (chain) return [{ name, dynamic }, ...chain];
|
|
280
297
|
}
|
|
281
298
|
|
|
@@ -308,22 +325,21 @@ const get_module_types = (config_module_types) => {
|
|
|
308
325
|
/**
|
|
309
326
|
* Throw an error if a private module is imported from a client-side node.
|
|
310
327
|
* @param {import('vite').ModuleNode} node
|
|
311
|
-
* @param {
|
|
328
|
+
* @param {string} lib_dir
|
|
312
329
|
* @param {Iterable<string>} module_types File extensions to analyze in addition to the defaults: `.ts`, `.js`, etc.
|
|
313
330
|
*/
|
|
314
|
-
export function prevent_illegal_vite_imports(node,
|
|
315
|
-
const chain = find_illegal_vite_imports(node,
|
|
316
|
-
if (chain) throw new Error(format_illegal_import_chain(chain));
|
|
331
|
+
export function prevent_illegal_vite_imports(node, lib_dir, module_types) {
|
|
332
|
+
const chain = find_illegal_vite_imports(node, get_module_types(module_types));
|
|
333
|
+
if (chain) throw new Error(format_illegal_import_chain(chain, lib_dir));
|
|
317
334
|
}
|
|
318
335
|
|
|
319
336
|
/**
|
|
320
337
|
* @param {import('vite').ModuleNode} node
|
|
321
|
-
* @param {Set<string>} illegal_imports Illegal module IDs -- be sure to call vite.normalizePath!
|
|
322
338
|
* @param {Set<string>} module_types File extensions to analyze: `.ts`, `.js`, etc.
|
|
323
339
|
* @param {Set<string>} seen
|
|
324
340
|
* @returns {Array<import('types').ImportNode> | null}
|
|
325
341
|
*/
|
|
326
|
-
function find_illegal_vite_imports(node,
|
|
342
|
+
function find_illegal_vite_imports(node, module_types, seen = new Set()) {
|
|
327
343
|
if (!node.id) return null; // TODO when does this happen?
|
|
328
344
|
const name = remove_query_from_path(normalizePath(node.id));
|
|
329
345
|
|
|
@@ -332,12 +348,12 @@ function find_illegal_vite_imports(node, illegal_imports, module_types, seen = n
|
|
|
332
348
|
}
|
|
333
349
|
seen.add(name);
|
|
334
350
|
|
|
335
|
-
if (
|
|
351
|
+
if (is_illegal(name)) {
|
|
336
352
|
return [{ name, dynamic: false }];
|
|
337
353
|
}
|
|
338
354
|
|
|
339
355
|
for (const child of node.importedModules) {
|
|
340
|
-
const chain = child && find_illegal_vite_imports(child,
|
|
356
|
+
const chain = child && find_illegal_vite_imports(child, module_types, seen);
|
|
341
357
|
if (chain) return [{ name, dynamic: false }, ...chain];
|
|
342
358
|
}
|
|
343
359
|
|
|
@@ -14,6 +14,8 @@ import { DATA_SUFFIX } from '../../constants.js';
|
|
|
14
14
|
/** @param {{ html: string }} opts */
|
|
15
15
|
const default_transform = ({ html }) => html;
|
|
16
16
|
|
|
17
|
+
const default_filter = () => false;
|
|
18
|
+
|
|
17
19
|
/** @type {import('types').Respond} */
|
|
18
20
|
export async function respond(request, options, state) {
|
|
19
21
|
let url = new URL(request.url);
|
|
@@ -201,7 +203,8 @@ export async function respond(request, options, state) {
|
|
|
201
203
|
|
|
202
204
|
/** @type {import('types').RequiredResolveOptions} */
|
|
203
205
|
let resolve_opts = {
|
|
204
|
-
transformPageChunk: default_transform
|
|
206
|
+
transformPageChunk: default_transform,
|
|
207
|
+
filterSerializedResponseHeaders: default_filter
|
|
205
208
|
};
|
|
206
209
|
|
|
207
210
|
/**
|
|
@@ -226,7 +229,8 @@ export async function respond(request, options, state) {
|
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
resolve_opts = {
|
|
229
|
-
transformPageChunk: opts.transformPageChunk || default_transform
|
|
232
|
+
transformPageChunk: opts.transformPageChunk || default_transform,
|
|
233
|
+
filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter
|
|
230
234
|
};
|
|
231
235
|
}
|
|
232
236
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as cookie from 'cookie';
|
|
2
2
|
import * as set_cookie_parser from 'set-cookie-parser';
|
|
3
3
|
import { respond } from '../index.js';
|
|
4
|
-
import { is_root_relative, resolve } from '../../../utils/url.js';
|
|
5
4
|
import { domain_matches, path_matches } from './cookie.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -11,194 +10,175 @@ import { domain_matches, path_matches } from './cookie.js';
|
|
|
11
10
|
* state: import('types').SSRState;
|
|
12
11
|
* route: import('types').SSRRoute | import('types').SSRErrorPage;
|
|
13
12
|
* prerender_default?: import('types').PrerenderOption;
|
|
13
|
+
* resolve_opts: import('types').RequiredResolveOptions;
|
|
14
14
|
* }} opts
|
|
15
15
|
*/
|
|
16
|
-
export function create_fetch({ event, options, state, route, prerender_default }) {
|
|
16
|
+
export function create_fetch({ event, options, state, route, prerender_default, resolve_opts }) {
|
|
17
17
|
/** @type {import('./types').Fetched[]} */
|
|
18
18
|
const fetched = [];
|
|
19
19
|
|
|
20
20
|
const initial_cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
|
21
21
|
|
|
22
22
|
/** @type {import('set-cookie-parser').Cookie[]} */
|
|
23
|
-
const
|
|
23
|
+
const set_cookies = [];
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} else {
|
|
33
|
-
requested = resource.url;
|
|
34
|
-
|
|
35
|
-
opts = {
|
|
36
|
-
method: resource.method,
|
|
37
|
-
headers: resource.headers,
|
|
38
|
-
body: resource.body,
|
|
39
|
-
mode: resource.mode,
|
|
40
|
-
credentials: resource.credentials,
|
|
41
|
-
cache: resource.cache,
|
|
42
|
-
redirect: resource.redirect,
|
|
43
|
-
referrer: resource.referrer,
|
|
44
|
-
integrity: resource.integrity,
|
|
45
|
-
...opts
|
|
46
|
-
};
|
|
47
|
-
}
|
|
25
|
+
/**
|
|
26
|
+
* @param {URL} url
|
|
27
|
+
* @param {string | null} header
|
|
28
|
+
*/
|
|
29
|
+
function get_cookie_header(url, header) {
|
|
30
|
+
/** @type {Record<string, string>} */
|
|
31
|
+
const new_cookies = {};
|
|
48
32
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
key !== 'authorization' &&
|
|
55
|
-
key !== 'connection' &&
|
|
56
|
-
key !== 'content-length' &&
|
|
57
|
-
key !== 'cookie' &&
|
|
58
|
-
key !== 'host' &&
|
|
59
|
-
key !== 'if-none-match' &&
|
|
60
|
-
!opts.headers.has(key)
|
|
61
|
-
) {
|
|
62
|
-
opts.headers.set(key, value);
|
|
63
|
-
}
|
|
33
|
+
for (const cookie of set_cookies) {
|
|
34
|
+
if (!domain_matches(url.hostname, cookie.domain)) continue;
|
|
35
|
+
if (!path_matches(url.pathname, cookie.path)) continue;
|
|
36
|
+
|
|
37
|
+
new_cookies[cookie.name] = cookie.value;
|
|
64
38
|
}
|
|
65
39
|
|
|
66
|
-
|
|
40
|
+
// cookies from explicit `cookie` header take precedence over cookies previously set
|
|
41
|
+
// during this load with `set-cookie`, which take precedence over the cookies
|
|
42
|
+
// sent by the user agent
|
|
43
|
+
const combined_cookies = {
|
|
44
|
+
...initial_cookies,
|
|
45
|
+
...new_cookies,
|
|
46
|
+
...cookie.parse(header ?? '')
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return Object.entries(combined_cookies)
|
|
50
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
51
|
+
.join('; ');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** @type {typeof fetch} */
|
|
55
|
+
const fetcher = async (info, init) => {
|
|
56
|
+
const request = normalize_fetch_input(info, init, event.url);
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
let response;
|
|
58
|
+
const request_body = init?.body;
|
|
70
59
|
|
|
71
60
|
/** @type {import('types').PrerenderDependency} */
|
|
72
61
|
let dependency;
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
).slice(1);
|
|
80
|
-
const filename_html = `${filename}/index.html`; // path may also match path/index.html
|
|
63
|
+
const response = await options.hooks.handleFetch({
|
|
64
|
+
event,
|
|
65
|
+
request,
|
|
66
|
+
fetch: async (info, init) => {
|
|
67
|
+
const request = normalize_fetch_input(info, init, event.url);
|
|
81
68
|
|
|
82
|
-
|
|
83
|
-
const is_asset_html = options.manifest.assets.has(filename_html);
|
|
69
|
+
const url = new URL(request.url);
|
|
84
70
|
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
if (url.origin !== event.url.origin) {
|
|
72
|
+
// allow cookie passthrough for "same-origin"
|
|
73
|
+
// if SvelteKit is serving my.domain.com:
|
|
74
|
+
// - domain.com WILL NOT receive cookies
|
|
75
|
+
// - my.domain.com WILL receive cookies
|
|
76
|
+
// - api.domain.dom WILL NOT receive cookies
|
|
77
|
+
// - sub.my.domain.com WILL receive cookies
|
|
78
|
+
// ports do not affect the resolution
|
|
79
|
+
// leading dot prevents mydomain.com matching domain.com
|
|
80
|
+
if (
|
|
81
|
+
`.${url.hostname}`.endsWith(`.${event.url.hostname}`) &&
|
|
82
|
+
request.credentials !== 'omit'
|
|
83
|
+
) {
|
|
84
|
+
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
|
85
|
+
if (cookie) request.headers.set('cookie', cookie);
|
|
86
|
+
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
const type = is_asset
|
|
90
|
-
? options.manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))]
|
|
91
|
-
: 'text/html';
|
|
88
|
+
let response = await fetch(request);
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
if (request.mode === 'no-cors') {
|
|
91
|
+
response = new Response('', {
|
|
92
|
+
status: response.status,
|
|
93
|
+
statusText: response.statusText,
|
|
94
|
+
headers: response.headers
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
if (url.origin !== event.url.origin) {
|
|
98
|
+
const acao = response.headers.get('access-control-allow-origin');
|
|
99
|
+
if (!acao || (acao !== event.url.origin && acao !== '*')) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`CORS error: ${
|
|
102
|
+
acao ? 'Incorrect' : 'No'
|
|
103
|
+
} 'Access-Control-Allow-Origin' header is present on the requested resource`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const combined_cookies = { ...initial_cookies };
|
|
109
|
+
return response;
|
|
110
|
+
}
|
|
106
111
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!path_matches(resolved, cookie.path)) continue;
|
|
112
|
+
/** @type {Response} */
|
|
113
|
+
let response;
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
// handle fetch requests for static assets. e.g. prebaked data, etc.
|
|
116
|
+
// we need to support everything the browser's fetch supports
|
|
117
|
+
const prefix = options.paths.assets || options.paths.base;
|
|
118
|
+
const decoded = decodeURIComponent(url.pathname);
|
|
119
|
+
const filename = (
|
|
120
|
+
decoded.startsWith(prefix) ? decoded.slice(prefix.length) : decoded
|
|
121
|
+
).slice(1);
|
|
122
|
+
const filename_html = `${filename}/index.html`; // path may also match path/index.html
|
|
113
123
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
.join('; ');
|
|
124
|
+
const is_asset = options.manifest.assets.has(filename);
|
|
125
|
+
const is_asset_html = options.manifest.assets.has(filename_html);
|
|
117
126
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
}
|
|
127
|
+
if (is_asset || is_asset_html) {
|
|
128
|
+
const file = is_asset ? filename : filename_html;
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
if (options.read) {
|
|
131
|
+
const type = is_asset
|
|
132
|
+
? options.manifest.mimeTypes[filename.slice(filename.lastIndexOf('.'))]
|
|
133
|
+
: 'text/html';
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// in this context anyway, so we take the easy route and ban them
|
|
132
|
-
throw new Error('Request body must be a string');
|
|
133
|
-
}
|
|
135
|
+
return new Response(options.read(file), {
|
|
136
|
+
headers: type ? { 'content-type': type } : {}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
new Request(new URL(requested, event.url).href, { ...opts }),
|
|
137
|
-
options,
|
|
138
|
-
{
|
|
139
|
-
prerender_default,
|
|
140
|
-
...state,
|
|
141
|
-
initiator: route
|
|
140
|
+
return await fetch(request);
|
|
142
141
|
}
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
if (state.prerendering) {
|
|
146
|
-
dependency = { response, body: null };
|
|
147
|
-
state.prerendering.dependencies.set(resolved, dependency);
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
// external
|
|
151
|
-
if (resolved.startsWith('//')) {
|
|
152
|
-
requested = event.url.protocol + requested;
|
|
153
|
-
}
|
|
154
142
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// - domain.com WILL NOT receive cookies
|
|
161
|
-
// - my.domain.com WILL receive cookies
|
|
162
|
-
// - api.domain.dom WILL NOT receive cookies
|
|
163
|
-
// - sub.my.domain.com WILL receive cookies
|
|
164
|
-
// ports do not affect the resolution
|
|
165
|
-
// leading dot prevents mydomain.com matching domain.com
|
|
166
|
-
if (`.${url.hostname}`.endsWith(`.${event.url.hostname}`) && opts.credentials !== 'omit') {
|
|
167
|
-
const cookie = event.request.headers.get('cookie');
|
|
168
|
-
if (cookie) opts.headers.set('cookie', cookie);
|
|
169
|
-
}
|
|
143
|
+
if (request.credentials !== 'omit') {
|
|
144
|
+
const cookie = get_cookie_header(url, request.headers.get('cookie'));
|
|
145
|
+
if (cookie) {
|
|
146
|
+
request.headers.set('cookie', cookie);
|
|
147
|
+
}
|
|
170
148
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
149
|
+
const authorization = event.request.headers.get('authorization');
|
|
150
|
+
if (authorization && !request.headers.has('authorization')) {
|
|
151
|
+
request.headers.set('authorization', authorization);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
175
154
|
|
|
176
|
-
|
|
177
|
-
|
|
155
|
+
if (request_body && typeof request_body !== 'string') {
|
|
156
|
+
// TODO is this still necessary? we just bail out below
|
|
157
|
+
// per https://developer.mozilla.org/en-US/docs/Web/API/Request/Request, this can be a
|
|
158
|
+
// Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object.
|
|
159
|
+
// non-string bodies are irksome to deal with, but luckily aren't particularly useful
|
|
160
|
+
// in this context anyway, so we take the easy route and ban them
|
|
161
|
+
throw new Error('Request body must be a string');
|
|
162
|
+
}
|
|
178
163
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
headers: response.headers
|
|
164
|
+
response = await respond(request, options, {
|
|
165
|
+
prerender_default,
|
|
166
|
+
...state,
|
|
167
|
+
initiator: route
|
|
184
168
|
});
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
throw new Error(
|
|
190
|
-
`CORS error: ${
|
|
191
|
-
acao ? 'Incorrect' : 'No'
|
|
192
|
-
} 'Access-Control-Allow-Origin' header is present on the requested resource`
|
|
193
|
-
);
|
|
194
|
-
}
|
|
169
|
+
|
|
170
|
+
if (state.prerendering) {
|
|
171
|
+
dependency = { response, body: null };
|
|
172
|
+
state.prerendering.dependencies.set(url.pathname, dependency);
|
|
195
173
|
}
|
|
174
|
+
|
|
175
|
+
return response;
|
|
196
176
|
}
|
|
197
|
-
}
|
|
177
|
+
});
|
|
198
178
|
|
|
199
179
|
const set_cookie = response.headers.get('set-cookie');
|
|
200
180
|
if (set_cookie) {
|
|
201
|
-
|
|
181
|
+
set_cookies.push(
|
|
202
182
|
...set_cookie_parser
|
|
203
183
|
.splitCookiesString(set_cookie)
|
|
204
184
|
.map((str) => set_cookie_parser.parseString(str))
|
|
@@ -210,17 +190,7 @@ export function create_fetch({ event, options, state, route, prerender_default }
|
|
|
210
190
|
async function text() {
|
|
211
191
|
const body = await response.text();
|
|
212
192
|
|
|
213
|
-
|
|
214
|
-
/** @type {import('types').ResponseHeaders} */
|
|
215
|
-
const headers = {};
|
|
216
|
-
for (const [key, value] of response.headers) {
|
|
217
|
-
// TODO skip others besides set-cookie and etag?
|
|
218
|
-
if (key !== 'set-cookie' && key !== 'etag') {
|
|
219
|
-
headers[key] = value;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!opts.body || typeof opts.body === 'string') {
|
|
193
|
+
if (!body || typeof body === 'string') {
|
|
224
194
|
const status_number = Number(response.status);
|
|
225
195
|
if (isNaN(status_number)) {
|
|
226
196
|
throw new Error(
|
|
@@ -231,16 +201,31 @@ export function create_fetch({ event, options, state, route, prerender_default }
|
|
|
231
201
|
}
|
|
232
202
|
|
|
233
203
|
fetched.push({
|
|
234
|
-
url:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
body
|
|
242
|
-
}
|
|
204
|
+
url: request.url.startsWith(event.url.origin)
|
|
205
|
+
? request.url.slice(event.url.origin.length)
|
|
206
|
+
: request.url,
|
|
207
|
+
method: request.method,
|
|
208
|
+
request_body: /** @type {string | undefined} */ (request_body),
|
|
209
|
+
response_body: body,
|
|
210
|
+
response: response
|
|
243
211
|
});
|
|
212
|
+
|
|
213
|
+
// ensure that excluded headers can't be read
|
|
214
|
+
const get = response.headers.get;
|
|
215
|
+
response.headers.get = (key) => {
|
|
216
|
+
const lower = key.toLowerCase();
|
|
217
|
+
const value = get.call(response.headers, lower);
|
|
218
|
+
if (value && !lower.startsWith('x-sveltekit-')) {
|
|
219
|
+
const included = resolve_opts.filterSerializedResponseHeaders(lower, value);
|
|
220
|
+
if (!included) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Failed to get response header "${lower}" — it must be included by the \`filterSerializedResponseHeaders\` option: https://kit.svelte.dev/docs/hooks#handle`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return value;
|
|
228
|
+
};
|
|
244
229
|
}
|
|
245
230
|
|
|
246
231
|
if (dependency) {
|
|
@@ -284,5 +269,18 @@ export function create_fetch({ event, options, state, route, prerender_default }
|
|
|
284
269
|
return proxy;
|
|
285
270
|
};
|
|
286
271
|
|
|
287
|
-
return { fetcher, fetched, cookies };
|
|
272
|
+
return { fetcher, fetched, cookies: set_cookies };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {RequestInfo | URL} info
|
|
277
|
+
* @param {RequestInit | undefined} init
|
|
278
|
+
* @param {URL} url
|
|
279
|
+
*/
|
|
280
|
+
function normalize_fetch_input(info, init, url) {
|
|
281
|
+
if (info instanceof Request) {
|
|
282
|
+
return info;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return new Request(typeof info === 'string' ? new URL(info, url) : info, init);
|
|
288
286
|
}
|
|
@@ -131,7 +131,8 @@ export async function render_page(event, route, page, options, state, resolve_op
|
|
|
131
131
|
options,
|
|
132
132
|
state,
|
|
133
133
|
route,
|
|
134
|
-
prerender_default: should_prerender
|
|
134
|
+
prerender_default: should_prerender,
|
|
135
|
+
resolve_opts
|
|
135
136
|
});
|
|
136
137
|
|
|
137
138
|
if (get_option(nodes, 'ssr') === false) {
|
|
@@ -284,7 +284,11 @@ export async function render_response({
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
if (page_config.ssr && page_config.csr) {
|
|
287
|
-
body += `\n\t${fetched
|
|
287
|
+
body += `\n\t${fetched
|
|
288
|
+
.map((item) =>
|
|
289
|
+
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
|
|
290
|
+
)
|
|
291
|
+
.join('\n\t')}`;
|
|
288
292
|
}
|
|
289
293
|
|
|
290
294
|
if (options.service_worker) {
|
|
@@ -321,6 +325,7 @@ export async function render_response({
|
|
|
321
325
|
})) || '';
|
|
322
326
|
|
|
323
327
|
const headers = new Headers({
|
|
328
|
+
'x-sveltekit-page': 'true',
|
|
324
329
|
'content-type': 'text/html',
|
|
325
330
|
etag: `"${hash(html)}"`
|
|
326
331
|
});
|
|
@@ -35,15 +35,35 @@ const pattern = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
|
|
|
35
35
|
* and that the resulting string isn't further modified.
|
|
36
36
|
*
|
|
37
37
|
* @param {import('./types.js').Fetched} fetched
|
|
38
|
+
* @param {(name: string, value: string) => boolean} filter
|
|
38
39
|
* @param {boolean} [prerendering]
|
|
39
40
|
* @returns {string} The raw HTML of a script element carrying the JSON payload.
|
|
40
41
|
* @example const html = serialize_data('/data.json', null, { foo: 'bar' });
|
|
41
42
|
*/
|
|
42
|
-
export function serialize_data(fetched, prerendering = false) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
export function serialize_data(fetched, filter, prerendering = false) {
|
|
44
|
+
/** @type {Record<string, string>} */
|
|
45
|
+
const headers = {};
|
|
46
|
+
|
|
47
|
+
let cache_control = null;
|
|
48
|
+
let age = null;
|
|
49
|
+
|
|
50
|
+
for (const [key, value] of fetched.response.headers) {
|
|
51
|
+
if (filter(key, value)) {
|
|
52
|
+
headers[key] = value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (key === 'cache-control') cache_control = value;
|
|
56
|
+
if (key === 'age') age = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const payload = {
|
|
60
|
+
status: fetched.response.status,
|
|
61
|
+
statusText: fetched.response.statusText,
|
|
62
|
+
headers,
|
|
63
|
+
body: fetched.response_body
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const safe_payload = JSON.stringify(payload).replace(pattern, (match) => replacements[match]);
|
|
47
67
|
|
|
48
68
|
const attrs = [
|
|
49
69
|
'type="application/json"',
|
|
@@ -51,20 +71,15 @@ export function serialize_data(fetched, prerendering = false) {
|
|
|
51
71
|
`data-url=${escape_html_attr(fetched.url)}`
|
|
52
72
|
];
|
|
53
73
|
|
|
54
|
-
if (fetched.
|
|
55
|
-
attrs.push(`data-hash=${escape_html_attr(hash(fetched.
|
|
74
|
+
if (fetched.request_body) {
|
|
75
|
+
attrs.push(`data-hash=${escape_html_attr(hash(fetched.request_body))}`);
|
|
56
76
|
}
|
|
57
77
|
|
|
58
|
-
if (!prerendering && fetched.method === 'GET') {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const age = /** @type {string} */ (fetched.response.headers['age']) ?? '0';
|
|
64
|
-
|
|
65
|
-
const ttl = +match[1] - +age;
|
|
66
|
-
attrs.push(`data-ttl="${ttl}"`);
|
|
67
|
-
}
|
|
78
|
+
if (!prerendering && fetched.method === 'GET' && cache_control) {
|
|
79
|
+
const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);
|
|
80
|
+
if (match) {
|
|
81
|
+
const ttl = +match[1] - +(age ?? '0');
|
|
82
|
+
attrs.push(`data-ttl="${ttl}"`);
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SSRNode, CspDirectives } from 'types';
|
|
2
2
|
import { HttpError } from '../../control.js';
|
|
3
3
|
|
|
4
4
|
export interface Fetched {
|
|
5
5
|
url: string;
|
|
6
6
|
method: string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
statusText: string;
|
|
11
|
-
headers: ResponseHeaders;
|
|
12
|
-
body: string;
|
|
13
|
-
};
|
|
7
|
+
request_body?: string | null;
|
|
8
|
+
response_body: string;
|
|
9
|
+
response: Response;
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
export interface FetchState {
|
package/src/utils/routing.js
CHANGED
|
@@ -12,14 +12,6 @@ export function parse_route_id(id) {
|
|
|
12
12
|
// const add_trailing_slash = !/\.[a-z]+$/.test(key);
|
|
13
13
|
let add_trailing_slash = true;
|
|
14
14
|
|
|
15
|
-
if (/\]\[/.test(id)) {
|
|
16
|
-
throw new Error(`Invalid route ${id} — parameters must be separated`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (count_occurrences('[', id) !== count_occurrences(']', id)) {
|
|
20
|
-
throw new Error(`Invalid route ${id} — brackets are unbalanced`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
const pattern =
|
|
24
16
|
id === ''
|
|
25
17
|
? /^\/$/
|
|
@@ -123,15 +115,3 @@ export function exec(match, names, types, matchers) {
|
|
|
123
115
|
|
|
124
116
|
return params;
|
|
125
117
|
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @param {string} needle
|
|
129
|
-
* @param {string} haystack
|
|
130
|
-
*/
|
|
131
|
-
function count_occurrences(needle, haystack) {
|
|
132
|
-
let count = 0;
|
|
133
|
-
for (let i = 0; i < haystack.length; i += 1) {
|
|
134
|
-
if (haystack[i] === needle) count += 1;
|
|
135
|
-
}
|
|
136
|
-
return count;
|
|
137
|
-
}
|
package/types/index.d.ts
CHANGED
|
@@ -176,10 +176,6 @@ export interface KitConfig {
|
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
export interface ExternalFetch {
|
|
180
|
-
(req: Request): Promise<Response>;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
179
|
export interface Handle {
|
|
184
180
|
(input: {
|
|
185
181
|
event: RequestEvent;
|
|
@@ -191,6 +187,10 @@ export interface HandleError {
|
|
|
191
187
|
(input: { error: Error & { frame?: string }; event: RequestEvent }): void;
|
|
192
188
|
}
|
|
193
189
|
|
|
190
|
+
export interface HandleFetch {
|
|
191
|
+
(input: { event: RequestEvent; request: Request; fetch: typeof fetch }): MaybePromise<Response>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
194
|
/**
|
|
195
195
|
* The generic form of `PageLoad` and `LayoutLoad`. You should import those from `./$types` (see [generated types](https://kit.svelte.dev/docs/types#generated-types))
|
|
196
196
|
* rather than using `Load` directly.
|
|
@@ -273,6 +273,7 @@ export interface RequestHandler<
|
|
|
273
273
|
|
|
274
274
|
export interface ResolveOptions {
|
|
275
275
|
transformPageChunk?: (input: { html: string; done: boolean }) => MaybePromise<string | undefined>;
|
|
276
|
+
filterSerializedResponseHeaders?: (name: string, value: string) => boolean;
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
export class Server {
|
package/types/internal.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { SvelteComponent } from 'svelte/internal';
|
|
|
3
3
|
import {
|
|
4
4
|
Action,
|
|
5
5
|
Config,
|
|
6
|
-
ExternalFetch,
|
|
7
6
|
ServerLoad,
|
|
8
7
|
Handle,
|
|
9
8
|
HandleError,
|
|
@@ -14,7 +13,8 @@ import {
|
|
|
14
13
|
ResolveOptions,
|
|
15
14
|
Server,
|
|
16
15
|
ServerInitOptions,
|
|
17
|
-
SSRManifest
|
|
16
|
+
SSRManifest,
|
|
17
|
+
HandleFetch
|
|
18
18
|
} from './index.js';
|
|
19
19
|
import {
|
|
20
20
|
HttpMethod,
|
|
@@ -90,9 +90,9 @@ export type CSRRoute = {
|
|
|
90
90
|
export type GetParams = (match: RegExpExecArray) => Record<string, string>;
|
|
91
91
|
|
|
92
92
|
export interface Hooks {
|
|
93
|
-
externalFetch: ExternalFetch;
|
|
94
93
|
handle: Handle;
|
|
95
94
|
handleError: HandleError;
|
|
95
|
+
handleFetch: HandleFetch;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export interface ImportNode {
|