@sveltejs/kit 1.0.12 → 1.1.0
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 +1 -1
- 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 +106 -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/sync/write_tsconfig.js +10 -1
- 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 +81 -28
- package/src/exports/vite/preview/index.js +11 -12
- package/src/runtime/app/environment.js +1 -1
- package/src/runtime/app/forms.js +5 -1
- package/src/runtime/app/paths.js +1 -1
- package/src/runtime/client/client.js +1 -1
- package/src/runtime/client/start.js +1 -2
- 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 +3 -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 +26 -19
- 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/ambient.d.ts +7 -29
- package/types/internal.d.ts +23 -37
- 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();
|
|
@@ -73,6 +77,11 @@ export async function render_response({
|
|
|
73
77
|
: null;
|
|
74
78
|
|
|
75
79
|
if (page_config.ssr) {
|
|
80
|
+
if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) {
|
|
81
|
+
// Can only be the leaf, layouts have a fallback component generated
|
|
82
|
+
throw new Error(`Missing +page.svelte component for route ${event.route.id}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
/** @type {Record<string, any>} */
|
|
77
86
|
const props = {
|
|
78
87
|
stores: {
|
|
@@ -130,7 +139,6 @@ export async function render_response({
|
|
|
130
139
|
let body = rendered.html;
|
|
131
140
|
|
|
132
141
|
const csp = new Csp(options.csp, {
|
|
133
|
-
dev: options.dev,
|
|
134
142
|
prerender: !!state.prerendering
|
|
135
143
|
});
|
|
136
144
|
|
|
@@ -140,23 +148,23 @@ export async function render_response({
|
|
|
140
148
|
* The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
|
|
141
149
|
* @type {string}
|
|
142
150
|
*/
|
|
143
|
-
let
|
|
151
|
+
let resolved_assets;
|
|
144
152
|
|
|
145
|
-
if (
|
|
153
|
+
if (assets) {
|
|
146
154
|
// if an asset path is specified, use it
|
|
147
|
-
|
|
155
|
+
resolved_assets = assets;
|
|
148
156
|
} else if (state.prerendering?.fallback) {
|
|
149
157
|
// if we're creating a fallback page, asset paths need to be root-relative
|
|
150
|
-
|
|
158
|
+
resolved_assets = base;
|
|
151
159
|
} else {
|
|
152
160
|
// otherwise we want asset paths to be relative to the page, so that they
|
|
153
161
|
// will work in odd contexts like IPFS, the internet archive, and so on
|
|
154
|
-
const segments = event.url.pathname.slice(
|
|
155
|
-
|
|
162
|
+
const segments = event.url.pathname.slice(base.length).split('/').slice(2);
|
|
163
|
+
resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
/** @param {string} path */
|
|
159
|
-
const prefixed = (path) => (path.startsWith('/') ? path : `${
|
|
167
|
+
const prefixed = (path) => (path.startsWith('/') ? path : `${resolved_assets}/${path}`);
|
|
160
168
|
|
|
161
169
|
const serialized = { data: '', form: 'null', error: 'null' };
|
|
162
170
|
|
|
@@ -203,8 +211,7 @@ export async function render_response({
|
|
|
203
211
|
if (inline_styles.size > 0) {
|
|
204
212
|
const content = Array.from(inline_styles.values()).join('\n');
|
|
205
213
|
|
|
206
|
-
const attributes = [];
|
|
207
|
-
if (options.dev) attributes.push(' data-sveltekit');
|
|
214
|
+
const attributes = __SVELTEKIT_DEV__ ? [' data-sveltekit'] : [];
|
|
208
215
|
if (csp.style_needs_nonce) attributes.push(` nonce="${csp.nonce}"`);
|
|
209
216
|
|
|
210
217
|
csp.add_style(content);
|
|
@@ -250,10 +257,10 @@ export async function render_response({
|
|
|
250
257
|
|
|
251
258
|
if (page_config.csr) {
|
|
252
259
|
const opts = [
|
|
253
|
-
`env: ${s(
|
|
254
|
-
`paths: ${s(
|
|
260
|
+
`env: ${s(env)}`,
|
|
261
|
+
`paths: ${s({ assets, base })}`,
|
|
255
262
|
`target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
|
|
256
|
-
`version: ${s(
|
|
263
|
+
`version: ${s(version)}`
|
|
257
264
|
];
|
|
258
265
|
|
|
259
266
|
if (page_config.ssr) {
|
|
@@ -315,7 +322,7 @@ export async function render_response({
|
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
if (options.service_worker) {
|
|
318
|
-
const opts =
|
|
325
|
+
const opts = __SVELTEKIT_DEV__ ? `, { type: 'module' }` : '';
|
|
319
326
|
|
|
320
327
|
// we use an anonymous function instead of an arrow function to support
|
|
321
328
|
// older browsers (https://github.com/sveltejs/kit/pull/5417)
|
|
@@ -355,10 +362,10 @@ export async function render_response({
|
|
|
355
362
|
// add the content after the script/css links so the link elements are parsed first
|
|
356
363
|
head += rendered.head;
|
|
357
364
|
|
|
358
|
-
const html = options.
|
|
365
|
+
const html = options.templates.app({
|
|
359
366
|
head,
|
|
360
367
|
body,
|
|
361
|
-
assets,
|
|
368
|
+
assets: resolved_assets,
|
|
362
369
|
nonce: /** @type {string} */ (csp.nonce)
|
|
363
370
|
});
|
|
364
371
|
|
|
@@ -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
|
+
}
|
|
@@ -3,6 +3,7 @@ import { coalesce_to_error } from '../../utils/error.js';
|
|
|
3
3
|
import { negotiate } from '../../utils/http.js';
|
|
4
4
|
import { has_data_suffix } from '../../utils/url.js';
|
|
5
5
|
import { HttpError } from '../control.js';
|
|
6
|
+
import { fix_stack_trace } from '../shared.js';
|
|
6
7
|
|
|
7
8
|
/** @param {any} body */
|
|
8
9
|
export function is_pojo(body) {
|
|
@@ -74,7 +75,7 @@ export function get_option(nodes, option) {
|
|
|
74
75
|
* @param {string} message
|
|
75
76
|
*/
|
|
76
77
|
export function static_error_page(options, status, message) {
|
|
77
|
-
return new Response(options.
|
|
78
|
+
return new Response(options.templates.error({ status, message }), {
|
|
78
79
|
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
79
80
|
status
|
|
80
81
|
});
|
|
@@ -110,13 +111,29 @@ export async function handle_fatal_error(event, options, error) {
|
|
|
110
111
|
* @param {import('types').RequestEvent} event
|
|
111
112
|
* @param {import('types').SSROptions} options
|
|
112
113
|
* @param {any} error
|
|
113
|
-
* @returns {
|
|
114
|
+
* @returns {Promise<App.Error>}
|
|
114
115
|
*/
|
|
115
|
-
export function handle_error_and_jsonify(event, options, error) {
|
|
116
|
+
export async function handle_error_and_jsonify(event, options, error) {
|
|
116
117
|
if (error instanceof HttpError) {
|
|
117
118
|
return error.body;
|
|
118
119
|
} else {
|
|
119
|
-
|
|
120
|
+
if (__SVELTEKIT_DEV__) {
|
|
121
|
+
error = new Proxy(error, {
|
|
122
|
+
get: (target, property) => {
|
|
123
|
+
if (property === 'stack') {
|
|
124
|
+
return fix_stack_trace(target.stack);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Reflect.get(target, property, target);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
(await options.hooks.handleError({ error, event })) ?? {
|
|
134
|
+
message: event.route.id != null ? 'Internal Error' : 'Not Found'
|
|
135
|
+
}
|
|
136
|
+
);
|
|
120
137
|
}
|
|
121
138
|
}
|
|
122
139
|
|