@sveltejs/kit 1.21.0 → 1.22.1

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.21.0",
3
+ "version": "1.22.1",
4
4
  "description": "The fastest way to build Svelte apps",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,7 +35,7 @@
35
35
  "dts-buddy": "^0.0.10",
36
36
  "marked": "^4.2.3",
37
37
  "rollup": "^3.7.0",
38
- "svelte": "^4.0.0",
38
+ "svelte": "^4.0.3",
39
39
  "svelte-preprocess": "^5.0.4",
40
40
  "typescript": "^4.9.4",
41
41
  "vite": "^4.3.6",
package/src/constants.js CHANGED
@@ -5,3 +5,15 @@
5
5
  export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets';
6
6
 
7
7
  export const GENERATED_COMMENT = '// this file is generated — do not edit it\n';
8
+
9
+ export const ENDPOINT_METHODS = new Set([
10
+ 'GET',
11
+ 'POST',
12
+ 'PUT',
13
+ 'PATCH',
14
+ 'DELETE',
15
+ 'OPTIONS',
16
+ 'HEAD'
17
+ ]);
18
+
19
+ export const PAGE_METHODS = new Set(['GET', 'POST', 'HEAD']);
@@ -13,6 +13,7 @@ import { forked } from '../../utils/fork.js';
13
13
  import { should_polyfill } from '../../utils/platform.js';
14
14
  import { installPolyfills } from '../../exports/node/polyfills.js';
15
15
  import { resolvePath } from '../../exports/index.js';
16
+ import { ENDPOINT_METHODS } from '../../constants.js';
16
17
  import { filter_private_env, filter_public_env } from '../../utils/env.js';
17
18
 
18
19
  export default forked(import.meta.url, analyse);
@@ -92,12 +93,11 @@ async function analyse({ manifest_path, env }) {
92
93
  prerender = mod.prerender;
93
94
  }
94
95
 
95
- if (mod.GET) api_methods.push('GET');
96
- if (mod.POST) api_methods.push('POST');
97
- if (mod.PUT) api_methods.push('PUT');
98
- if (mod.PATCH) api_methods.push('PATCH');
99
- if (mod.DELETE) api_methods.push('DELETE');
100
- if (mod.OPTIONS) api_methods.push('OPTIONS');
96
+ Object.values(mod).forEach((/** @type {import('types').HttpMethod} */ method) => {
97
+ if (mod[method] && ENDPOINT_METHODS.has(method)) {
98
+ api_methods.push(method);
99
+ }
100
+ });
101
101
 
102
102
  config = mod.config;
103
103
  entries = mod.entries;
@@ -454,7 +454,7 @@ export interface KitConfig {
454
454
  /**
455
455
  * SvelteKit will preload the JavaScript modules needed for the initial page to avoid import 'waterfalls', resulting in faster application startup. There
456
456
  * are three strategies with different trade-offs:
457
- * - `modulepreload` - uses `<link rel="modulepreload">`. This delivers the best results in Chromium-based browsers, but is currently ignored by Firefox and Safari (though support is coming to Safari soon).
457
+ * - `modulepreload` - uses `<link rel="modulepreload">`. This delivers the best results in Chromium-based browsers, in Firefox 115+, and Safari 17+. It is ignored in older browsers.
458
458
  * - `preload-js` - uses `<link rel="preload">`. Prevents waterfalls in Chromium and Safari, but Chromium will parse each module twice (once as a script, once as a module). Causes modules to be requested twice in Firefox. This is a good setting if you want to maximise performance for users on iOS devices at the cost of a very slight degradation for Chromium users.
459
459
  * - `preload-mjs` - uses `<link rel="preload">` but with the `.mjs` extension which prevents double-parsing in Chromium. Some static webservers will fail to serve .mjs files with a `Content-Type: application/javascript` header, which will cause your application to break. If that doesn't apply to you, this is the option that will deliver the best performance for the largest number of users, until `modulepreload` is more widely supported.
460
460
  * @default "modulepreload"
@@ -90,14 +90,3 @@ export function resolve_symlinks(manifest, file) {
90
90
  export function assets_base(config) {
91
91
  return (config.paths.assets || config.paths.base || '.') + '/';
92
92
  }
93
-
94
- const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'OPTIONS']);
95
-
96
- // If we'd written this in TypeScript, it could be easy...
97
- /**
98
- * @param {string} str
99
- * @returns {str is import('types').HttpMethod}
100
- */
101
- export function is_http_method(str) {
102
- return method_names.has(str);
103
- }
@@ -541,8 +541,14 @@ function kit({ svelte_config }) {
541
541
  // see the kit.output.preloadStrategy option for details on why we have multiple options here
542
542
  const ext = kit.output.preloadStrategy === 'preload-mjs' ? 'mjs' : 'js';
543
543
 
544
+ // We could always use a relative asset base path here, but it's better for performance not to.
545
+ // E.g. Vite generates `new URL('/asset.png', import.meta).href` for a relative path vs just '/asset.png'.
546
+ // That's larger and takes longer to run and also causes an HTML diff between SSR and client
547
+ // causing us to do a more expensive hydration check.
548
+ const client_base = kit.paths.relative || kit.paths.assets ? './' : kit.paths.base || '/';
549
+
544
550
  new_config = {
545
- base: ssr ? assets_base(kit) : './',
551
+ base: ssr ? assets_base(kit) : client_base,
546
552
  build: {
547
553
  copyPublicDir: !ssr,
548
554
  cssCodeSplit: true,
@@ -1,12 +1,21 @@
1
1
  import { DEV } from 'esm-env';
2
2
  import { onMount, tick } from 'svelte';
3
3
  import {
4
- make_trackable,
5
- decode_pathname,
4
+ add_data_suffix,
6
5
  decode_params,
7
- normalize_path,
8
- add_data_suffix
6
+ decode_pathname,
7
+ make_trackable,
8
+ normalize_path
9
9
  } from '../../utils/url.js';
10
+ import {
11
+ initial_fetch,
12
+ lock_fetch,
13
+ native_fetch,
14
+ subsequent_fetch,
15
+ unlock_fetch
16
+ } from './fetcher.js';
17
+ import { parse } from './parse.js';
18
+ import * as storage from './session-storage.js';
10
19
  import {
11
20
  find_anchor,
12
21
  get_base_uri,
@@ -15,25 +24,16 @@ import {
15
24
  is_external_url,
16
25
  scroll_state
17
26
  } from './utils.js';
18
- import * as storage from './session-storage.js';
19
- import {
20
- lock_fetch,
21
- unlock_fetch,
22
- initial_fetch,
23
- subsequent_fetch,
24
- native_fetch
25
- } from './fetcher.js';
26
- import { parse } from './parse.js';
27
27
 
28
28
  import { base } from '__sveltekit/paths';
29
- import { HttpError, Redirect } from '../control.js';
30
- import { stores } from './singletons.js';
31
- import { unwrap_promises } from '../../utils/promises.js';
32
29
  import * as devalue from 'devalue';
33
- import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
34
- import { validate_page_exports } from '../../utils/exports.js';
35
30
  import { compact } from '../../utils/array.js';
31
+ import { validate_page_exports } from '../../utils/exports.js';
32
+ import { unwrap_promises } from '../../utils/promises.js';
33
+ import { HttpError, Redirect } from '../control.js';
36
34
  import { INVALIDATED_PARAM, validate_depends } from '../shared.js';
35
+ import { INDEX_KEY, PRELOAD_PRIORITIES, SCROLL_KEY, SNAPSHOT_KEY } from './constants.js';
36
+ import { stores } from './singletons.js';
37
37
 
38
38
  let errored = false;
39
39
 
@@ -1555,9 +1555,7 @@ export function create_client(app, target) {
1555
1555
 
1556
1556
  update_scroll_positions(current_history_index);
1557
1557
 
1558
- current.url = url;
1559
- stores.page.set({ ...page, url });
1560
- stores.page.notify();
1558
+ update_url(url);
1561
1559
 
1562
1560
  if (!options.replace_state) return;
1563
1561
 
@@ -1670,6 +1668,14 @@ export function create_client(app, target) {
1670
1668
  type: 'popstate',
1671
1669
  delta
1672
1670
  });
1671
+ } else {
1672
+ // since popstate event is also emitted when an anchor referencing the same
1673
+ // document is clicked, we have to check that the router isn't already handling
1674
+ // the navigation. otherwise we would be updating the page store twice.
1675
+ if (!hash_navigating) {
1676
+ const url = new URL(location.href);
1677
+ update_url(url);
1678
+ }
1673
1679
  }
1674
1680
  });
1675
1681
 
@@ -1702,6 +1708,15 @@ export function create_client(app, target) {
1702
1708
  stores.navigating.set(null);
1703
1709
  }
1704
1710
  });
1711
+
1712
+ /**
1713
+ * @param {URL} url
1714
+ */
1715
+ function update_url(url) {
1716
+ current.url = url;
1717
+ stores.page.set({ ...page, url });
1718
+ stores.page.notify();
1719
+ }
1705
1720
  },
1706
1721
 
1707
1722
  _hydrate: async ({
@@ -1,3 +1,4 @@
1
+ import { ENDPOINT_METHODS, PAGE_METHODS } from '../../constants.js';
1
2
  import { negotiate } from '../../utils/http.js';
2
3
  import { Redirect } from '../control.js';
3
4
  import { method_not_allowed } from './utils.js';
@@ -79,8 +80,8 @@ export async function render_endpoint(event, mod, state) {
79
80
  export function is_endpoint_request(event) {
80
81
  const { method, headers } = event.request;
81
82
 
82
- if (method === 'PUT' || method === 'PATCH' || method === 'DELETE' || method === 'OPTIONS') {
83
- // These methods exist exclusively for endpoints
83
+ // These methods exist exclusively for endpoints
84
+ if (ENDPOINT_METHODS.has(method) && !PAGE_METHODS.has(method)) {
84
85
  return true;
85
86
  }
86
87
 
@@ -46,7 +46,7 @@ export function serialize_data(fetched, filter, prerendering = false) {
46
46
 
47
47
  let cache_control = null;
48
48
  let age = null;
49
- let vary = false;
49
+ let varyAny = false;
50
50
 
51
51
  for (const [key, value] of fetched.response.headers) {
52
52
  if (filter(key, value)) {
@@ -54,8 +54,8 @@ export function serialize_data(fetched, filter, prerendering = false) {
54
54
  }
55
55
 
56
56
  if (key === 'cache-control') cache_control = value;
57
- if (key === 'age') age = value;
58
- if (key === 'vary') vary = true;
57
+ else if (key === 'age') age = value;
58
+ else if (key === 'vary' && value.trim() === '*') varyAny = true;
59
59
  }
60
60
 
61
61
  const payload = {
@@ -89,10 +89,9 @@ export function serialize_data(fetched, filter, prerendering = false) {
89
89
  }
90
90
 
91
91
  // Compute the time the response should be cached, taking into account max-age and age.
92
- // Do not cache at all if a vary header is present, as this indicates that the cache is
93
- // likely to get busted. It would also mean we'd have to add more logic to computing the
94
- // selector on the client which results in more code for 99% of people for the 1% who use vary.
95
- if (!prerendering && fetched.method === 'GET' && cache_control && !vary) {
92
+ // Do not cache at all if a `Vary: *` header is present, as this indicates that the
93
+ // cache is likely to get busted.
94
+ if (!prerendering && fetched.method === 'GET' && cache_control && !varyAny) {
96
95
  const match = /s-maxage=(\d+)/g.exec(cache_control) ?? /max-age=(\d+)/g.exec(cache_control);
97
96
  if (match) {
98
97
  const ttl = +match[1] - +(age ?? '0');
@@ -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 { handle_fatal_error, redirect_response } from './utils.js';
8
+ import { handle_fatal_error, method_not_allowed, redirect_response } from './utils.js';
9
9
  import {
10
10
  decode_pathname,
11
11
  decode_params,
@@ -42,6 +42,10 @@ const default_filter = () => false;
42
42
  /** @type {import('types').RequiredResolveOptions['preload']} */
43
43
  const default_preload = ({ type }) => type === 'js' || type === 'css';
44
44
 
45
+ const page_methods = new Set(['GET', 'HEAD', 'POST']);
46
+
47
+ const allowed_page_methods = new Set(['GET', 'HEAD', 'OPTIONS']);
48
+
45
49
  /**
46
50
  * @param {Request} request
47
51
  * @param {import('types').SSROptions} options
@@ -343,7 +347,6 @@ export async function respond(request, options, manifest, state) {
343
347
  }
344
348
 
345
349
  /**
346
- *
347
350
  * @param {import('@sveltejs/kit').RequestEvent} event
348
351
  * @param {import('@sveltejs/kit').ResolveOptions} [opts]
349
352
  */
@@ -379,6 +382,8 @@ export async function respond(request, options, manifest, state) {
379
382
  }
380
383
 
381
384
  if (route) {
385
+ const method = /** @type {import('types').HttpMethod} */ (event.request.method);
386
+
382
387
  /** @type {Response} */
383
388
  let response;
384
389
 
@@ -395,13 +400,50 @@ export async function respond(request, options, manifest, state) {
395
400
  } else if (route.endpoint && (!route.page || is_endpoint_request(event))) {
396
401
  response = await render_endpoint(event, await route.endpoint(), state);
397
402
  } else if (route.page) {
398
- response = await render_page(event, route.page, options, manifest, state, resolve_opts);
403
+ if (page_methods.has(method)) {
404
+ response = await render_page(event, route.page, options, manifest, state, resolve_opts);
405
+ } else {
406
+ const allowed_methods = new Set(allowed_page_methods);
407
+ const node = await manifest._.nodes[route.page.leaf]();
408
+ if (node?.server?.actions) {
409
+ allowed_methods.add('POST');
410
+ }
411
+
412
+ if (method === 'OPTIONS') {
413
+ // This will deny CORS preflight requests implicitly because we don't
414
+ // add the required CORS headers to the response.
415
+ response = new Response(null, {
416
+ status: 204,
417
+ headers: {
418
+ allow: Array.from(allowed_methods.values()).join(', ')
419
+ }
420
+ });
421
+ } else {
422
+ const mod = [...allowed_methods].reduce((acc, curr) => {
423
+ acc[curr] = true;
424
+ return acc;
425
+ }, /** @type {Record<string, any>} */ ({}));
426
+ response = method_not_allowed(mod, method);
427
+ }
428
+ }
399
429
  } else {
400
430
  // a route will always have a page or an endpoint, but TypeScript
401
431
  // doesn't know that
402
432
  throw new Error('This should never happen');
403
433
  }
404
434
 
435
+ // If the route contains a page and an endpoint, we need to add a
436
+ // `Vary: Accept` header to the response because of browser caching
437
+ if (request.method === 'GET' && route.page && route.endpoint) {
438
+ const vary = response.headers
439
+ .get('vary')
440
+ ?.split(',')
441
+ ?.map((v) => v.trim().toLowerCase());
442
+ if (!(vary?.includes('accept') || vary?.includes('*'))) {
443
+ response.headers.append('Vary', 'Accept');
444
+ }
445
+ }
446
+
405
447
  return response;
406
448
  }
407
449
 
@@ -4,6 +4,7 @@ import { coalesce_to_error } from '../../utils/error.js';
4
4
  import { negotiate } from '../../utils/http.js';
5
5
  import { HttpError } from '../control.js';
6
6
  import { fix_stack_trace } from '../shared-server.js';
7
+ import { ENDPOINT_METHODS } from '../../constants.js';
7
8
 
8
9
  /** @param {any} body */
9
10
  export function is_pojo(body) {
@@ -34,9 +35,7 @@ export function method_not_allowed(mod, method) {
34
35
 
35
36
  /** @param {Partial<Record<import('types').HttpMethod, any>>} mod */
36
37
  export function allowed_methods(mod) {
37
- const allowed = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'].filter(
38
- (method) => method in mod
39
- );
38
+ const allowed = Array.from(ENDPOINT_METHODS).filter((method) => method in mod);
40
39
 
41
40
  if ('GET' in mod || 'HEAD' in mod) allowed.push('HEAD');
42
41
 
@@ -78,6 +78,7 @@ const valid_server_exports = new Set([
78
78
  'PUT',
79
79
  'DELETE',
80
80
  'OPTIONS',
81
+ 'HEAD',
81
82
  'prerender',
82
83
  'trailingSlash',
83
84
  'config',
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // generated during release, do not modify
2
2
 
3
3
  /** @type {string} */
4
- export const VERSION = '1.21.0';
4
+ export const VERSION = '1.22.1';
package/types/index.d.ts CHANGED
@@ -408,7 +408,7 @@ declare module '@sveltejs/kit' {
408
408
  /**
409
409
  * SvelteKit will preload the JavaScript modules needed for the initial page to avoid import 'waterfalls', resulting in faster application startup. There
410
410
  * are three strategies with different trade-offs:
411
- * - `modulepreload` - uses `<link rel="modulepreload">`. This delivers the best results in Chromium-based browsers, but is currently ignored by Firefox and Safari (though support is coming to Safari soon).
411
+ * - `modulepreload` - uses `<link rel="modulepreload">`. This delivers the best results in Chromium-based browsers, in Firefox 115+, and Safari 17+. It is ignored in older browsers.
412
412
  * - `preload-js` - uses `<link rel="preload">`. Prevents waterfalls in Chromium and Safari, but Chromium will parse each module twice (once as a script, once as a module). Causes modules to be requested twice in Firefox. This is a good setting if you want to maximise performance for users on iOS devices at the cost of a very slight degradation for Chromium users.
413
413
  * - `preload-mjs` - uses `<link rel="preload">` but with the `.mjs` extension which prevents double-parsing in Chromium. Some static webservers will fail to serve .mjs files with a `Content-Type: application/javascript` header, which will cause your application to break. If that doesn't apply to you, this is the option that will deliver the best performance for the largest number of users, until `modulepreload` is more widely supported.
414
414
  * @default "modulepreload"