@sveltejs/kit 1.8.4 → 1.8.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -218,6 +218,21 @@ function kit({ svelte_config }) {
218
218
 
219
219
  const generated = path.posix.join(kit.outDir, 'generated');
220
220
 
221
+ // This ensures that esm-env is inlined into the server output with the
222
+ // export conditions resolved correctly through Vite. This prevents adapters
223
+ // that bundle later on from resolving the export conditions incorrectly
224
+ // and for example include browser-only code in the server output
225
+ // because they for example use esbuild.build with `platform: 'browser'`
226
+ const noExternal = ['esm-env'];
227
+
228
+ // Vitest bypasses Vite when loading external modules, so we bundle
229
+ // when it is detected to keep our virtual modules working.
230
+ // See https://github.com/sveltejs/kit/pull/9172
231
+ // and https://vitest.dev/config/#deps-registernodeloader
232
+ if (process.env.TEST) {
233
+ noExternal.push('@sveltejs/kit');
234
+ }
235
+
221
236
  // dev and preview config can be shared
222
237
  /** @type {import('vite').UserConfig} */
223
238
  const new_config = {
@@ -252,6 +267,9 @@ function kit({ svelte_config }) {
252
267
  '$app',
253
268
  '$env'
254
269
  ]
270
+ },
271
+ ssr: {
272
+ noExternal
255
273
  }
256
274
  };
257
275
 
@@ -267,19 +285,6 @@ function kit({ svelte_config }) {
267
285
  __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
268
286
  };
269
287
 
270
- new_config.ssr = {
271
- noExternal: [
272
- // TODO document why this is necessary
273
- '@sveltejs/kit',
274
- // This ensures that esm-env is inlined into the server output with the
275
- // export conditions resolved correctly through Vite. This prevents adapters
276
- // that bundle later on to resolve the export conditions incorrectly
277
- // and for example include browser-only code in the server output
278
- // because they for example use esbuild.build with `platform: 'browser'`
279
- 'esm-env'
280
- ]
281
- };
282
-
283
288
  if (!secondary_build_started) {
284
289
  manifest_data = (await sync.all(svelte_config, config_env.mode)).manifest_data;
285
290
  }
@@ -290,13 +295,12 @@ function kit({ svelte_config }) {
290
295
  __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
291
296
  };
292
297
 
293
- new_config.ssr = {
294
- // Without this, Vite will treat `@sveltejs/kit` as noExternal if it's
295
- // a linked dependency, and that causes modules to be imported twice
296
- // under different IDs, which breaks a bunch of stuff
297
- // https://github.com/vitejs/vite/pull/9296
298
- external: ['@sveltejs/kit', 'cookie', 'set-cookie-parser']
299
- };
298
+ // These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
299
+ // Without this, the tests will still pass but `pnpm dev` will fail in projects that link `@sveltejs/kit`.
300
+ /** @type {NonNullable<import('vite').UserConfig['ssr']>} */ (new_config.ssr).external = [
301
+ 'cookie',
302
+ 'set-cookie-parser'
303
+ ];
300
304
  }
301
305
 
302
306
  warn_overridden_config(config, new_config);
@@ -38,8 +38,6 @@ export async function render_data(
38
38
  });
39
39
  }
40
40
 
41
- state.initiator = route;
42
-
43
41
  try {
44
42
  const node_ids = [...route.page.layouts, route.page.leaf];
45
43
  const invalidated = invalidated_data_nodes ?? node_ids.map(() => true);
@@ -4,12 +4,11 @@ import { method_not_allowed } from './utils.js';
4
4
 
5
5
  /**
6
6
  * @param {import('types').RequestEvent} event
7
- * @param {import('types').SSRRoute} route
8
7
  * @param {import('types').SSREndpoint} mod
9
8
  * @param {import('types').SSRState} state
10
9
  * @returns {Promise<Response>}
11
10
  */
12
- export async function render_endpoint(event, route, mod, state) {
11
+ export async function render_endpoint(event, mod, state) {
13
12
  const method = /** @type {import('types').HttpMethod} */ (event.request.method);
14
13
 
15
14
  let handler = mod[method];
@@ -29,7 +28,7 @@ export async function render_endpoint(event, route, mod, state) {
29
28
  }
30
29
 
31
30
  if (state.prerendering && !prerender) {
32
- if (state.initiator) {
31
+ if (state.depth > 0) {
33
32
  // if request came from a prerendered page, bail
34
33
  throw new Error(`${event.route.id} is not prerenderable`);
35
34
  } else {
@@ -39,8 +38,6 @@ export async function render_endpoint(event, route, mod, state) {
39
38
  }
40
39
  }
41
40
 
42
- state.initiator = route;
43
-
44
41
  try {
45
42
  const response = await handler(
46
43
  /** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
@@ -131,7 +131,10 @@ export function create_fetch({ event, options, manifest, state, get_cookie_heade
131
131
  );
132
132
  }
133
133
 
134
- response = await respond(request, options, manifest, state);
134
+ response = await respond(request, options, manifest, {
135
+ ...state,
136
+ depth: state.depth + 1
137
+ });
135
138
 
136
139
  const set_cookie = response.headers.get('set-cookie');
137
140
  if (set_cookie) {
@@ -58,6 +58,10 @@ export class Server {
58
58
  );
59
59
  }
60
60
 
61
- return respond(request, this.#options, this.#manifest, options);
61
+ return respond(request, this.#options, this.#manifest, {
62
+ ...options,
63
+ error: false,
64
+ depth: 0
65
+ });
62
66
  }
63
67
  }
@@ -16,9 +16,13 @@ import { respond_with_error } from './respond_with_error.js';
16
16
  import { get_option } from '../../../utils/options.js';
17
17
  import { get_data_json } from '../data/index.js';
18
18
 
19
+ /**
20
+ * The maximum request depth permitted before assuming we're stuck in an infinite loop
21
+ */
22
+ const MAX_DEPTH = 10;
23
+
19
24
  /**
20
25
  * @param {import('types').RequestEvent} event
21
- * @param {import('types').SSRRoute} route
22
26
  * @param {import('types').PageNodeIndexes} page
23
27
  * @param {import('types').SSROptions} options
24
28
  * @param {import('types').SSRManifest} manifest
@@ -26,16 +30,14 @@ import { get_data_json } from '../data/index.js';
26
30
  * @param {import('types').RequiredResolveOptions} resolve_opts
27
31
  * @returns {Promise<Response>}
28
32
  */
29
- export async function render_page(event, route, page, options, manifest, state, resolve_opts) {
30
- if (state.initiator === route) {
33
+ export async function render_page(event, page, options, manifest, state, resolve_opts) {
34
+ if (state.depth > MAX_DEPTH) {
31
35
  // infinite request cycle detected
32
36
  return text(`Not found: ${event.url.pathname}`, {
33
- status: 404
37
+ status: 404 // TODO in some cases this should be 500. not sure how to differentiate
34
38
  });
35
39
  }
36
40
 
37
- state.initiator = route;
38
-
39
41
  if (is_action_json_request(event)) {
40
42
  const node = await manifest._.nodes[page.leaf]();
41
43
  return handle_action_json_request(event, options, node?.server);
@@ -322,7 +324,7 @@ export async function render_page(event, route, page, options, manifest, state,
322
324
  fetched
323
325
  });
324
326
  } catch (e) {
325
- // if we end up here, it means the data loaded successfull
327
+ // if we end up here, it means the data loaded successfully
326
328
  // but the page failed to render, or that a prerendering error occurred
327
329
  return await respond_with_error({
328
330
  event,
@@ -1,11 +1,6 @@
1
1
  import { render_response } from './render.js';
2
2
  import { load_data, load_server_data } from './load_data.js';
3
- import {
4
- handle_error_and_jsonify,
5
- static_error_page,
6
- redirect_response,
7
- GENERIC_ERROR
8
- } from '../utils.js';
3
+ import { handle_error_and_jsonify, static_error_page, redirect_response } from '../utils.js';
9
4
  import { get_option } from '../../../utils/options.js';
10
5
  import { HttpError, Redirect } from '../../control.js';
11
6
 
@@ -43,7 +38,7 @@ export async function respond_with_error({
43
38
  const csr = get_option([default_layout], 'csr') ?? true;
44
39
 
45
40
  if (ssr) {
46
- state.initiator = GENERIC_ERROR;
41
+ state.error = true;
47
42
 
48
43
  const server_data_promise = load_server_data({
49
44
  event,
@@ -5,7 +5,7 @@ import { render_page } from './page/index.js';
5
5
  import { render_response } from './page/render.js';
6
6
  import { respond_with_error } from './page/respond_with_error.js';
7
7
  import { is_form_content_type } from '../../utils/http.js';
8
- import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
8
+ import { handle_fatal_error, redirect_response } from './utils.js';
9
9
  import {
10
10
  decode_pathname,
11
11
  decode_params,
@@ -38,7 +38,13 @@ const default_filter = () => false;
38
38
  /** @type {import('types').RequiredResolveOptions['preload']} */
39
39
  const default_preload = ({ type }) => type === 'js' || type === 'css';
40
40
 
41
- /** @type {import('types').Respond} */
41
+ /**
42
+ * @param {Request} request
43
+ * @param {import('types').SSROptions} options
44
+ * @param {import('types').SSRManifest} manifest
45
+ * @param {import('types').SSRState} state
46
+ * @returns {Promise<Response>}
47
+ */
42
48
  export async function respond(request, options, manifest, state) {
43
49
  /** URL but stripped from the potential `/__data.json` suffix and its search param */
44
50
  let url = new URL(request.url);
@@ -358,17 +364,9 @@ export async function respond(request, options, manifest, state) {
358
364
  trailing_slash ?? 'never'
359
365
  );
360
366
  } else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
361
- response = await render_endpoint(event, route, await route.endpoint(), state);
367
+ response = await render_endpoint(event, await route.endpoint(), state);
362
368
  } else if (route.page) {
363
- response = await render_page(
364
- event,
365
- route,
366
- route.page,
367
- options,
368
- manifest,
369
- state,
370
- resolve_opts
371
- );
369
+ response = await render_page(event, route.page, options, manifest, state, resolve_opts);
372
370
  } else {
373
371
  // a route will always have a page or an endpoint, but TypeScript
374
372
  // doesn't know that
@@ -378,15 +376,15 @@ export async function respond(request, options, manifest, state) {
378
376
  return response;
379
377
  }
380
378
 
381
- if (state.initiator === GENERIC_ERROR) {
379
+ if (state.error) {
382
380
  return text('Internal Server Error', {
383
381
  status: 500
384
382
  });
385
383
  }
386
384
 
387
385
  // if this request came direct from the user, rather than
388
- // via a `fetch` in a `load`, render a 404 page
389
- if (!state.initiator) {
386
+ // via our own `fetch`, render a 404 page
387
+ if (state.depth === 0) {
390
388
  return await respond_with_error({
391
389
  event,
392
390
  options,
@@ -16,11 +16,6 @@ export function is_pojo(body) {
16
16
  return true;
17
17
  }
18
18
 
19
- /** @type {import('types').SSRErrorPage} */
20
- export const GENERIC_ERROR = {
21
- id: '__error'
22
- };
23
-
24
19
  /**
25
20
  * @param {Partial<Record<import('types').HttpMethod, any>>} mod
26
21
  * @param {import('types').HttpMethod} method
package/types/index.d.ts CHANGED
@@ -539,7 +539,7 @@ export interface KitConfig {
539
539
  * Client-side navigation can be buggy if you deploy a new version of your app while people are using it. If the code for the new page is already loaded, it may have stale content; if it isn't, the app's route manifest may point to a JavaScript file that no longer exists.
540
540
  * SvelteKit helps you solve this problem through version management.
541
541
  * If SvelteKit encounters an error while loading the page and detects that a new version has been deployed (using the `name` specified here, which defaults to a timestamp of the build) it will fall back to traditional full-page navigation.
542
- * Not all navigations will result in an error though, for example if the JavaScript for the next page is already loaded. If you still want to force a full-page navigation in these cases, use techniques such as setting the `pollInverval` and then using `beforeNavigate`:
542
+ * Not all navigations will result in an error though, for example if the JavaScript for the next page is already loaded. If you still want to force a full-page navigation in these cases, use techniques such as setting the `pollInterval` and then using `beforeNavigate`:
543
543
  * ```html
544
544
  * /// +layout.svelte
545
545
  * <script>
@@ -558,7 +558,7 @@ export interface KitConfig {
558
558
  */
559
559
  version?: {
560
560
  /**
561
- * The current app version string. If specified, this must be deterministic (e.g. a commit ref rather than `Math.random()` or Date.now().toString()`), otherwise defaults to a timestamp of the build
561
+ * The current app version string. If specified, this must be deterministic (e.g. a commit ref rather than `Math.random()` or `Date.now().toString()`), otherwise defaults to a timestamp of the build
562
562
  */
563
563
  name?: string;
564
564
  /**
@@ -157,15 +157,6 @@ export type RecursiveRequired<T> = {
157
157
 
158
158
  export type RequiredResolveOptions = Required<ResolveOptions>;
159
159
 
160
- export interface Respond {
161
- (
162
- request: Request,
163
- options: SSROptions,
164
- manifest: SSRManifest,
165
- state: SSRState
166
- ): Promise<Response>;
167
- }
168
-
169
160
  export interface RouteParam {
170
161
  name: string;
171
162
  matcher: string;
@@ -353,10 +344,6 @@ export interface SSROptions {
353
344
  version_hash: string;
354
345
  }
355
346
 
356
- export interface SSRErrorPage {
357
- id: '__error';
358
- }
359
-
360
347
  export interface PageNodeIndexes {
361
348
  errors: Array<number | undefined>;
362
349
  layouts: Array<number | undefined>;
@@ -381,7 +368,14 @@ export interface SSRRoute {
381
368
  export interface SSRState {
382
369
  fallback?: string;
383
370
  getClientAddress(): string;
384
- initiator?: SSRRoute | SSRErrorPage;
371
+ /**
372
+ * True if we're currently attempting to render an error page
373
+ */
374
+ error: boolean;
375
+ /**
376
+ * Allows us to prevent `event.fetch` from making infinitely looping internal requests
377
+ */
378
+ depth: number;
385
379
  platform?: any;
386
380
  prerendering?: PrerenderOptions;
387
381
  /**