@sveltejs/kit 1.0.11 → 1.0.13
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 +3 -3
- package/src/core/adapt/builder.js +1 -1
- package/src/core/{prerender → postbuild}/crawl.js +0 -0
- package/src/core/{prerender → postbuild}/entities.js +0 -0
- package/src/core/{prerender → postbuild}/fallback.js +10 -7
- package/src/core/postbuild/index.js +104 -0
- package/src/core/{prerender → postbuild}/prerender.js +32 -106
- package/src/core/{prerender → postbuild}/queue.js +0 -0
- package/src/core/sync/sync.js +10 -0
- package/src/core/sync/write_client_manifest.js +1 -1
- package/src/core/sync/write_server.js +89 -0
- package/src/core/utils.js +0 -9
- package/src/exports/vite/build/build_server.js +11 -163
- package/src/exports/vite/build/build_service_worker.js +3 -2
- package/src/exports/vite/build/utils.js +1 -0
- package/src/exports/vite/dev/index.js +72 -114
- package/src/exports/vite/index.js +26 -27
- package/src/exports/vite/preview/index.js +11 -12
- package/src/runtime/app/environment.js +1 -1
- package/src/runtime/app/paths.js +1 -1
- package/src/runtime/client/client.js +8 -2
- package/src/runtime/client/fetcher.js +12 -4
- package/src/runtime/client/start.js +1 -2
- package/src/runtime/client/types.d.ts +1 -1
- package/src/runtime/client/utils.js +1 -2
- package/src/runtime/control.js +23 -5
- package/src/runtime/server/ambient.d.ts +8 -0
- package/src/runtime/server/cookie.js +4 -5
- package/src/runtime/server/data/index.js +5 -2
- package/src/runtime/server/endpoint.js +5 -5
- package/src/runtime/server/fetch.js +11 -9
- package/src/runtime/server/index.js +54 -395
- package/src/runtime/server/page/csp.js +9 -11
- package/src/runtime/server/page/index.js +13 -8
- package/src/runtime/server/page/load_data.js +2 -3
- package/src/runtime/server/page/render.js +28 -25
- package/src/runtime/server/page/respond_with_error.js +20 -13
- package/src/runtime/server/page/types.d.ts +0 -1
- package/src/runtime/server/respond.js +419 -0
- package/src/runtime/server/utils.js +21 -4
- package/src/runtime/shared.js +28 -0
- package/types/index.d.ts +10 -5
- package/types/internal.d.ts +22 -39
- package/src/runtime/env.js +0 -12
- package/src/runtime/paths.js +0 -11
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as devalue from 'devalue';
|
|
2
2
|
import { readable, writable } from 'svelte/store';
|
|
3
|
+
import { DEV } from 'esm-env';
|
|
3
4
|
import { hash } from '../../hash.js';
|
|
4
5
|
import { serialize_data } from './serialize_data.js';
|
|
5
6
|
import { s } from '../../../utils/misc.js';
|
|
6
7
|
import { Csp } from './csp.js';
|
|
7
8
|
import { uneval_action_response } from './actions.js';
|
|
8
9
|
import { clarify_devalue_error } from '../utils.js';
|
|
9
|
-
import {
|
|
10
|
+
import { assets, base, version } from '../../shared.js';
|
|
11
|
+
import { env } from '../../env-public.js';
|
|
10
12
|
|
|
11
13
|
// TODO rename this function/module
|
|
12
14
|
|
|
@@ -21,6 +23,7 @@ const updated = {
|
|
|
21
23
|
* branch: Array<import('./types').Loaded>;
|
|
22
24
|
* fetched: Array<import('./types').Fetched>;
|
|
23
25
|
* options: import('types').SSROptions;
|
|
26
|
+
* manifest: import('types').SSRManifest;
|
|
24
27
|
* state: import('types').SSRState;
|
|
25
28
|
* page_config: { ssr: boolean; csr: boolean };
|
|
26
29
|
* status: number;
|
|
@@ -34,6 +37,7 @@ export async function render_response({
|
|
|
34
37
|
branch,
|
|
35
38
|
fetched,
|
|
36
39
|
options,
|
|
40
|
+
manifest,
|
|
37
41
|
state,
|
|
38
42
|
page_config,
|
|
39
43
|
status,
|
|
@@ -52,11 +56,11 @@ export async function render_response({
|
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
const { entry } =
|
|
59
|
+
const { entry } = manifest._;
|
|
56
60
|
|
|
57
61
|
const stylesheets = new Set(entry.stylesheets);
|
|
58
62
|
const modulepreloads = new Set(entry.imports);
|
|
59
|
-
const fonts = new Set(
|
|
63
|
+
const fonts = new Set(manifest._.entry.fonts);
|
|
60
64
|
|
|
61
65
|
/** @type {Set<string>} */
|
|
62
66
|
const link_header_preloads = new Set();
|
|
@@ -130,7 +134,6 @@ export async function render_response({
|
|
|
130
134
|
let body = rendered.html;
|
|
131
135
|
|
|
132
136
|
const csp = new Csp(options.csp, {
|
|
133
|
-
dev: options.dev,
|
|
134
137
|
prerender: !!state.prerendering
|
|
135
138
|
});
|
|
136
139
|
|
|
@@ -140,25 +143,25 @@ export async function render_response({
|
|
|
140
143
|
* The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
|
|
141
144
|
* @type {string}
|
|
142
145
|
*/
|
|
143
|
-
let
|
|
146
|
+
let resolved_assets;
|
|
144
147
|
|
|
145
|
-
if (
|
|
148
|
+
if (assets) {
|
|
146
149
|
// if an asset path is specified, use it
|
|
147
|
-
|
|
150
|
+
resolved_assets = assets;
|
|
148
151
|
} else if (state.prerendering?.fallback) {
|
|
149
152
|
// if we're creating a fallback page, asset paths need to be root-relative
|
|
150
|
-
|
|
153
|
+
resolved_assets = base;
|
|
151
154
|
} else {
|
|
152
155
|
// otherwise we want asset paths to be relative to the page, so that they
|
|
153
156
|
// will work in odd contexts like IPFS, the internet archive, and so on
|
|
154
|
-
const segments = event.url.pathname.slice(
|
|
155
|
-
|
|
157
|
+
const segments = event.url.pathname.slice(base.length).split('/').slice(2);
|
|
158
|
+
resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
/** @param {string} path */
|
|
159
|
-
const prefixed = (path) => (path.startsWith('/') ? path : `${
|
|
162
|
+
const prefixed = (path) => (path.startsWith('/') ? path : `${resolved_assets}/${path}`);
|
|
160
163
|
|
|
161
|
-
const serialized = { data: '', form: 'null' };
|
|
164
|
+
const serialized = { data: '', form: 'null', error: 'null' };
|
|
162
165
|
|
|
163
166
|
try {
|
|
164
167
|
serialized.data = `[${branch
|
|
@@ -196,11 +199,14 @@ export async function render_response({
|
|
|
196
199
|
serialized.form = uneval_action_response(form_value, /** @type {string} */ (event.route.id));
|
|
197
200
|
}
|
|
198
201
|
|
|
202
|
+
if (error) {
|
|
203
|
+
serialized.error = devalue.uneval(error);
|
|
204
|
+
}
|
|
205
|
+
|
|
199
206
|
if (inline_styles.size > 0) {
|
|
200
207
|
const content = Array.from(inline_styles.values()).join('\n');
|
|
201
208
|
|
|
202
|
-
const attributes = [];
|
|
203
|
-
if (options.dev) attributes.push(' data-sveltekit');
|
|
209
|
+
const attributes = __SVELTEKIT_DEV__ ? [' data-sveltekit'] : [];
|
|
204
210
|
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
|
|
205
211
|
|
|
206
212
|
csp.add_style(content);
|
|
@@ -246,27 +252,24 @@ export async function render_response({
|
|
|
246
252
|
|
|
247
253
|
if (page_config.csr) {
|
|
248
254
|
const opts = [
|
|
249
|
-
`env: ${s(
|
|
250
|
-
`paths: ${s(
|
|
255
|
+
`env: ${s(env)}`,
|
|
256
|
+
`paths: ${s({ assets, base })}`,
|
|
251
257
|
`target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
|
|
252
|
-
`version: ${s(
|
|
258
|
+
`version: ${s(version)}`
|
|
253
259
|
];
|
|
254
260
|
|
|
255
261
|
if (page_config.ssr) {
|
|
256
262
|
const hydrate = [
|
|
257
263
|
`node_ids: [${branch.map(({ node }) => node.index).join(', ')}]`,
|
|
258
264
|
`data: ${serialized.data}`,
|
|
259
|
-
`form: ${serialized.form}
|
|
265
|
+
`form: ${serialized.form}`,
|
|
266
|
+
`error: ${serialized.error}`
|
|
260
267
|
];
|
|
261
268
|
|
|
262
269
|
if (status !== 200) {
|
|
263
270
|
hydrate.push(`status: ${status}`);
|
|
264
271
|
}
|
|
265
272
|
|
|
266
|
-
if (error) {
|
|
267
|
-
hydrate.push(`error: ${devalue.uneval(error)}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
273
|
if (options.embedded) {
|
|
271
274
|
hydrate.push(`params: ${devalue.uneval(event.params)}`, `route: ${s(event.route)}`);
|
|
272
275
|
}
|
|
@@ -314,7 +317,7 @@ export async function render_response({
|
|
|
314
317
|
}
|
|
315
318
|
|
|
316
319
|
if (options.service_worker) {
|
|
317
|
-
const opts =
|
|
320
|
+
const opts = __SVELTEKIT_DEV__ ? `, { type: 'module' }` : '';
|
|
318
321
|
|
|
319
322
|
// we use an anonymous function instead of an arrow function to support
|
|
320
323
|
// older browsers (https://github.com/sveltejs/kit/pull/5417)
|
|
@@ -354,10 +357,10 @@ export async function render_response({
|
|
|
354
357
|
// add the content after the script/css links so the link elements are parsed first
|
|
355
358
|
head += rendered.head;
|
|
356
359
|
|
|
357
|
-
const html = options.
|
|
360
|
+
const html = options.templates.app({
|
|
358
361
|
head,
|
|
359
362
|
body,
|
|
360
|
-
assets,
|
|
363
|
+
assets: resolved_assets,
|
|
361
364
|
nonce: /** @type {string} */ (csp.nonce)
|
|
362
365
|
});
|
|
363
366
|
|
|
@@ -11,27 +11,34 @@ import { HttpError, Redirect } from '../../control.js';
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @typedef {import('./types.js').Loaded} Loaded
|
|
14
|
-
* @typedef {import('types').SSROptions} SSROptions
|
|
15
|
-
* @typedef {import('types').SSRState} SSRState
|
|
16
14
|
*/
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* @param {{
|
|
20
18
|
* event: import('types').RequestEvent;
|
|
21
|
-
* options: SSROptions;
|
|
22
|
-
*
|
|
19
|
+
* options: import('types').SSROptions;
|
|
20
|
+
* manifest: import('types').SSRManifest;
|
|
21
|
+
* state: import('types').SSRState;
|
|
23
22
|
* status: number;
|
|
24
23
|
* error: unknown;
|
|
25
24
|
* resolve_opts: import('types').RequiredResolveOptions;
|
|
26
25
|
* }} opts
|
|
27
26
|
*/
|
|
28
|
-
export async function respond_with_error({
|
|
27
|
+
export async function respond_with_error({
|
|
28
|
+
event,
|
|
29
|
+
options,
|
|
30
|
+
manifest,
|
|
31
|
+
state,
|
|
32
|
+
status,
|
|
33
|
+
error,
|
|
34
|
+
resolve_opts
|
|
35
|
+
}) {
|
|
29
36
|
/** @type {import('./types').Fetched[]} */
|
|
30
37
|
const fetched = [];
|
|
31
38
|
|
|
32
39
|
try {
|
|
33
40
|
const branch = [];
|
|
34
|
-
const default_layout = await
|
|
41
|
+
const default_layout = await manifest._.nodes[0](); // 0 is always the root layout
|
|
35
42
|
const ssr = get_option([default_layout], 'ssr') ?? true;
|
|
36
43
|
const csr = get_option([default_layout], 'csr') ?? true;
|
|
37
44
|
|
|
@@ -40,7 +47,6 @@ export async function respond_with_error({ event, options, state, status, error,
|
|
|
40
47
|
|
|
41
48
|
const server_data_promise = load_server_data({
|
|
42
49
|
event,
|
|
43
|
-
options,
|
|
44
50
|
state,
|
|
45
51
|
node: default_layout,
|
|
46
52
|
parent: async () => ({})
|
|
@@ -66,7 +72,7 @@ export async function respond_with_error({ event, options, state, status, error,
|
|
|
66
72
|
data
|
|
67
73
|
},
|
|
68
74
|
{
|
|
69
|
-
node: await
|
|
75
|
+
node: await manifest._.nodes[1](), // 1 is always the root error
|
|
70
76
|
data: null,
|
|
71
77
|
server_data: null
|
|
72
78
|
}
|
|
@@ -75,6 +81,7 @@ export async function respond_with_error({ event, options, state, status, error,
|
|
|
75
81
|
|
|
76
82
|
return await render_response({
|
|
77
83
|
options,
|
|
84
|
+
manifest,
|
|
78
85
|
state,
|
|
79
86
|
page_config: {
|
|
80
87
|
ssr,
|
|
@@ -87,17 +94,17 @@ export async function respond_with_error({ event, options, state, status, error,
|
|
|
87
94
|
event,
|
|
88
95
|
resolve_opts
|
|
89
96
|
});
|
|
90
|
-
} catch (
|
|
97
|
+
} catch (e) {
|
|
91
98
|
// Edge case: If route is a 404 and the user redirects to somewhere from the root layout,
|
|
92
99
|
// we end up here.
|
|
93
|
-
if (
|
|
94
|
-
return redirect_response(
|
|
100
|
+
if (e instanceof Redirect) {
|
|
101
|
+
return redirect_response(e.status, e.location);
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
return static_error_page(
|
|
98
105
|
options,
|
|
99
|
-
|
|
100
|
-
(await handle_error_and_jsonify(event, options,
|
|
106
|
+
e instanceof HttpError ? e.status : 500,
|
|
107
|
+
(await handle_error_and_jsonify(event, options, e)).message
|
|
101
108
|
);
|
|
102
109
|
}
|
|
103
110
|
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { DEV } from 'esm-env';
|
|
2
|
+
import { is_endpoint_request, render_endpoint } from './endpoint.js';
|
|
3
|
+
import { render_page } from './page/index.js';
|
|
4
|
+
import { render_response } from './page/render.js';
|
|
5
|
+
import { respond_with_error } from './page/respond_with_error.js';
|
|
6
|
+
import { is_form_content_type } from '../../utils/http.js';
|
|
7
|
+
import { GENERIC_ERROR, get_option, handle_fatal_error, redirect_response } from './utils.js';
|
|
8
|
+
import {
|
|
9
|
+
decode_pathname,
|
|
10
|
+
decode_params,
|
|
11
|
+
disable_search,
|
|
12
|
+
has_data_suffix,
|
|
13
|
+
normalize_path,
|
|
14
|
+
strip_data_suffix
|
|
15
|
+
} from '../../utils/url.js';
|
|
16
|
+
import { exec } from '../../utils/routing.js';
|
|
17
|
+
import { INVALIDATED_PARAM, redirect_json_response, render_data } from './data/index.js';
|
|
18
|
+
import { add_cookies_to_headers, get_cookies } from './cookie.js';
|
|
19
|
+
import { create_fetch } from './fetch.js';
|
|
20
|
+
import { Redirect } from '../control.js';
|
|
21
|
+
import {
|
|
22
|
+
validate_common_exports,
|
|
23
|
+
validate_page_server_exports,
|
|
24
|
+
validate_server_exports
|
|
25
|
+
} from '../../utils/exports.js';
|
|
26
|
+
import { error, json } from '../../exports/index.js';
|
|
27
|
+
import * as paths from '../shared.js';
|
|
28
|
+
|
|
29
|
+
/* global __SVELTEKIT_ADAPTER_NAME__ */
|
|
30
|
+
|
|
31
|
+
/** @type {import('types').RequiredResolveOptions['transformPageChunk']} */
|
|
32
|
+
const default_transform = ({ html }) => html;
|
|
33
|
+
|
|
34
|
+
/** @type {import('types').RequiredResolveOptions['filterSerializedResponseHeaders']} */
|
|
35
|
+
const default_filter = () => false;
|
|
36
|
+
|
|
37
|
+
/** @type {import('types').RequiredResolveOptions['preload']} */
|
|
38
|
+
const default_preload = ({ type }) => type === 'js' || type === 'css';
|
|
39
|
+
|
|
40
|
+
/** @type {import('types').Respond} */
|
|
41
|
+
export async function respond(request, options, manifest, state) {
|
|
42
|
+
/** URL but stripped from the potential `/__data.json` suffix and its search param */
|
|
43
|
+
let url = new URL(request.url);
|
|
44
|
+
|
|
45
|
+
if (options.csrf_check_origin) {
|
|
46
|
+
const forbidden =
|
|
47
|
+
request.method === 'POST' &&
|
|
48
|
+
request.headers.get('origin') !== url.origin &&
|
|
49
|
+
is_form_content_type(request);
|
|
50
|
+
|
|
51
|
+
if (forbidden) {
|
|
52
|
+
const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
|
|
53
|
+
if (request.headers.get('accept') === 'application/json') {
|
|
54
|
+
return json(csrf_error.body, { status: csrf_error.status });
|
|
55
|
+
}
|
|
56
|
+
return new Response(csrf_error.body.message, { status: csrf_error.status });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let decoded;
|
|
61
|
+
try {
|
|
62
|
+
decoded = decode_pathname(url.pathname);
|
|
63
|
+
} catch {
|
|
64
|
+
return new Response('Malformed URI', { status: 400 });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @type {import('types').SSRRoute | null} */
|
|
68
|
+
let route = null;
|
|
69
|
+
|
|
70
|
+
/** @type {Record<string, string>} */
|
|
71
|
+
let params = {};
|
|
72
|
+
|
|
73
|
+
if (paths.base && !state.prerendering?.fallback) {
|
|
74
|
+
if (!decoded.startsWith(paths.base)) {
|
|
75
|
+
return new Response('Not found', { status: 404 });
|
|
76
|
+
}
|
|
77
|
+
decoded = decoded.slice(paths.base.length) || '/';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const is_data_request = has_data_suffix(decoded);
|
|
81
|
+
/** @type {boolean[] | undefined} */
|
|
82
|
+
let invalidated_data_nodes;
|
|
83
|
+
if (is_data_request) {
|
|
84
|
+
decoded = strip_data_suffix(decoded) || '/';
|
|
85
|
+
url.pathname = strip_data_suffix(url.pathname) || '/';
|
|
86
|
+
invalidated_data_nodes = url.searchParams.get(INVALIDATED_PARAM)?.split('_').map(Boolean);
|
|
87
|
+
url.searchParams.delete(INVALIDATED_PARAM);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!state.prerendering?.fallback) {
|
|
91
|
+
// TODO this could theoretically break — should probably be inside a try-catch
|
|
92
|
+
const matchers = await manifest._.matchers();
|
|
93
|
+
|
|
94
|
+
for (const candidate of manifest._.routes) {
|
|
95
|
+
const match = candidate.pattern.exec(decoded);
|
|
96
|
+
if (!match) continue;
|
|
97
|
+
|
|
98
|
+
const matched = exec(match, candidate.params, matchers);
|
|
99
|
+
if (matched) {
|
|
100
|
+
route = candidate;
|
|
101
|
+
params = decode_params(matched);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @type {import('types').TrailingSlash | void} */
|
|
108
|
+
let trailing_slash = undefined;
|
|
109
|
+
|
|
110
|
+
/** @type {Record<string, string>} */
|
|
111
|
+
const headers = {};
|
|
112
|
+
|
|
113
|
+
/** @type {import('types').RequestEvent} */
|
|
114
|
+
const event = {
|
|
115
|
+
// @ts-expect-error `cookies` and `fetch` need to be created after the `event` itself
|
|
116
|
+
cookies: null,
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
fetch: null,
|
|
119
|
+
getClientAddress:
|
|
120
|
+
state.getClientAddress ||
|
|
121
|
+
(() => {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`${__SVELTEKIT_ADAPTER_NAME__} does not specify getClientAddress. Please raise an issue`
|
|
124
|
+
);
|
|
125
|
+
}),
|
|
126
|
+
locals: {},
|
|
127
|
+
params,
|
|
128
|
+
platform: state.platform,
|
|
129
|
+
request,
|
|
130
|
+
route: { id: route?.id ?? null },
|
|
131
|
+
setHeaders: (new_headers) => {
|
|
132
|
+
for (const key in new_headers) {
|
|
133
|
+
const lower = key.toLowerCase();
|
|
134
|
+
const value = new_headers[key];
|
|
135
|
+
|
|
136
|
+
if (lower === 'set-cookie') {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Use \`event.cookies.set(name, value, options)\` instead of \`event.setHeaders\` to set cookies`
|
|
139
|
+
);
|
|
140
|
+
} else if (lower in headers) {
|
|
141
|
+
throw new Error(`"${key}" header is already set`);
|
|
142
|
+
} else {
|
|
143
|
+
headers[lower] = value;
|
|
144
|
+
|
|
145
|
+
if (state.prerendering && lower === 'cache-control') {
|
|
146
|
+
state.prerendering.cache = /** @type {string} */ (value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
url,
|
|
152
|
+
isDataRequest: is_data_request
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** @type {import('types').RequiredResolveOptions} */
|
|
156
|
+
let resolve_opts = {
|
|
157
|
+
transformPageChunk: default_transform,
|
|
158
|
+
filterSerializedResponseHeaders: default_filter,
|
|
159
|
+
preload: default_preload
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
// determine whether we need to redirect to add/remove a trailing slash
|
|
164
|
+
if (route && !is_data_request) {
|
|
165
|
+
if (route.page) {
|
|
166
|
+
const nodes = await Promise.all([
|
|
167
|
+
// we use == here rather than === because [undefined] serializes as "[null]"
|
|
168
|
+
...route.page.layouts.map((n) => (n == undefined ? n : manifest._.nodes[n]())),
|
|
169
|
+
manifest._.nodes[route.page.leaf]()
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
if (DEV) {
|
|
173
|
+
const layouts = nodes.slice(0, -1);
|
|
174
|
+
const page = nodes.at(-1);
|
|
175
|
+
|
|
176
|
+
for (const layout of layouts) {
|
|
177
|
+
if (layout) {
|
|
178
|
+
validate_common_exports(layout.server, /** @type {string} */ (layout.server_id));
|
|
179
|
+
validate_common_exports(
|
|
180
|
+
layout.universal,
|
|
181
|
+
/** @type {string} */ (layout.universal_id)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (page) {
|
|
187
|
+
validate_page_server_exports(page.server, /** @type {string} */ (page.server_id));
|
|
188
|
+
validate_common_exports(page.universal, /** @type {string} */ (page.universal_id));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
trailing_slash = get_option(nodes, 'trailingSlash');
|
|
193
|
+
} else if (route.endpoint) {
|
|
194
|
+
const node = await route.endpoint();
|
|
195
|
+
trailing_slash = node.trailingSlash;
|
|
196
|
+
|
|
197
|
+
if (DEV) {
|
|
198
|
+
validate_server_exports(node, /** @type {string} */ (route.endpoint_id));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const normalized = normalize_path(url.pathname, trailing_slash ?? 'never');
|
|
203
|
+
|
|
204
|
+
if (normalized !== url.pathname && !state.prerendering?.fallback) {
|
|
205
|
+
return new Response(undefined, {
|
|
206
|
+
status: 301,
|
|
207
|
+
headers: {
|
|
208
|
+
'x-sveltekit-normalize': '1',
|
|
209
|
+
location:
|
|
210
|
+
// ensure paths starting with '//' are not treated as protocol-relative
|
|
211
|
+
(normalized.startsWith('//') ? url.origin + normalized : normalized) +
|
|
212
|
+
(url.search === '?' ? '' : url.search)
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const { cookies, new_cookies, get_cookie_header } = get_cookies(
|
|
219
|
+
request,
|
|
220
|
+
url,
|
|
221
|
+
trailing_slash ?? 'never'
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
event.cookies = cookies;
|
|
225
|
+
event.fetch = create_fetch({ event, options, manifest, state, get_cookie_header });
|
|
226
|
+
|
|
227
|
+
if (state.prerendering && !state.prerendering.fallback) disable_search(url);
|
|
228
|
+
|
|
229
|
+
const response = await options.hooks.handle({
|
|
230
|
+
event,
|
|
231
|
+
resolve: (event, opts) =>
|
|
232
|
+
resolve(event, opts).then((response) => {
|
|
233
|
+
// add headers/cookies here, rather than inside `resolve`, so that we
|
|
234
|
+
// can do it once for all responses instead of once per `return`
|
|
235
|
+
for (const key in headers) {
|
|
236
|
+
const value = headers[key];
|
|
237
|
+
response.headers.set(key, /** @type {string} */ (value));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
add_cookies_to_headers(response.headers, Object.values(new_cookies));
|
|
241
|
+
|
|
242
|
+
if (state.prerendering && event.route.id !== null) {
|
|
243
|
+
response.headers.set('x-sveltekit-routeid', encodeURI(event.route.id));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return response;
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// respond with 304 if etag matches
|
|
251
|
+
if (response.status === 200 && response.headers.has('etag')) {
|
|
252
|
+
let if_none_match_value = request.headers.get('if-none-match');
|
|
253
|
+
|
|
254
|
+
// ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
|
|
255
|
+
if (if_none_match_value?.startsWith('W/"')) {
|
|
256
|
+
if_none_match_value = if_none_match_value.substring(2);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const etag = /** @type {string} */ (response.headers.get('etag'));
|
|
260
|
+
|
|
261
|
+
if (if_none_match_value === etag) {
|
|
262
|
+
const headers = new Headers({ etag });
|
|
263
|
+
|
|
264
|
+
// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 + set-cookie
|
|
265
|
+
for (const key of [
|
|
266
|
+
'cache-control',
|
|
267
|
+
'content-location',
|
|
268
|
+
'date',
|
|
269
|
+
'expires',
|
|
270
|
+
'vary',
|
|
271
|
+
'set-cookie'
|
|
272
|
+
]) {
|
|
273
|
+
const value = response.headers.get(key);
|
|
274
|
+
if (value) headers.set(key, value);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return new Response(undefined, {
|
|
278
|
+
status: 304,
|
|
279
|
+
headers
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Edge case: If user does `return Response(30x)` in handle hook while processing a data request,
|
|
285
|
+
// we need to transform the redirect response to a corresponding JSON response.
|
|
286
|
+
if (is_data_request && response.status >= 300 && response.status <= 308) {
|
|
287
|
+
const location = response.headers.get('location');
|
|
288
|
+
if (location) {
|
|
289
|
+
return redirect_json_response(new Redirect(/** @type {any} */ (response.status), location));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return response;
|
|
294
|
+
} catch (e) {
|
|
295
|
+
if (e instanceof Redirect) {
|
|
296
|
+
if (is_data_request) {
|
|
297
|
+
return redirect_json_response(e);
|
|
298
|
+
} else {
|
|
299
|
+
return redirect_response(e.status, e.location);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return await handle_fatal_error(event, options, e);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
*
|
|
307
|
+
* @param {import('types').RequestEvent} event
|
|
308
|
+
* @param {import('types').ResolveOptions} [opts]
|
|
309
|
+
*/
|
|
310
|
+
async function resolve(event, opts) {
|
|
311
|
+
try {
|
|
312
|
+
if (opts) {
|
|
313
|
+
if ('ssr' in opts) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
resolve_opts = {
|
|
320
|
+
transformPageChunk: opts.transformPageChunk || default_transform,
|
|
321
|
+
filterSerializedResponseHeaders: opts.filterSerializedResponseHeaders || default_filter,
|
|
322
|
+
preload: opts.preload || default_preload
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (state.prerendering?.fallback) {
|
|
327
|
+
return await render_response({
|
|
328
|
+
event,
|
|
329
|
+
options,
|
|
330
|
+
manifest,
|
|
331
|
+
state,
|
|
332
|
+
page_config: { ssr: false, csr: true },
|
|
333
|
+
status: 200,
|
|
334
|
+
error: null,
|
|
335
|
+
branch: [],
|
|
336
|
+
fetched: [],
|
|
337
|
+
resolve_opts
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (route) {
|
|
342
|
+
/** @type {Response} */
|
|
343
|
+
let response;
|
|
344
|
+
|
|
345
|
+
if (is_data_request) {
|
|
346
|
+
response = await render_data(
|
|
347
|
+
event,
|
|
348
|
+
route,
|
|
349
|
+
options,
|
|
350
|
+
manifest,
|
|
351
|
+
state,
|
|
352
|
+
invalidated_data_nodes,
|
|
353
|
+
trailing_slash ?? 'never'
|
|
354
|
+
);
|
|
355
|
+
} else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
|
|
356
|
+
response = await render_endpoint(event, await route.endpoint(), state);
|
|
357
|
+
} else if (route.page) {
|
|
358
|
+
response = await render_page(
|
|
359
|
+
event,
|
|
360
|
+
route,
|
|
361
|
+
route.page,
|
|
362
|
+
options,
|
|
363
|
+
manifest,
|
|
364
|
+
state,
|
|
365
|
+
resolve_opts
|
|
366
|
+
);
|
|
367
|
+
} else {
|
|
368
|
+
// a route will always have a page or an endpoint, but TypeScript
|
|
369
|
+
// doesn't know that
|
|
370
|
+
throw new Error('This should never happen');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return response;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (state.initiator === GENERIC_ERROR) {
|
|
377
|
+
return new Response('Internal Server Error', {
|
|
378
|
+
status: 500
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// if this request came direct from the user, rather than
|
|
383
|
+
// via a `fetch` in a `load`, render a 404 page
|
|
384
|
+
if (!state.initiator) {
|
|
385
|
+
return await respond_with_error({
|
|
386
|
+
event,
|
|
387
|
+
options,
|
|
388
|
+
manifest,
|
|
389
|
+
state,
|
|
390
|
+
status: 404,
|
|
391
|
+
error: new Error(`Not found: ${event.url.pathname}`),
|
|
392
|
+
resolve_opts
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (state.prerendering) {
|
|
397
|
+
return new Response('not found', { status: 404 });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// we can't load the endpoint from our own manifest,
|
|
401
|
+
// so we need to make an actual HTTP request
|
|
402
|
+
return await fetch(request);
|
|
403
|
+
} catch (e) {
|
|
404
|
+
// TODO if `e` is instead named `error`, some fucked up Vite transformation happens
|
|
405
|
+
// and I don't even know how to describe it. need to investigate at some point
|
|
406
|
+
|
|
407
|
+
// HttpError from endpoint can end up here - TODO should it be handled there instead?
|
|
408
|
+
return await handle_fatal_error(event, options, e);
|
|
409
|
+
} finally {
|
|
410
|
+
event.cookies.set = () => {
|
|
411
|
+
throw new Error('Cannot use `cookies.set(...)` after the response has been generated');
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
event.setHeaders = () => {
|
|
415
|
+
throw new Error('Cannot use `setHeaders(...)` after the response has been generated');
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|