@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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/core/adapt/builder.js +1 -1
  3. package/src/core/{prerender → postbuild}/crawl.js +0 -0
  4. package/src/core/{prerender → postbuild}/entities.js +0 -0
  5. package/src/core/{prerender → postbuild}/fallback.js +10 -7
  6. package/src/core/postbuild/index.js +106 -0
  7. package/src/core/{prerender → postbuild}/prerender.js +32 -106
  8. package/src/core/{prerender → postbuild}/queue.js +0 -0
  9. package/src/core/sync/sync.js +10 -0
  10. package/src/core/sync/write_client_manifest.js +1 -1
  11. package/src/core/sync/write_server.js +89 -0
  12. package/src/core/sync/write_tsconfig.js +10 -1
  13. package/src/core/utils.js +0 -9
  14. package/src/exports/vite/build/build_server.js +11 -163
  15. package/src/exports/vite/build/build_service_worker.js +3 -2
  16. package/src/exports/vite/build/utils.js +1 -0
  17. package/src/exports/vite/dev/index.js +72 -114
  18. package/src/exports/vite/index.js +81 -28
  19. package/src/exports/vite/preview/index.js +11 -12
  20. package/src/runtime/app/environment.js +1 -1
  21. package/src/runtime/app/forms.js +5 -1
  22. package/src/runtime/app/paths.js +1 -1
  23. package/src/runtime/client/client.js +1 -1
  24. package/src/runtime/client/start.js +1 -2
  25. package/src/runtime/client/utils.js +1 -2
  26. package/src/runtime/control.js +23 -5
  27. package/src/runtime/server/ambient.d.ts +8 -0
  28. package/src/runtime/server/cookie.js +4 -5
  29. package/src/runtime/server/data/index.js +3 -2
  30. package/src/runtime/server/endpoint.js +5 -5
  31. package/src/runtime/server/fetch.js +11 -9
  32. package/src/runtime/server/index.js +54 -395
  33. package/src/runtime/server/page/csp.js +9 -11
  34. package/src/runtime/server/page/index.js +13 -8
  35. package/src/runtime/server/page/load_data.js +2 -3
  36. package/src/runtime/server/page/render.js +26 -19
  37. package/src/runtime/server/page/respond_with_error.js +20 -13
  38. package/src/runtime/server/page/types.d.ts +0 -1
  39. package/src/runtime/server/respond.js +419 -0
  40. package/src/runtime/server/utils.js +21 -4
  41. package/src/runtime/shared.js +28 -0
  42. package/types/ambient.d.ts +7 -29
  43. package/types/internal.d.ts +23 -37
  44. package/src/runtime/env.js +0 -12
  45. 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 { DEV } from 'esm-env';
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 } = options.manifest._;
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(options.manifest._.entry.fonts);
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 assets;
151
+ let resolved_assets;
144
152
 
145
- if (options.paths.assets) {
153
+ if (assets) {
146
154
  // if an asset path is specified, use it
147
- assets = options.paths.assets;
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
- assets = options.paths.base;
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(options.paths.base.length).split('/').slice(2);
155
- assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
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 : `${assets}/${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(options.public_env)}`,
254
- `paths: ${s(options.paths)}`,
260
+ `env: ${s(env)}`,
261
+ `paths: ${s({ assets, base })}`,
255
262
  `target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`,
256
- `version: ${s(options.version)}`
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 = options.dev ? `, { type: 'module' }` : '';
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.app_template({
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
- * state: SSRState;
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({ event, options, state, status, error, resolve_opts }) {
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 options.manifest._.nodes[0](); // 0 is always the root layout
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 options.manifest._.nodes[1](), // 1 is always the root error
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 (error) {
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 (error instanceof Redirect) {
94
- return redirect_response(error.status, error.location);
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
- error instanceof HttpError ? error.status : 500,
100
- (await handle_error_and_jsonify(event, options, error)).message
106
+ e instanceof HttpError ? e.status : 500,
107
+ (await handle_error_and_jsonify(event, options, e)).message
101
108
  );
102
109
  }
103
110
  }
@@ -24,7 +24,6 @@ export interface CspConfig {
24
24
  }
25
25
 
26
26
  export interface CspOpts {
27
- dev: boolean;
28
27
  prerender: boolean;
29
28
  }
30
29
 
@@ -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.error_template({ status, message }), {
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 {import('types').MaybePromise<App.Error>}
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
- return options.handle_error(error, event);
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