@sveltejs/kit 3.0.0-next.7 → 3.0.0-next.9

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 (61) hide show
  1. package/package.json +6 -3
  2. package/src/core/config/index.js +1 -2
  3. package/src/core/config/options.js +5 -2
  4. package/src/core/env.js +1 -0
  5. package/src/core/sync/create_manifest_data/index.js +1 -1
  6. package/src/core/sync/sync.js +0 -2
  7. package/src/core/sync/write_client_manifest.js +0 -7
  8. package/src/core/sync/write_non_ambient.js +2 -2
  9. package/src/core/sync/write_server.js +0 -3
  10. package/src/core/sync/write_tsconfig.js +14 -4
  11. package/src/core/sync/write_tsconfig_test/package.json +7 -0
  12. package/src/core/sync/write_types/index.js +17 -14
  13. package/src/core/utils.js +2 -2
  14. package/src/exports/hooks/sequence.js +3 -2
  15. package/src/exports/index.js +1 -1
  16. package/src/exports/node/index.js +4 -8
  17. package/src/exports/public.d.ts +22 -23
  18. package/src/exports/vite/dev/index.js +4 -7
  19. package/src/exports/vite/index.js +110 -59
  20. package/src/exports/vite/preview/index.js +13 -14
  21. package/src/exports/vite/utils.js +14 -14
  22. package/src/runtime/app/env/internal.js +4 -4
  23. package/src/runtime/app/environment/index.js +3 -3
  24. package/src/runtime/app/forms.js +2 -2
  25. package/src/runtime/app/paths/internal/client.js +4 -2
  26. package/src/runtime/app/paths/internal/server.js +2 -23
  27. package/src/runtime/app/paths/server.js +2 -2
  28. package/src/runtime/app/server/remote/prerender.js +1 -2
  29. package/src/runtime/client/bundle.js +1 -1
  30. package/src/runtime/client/client-entry.js +3 -0
  31. package/src/runtime/client/client.js +217 -172
  32. package/src/runtime/client/entry.js +24 -3
  33. package/src/runtime/client/payload.js +17 -0
  34. package/src/runtime/client/remote-functions/form.svelte.js +6 -6
  35. package/src/runtime/client/state.svelte.js +0 -1
  36. package/src/runtime/client/types.d.ts +2 -6
  37. package/src/runtime/components/root.svelte +66 -0
  38. package/src/runtime/env/dynamic/private.js +7 -0
  39. package/src/runtime/env/dynamic/public.js +7 -0
  40. package/src/runtime/env/static/private.js +6 -0
  41. package/src/runtime/env/static/public.js +6 -0
  42. package/src/runtime/form-utils.js +1 -4
  43. package/src/runtime/server/cookie.js +51 -23
  44. package/src/runtime/server/csrf.js +1 -1
  45. package/src/runtime/server/data/index.js +8 -12
  46. package/src/runtime/server/page/index.js +7 -14
  47. package/src/runtime/server/page/render.js +71 -78
  48. package/src/runtime/server/remote.js +2 -1
  49. package/src/runtime/server/respond.js +1 -1
  50. package/src/runtime/server/utils.js +28 -5
  51. package/src/runtime/types.d.ts +8 -0
  52. package/src/types/global-private.d.ts +10 -17
  53. package/src/types/internal.d.ts +25 -26
  54. package/src/utils/import.js +6 -1
  55. package/src/utils/imports.js +83 -0
  56. package/src/utils/routing.js +6 -6
  57. package/src/version.js +1 -1
  58. package/types/index.d.ts +34 -285
  59. package/types/index.d.ts.map +3 -26
  60. package/src/core/sync/write_root.js +0 -127
  61. package/src/types/synthetic/$lib.md +0 -5
@@ -1,3 +1,24 @@
1
- // we expose this as a separate entry point (rather than treating client.js as the entry point)
2
- // so that everything other than `start`/`load_css` can be treeshaken
3
- export { start, load_css } from './client.js';
1
+ /* in development or if `bundleStrategy` is 'split', this file is used as the entry point */
2
+
3
+ import { set_payload } from './payload.js';
4
+
5
+ /** @type {Promise<typeof import('./client-entry.js')>} */
6
+ let client;
7
+
8
+ /** @param {import('types').SvelteKitPayload} payload */
9
+ export function init(payload) {
10
+ set_payload(payload);
11
+ // Importing the client after setting the payload ensures that modules such as
12
+ // `$app/paths` can read it during initialization.
13
+ client ??= import('./client-entry.js');
14
+ }
15
+
16
+ /** @param {Parameters<import('./client-entry.js').start>} args */
17
+ export async function start(...args) {
18
+ return (await client).start(...args);
19
+ }
20
+
21
+ /** @param {Parameters<import('./client-entry.js').load_css>} args */
22
+ export async function load_css(...args) {
23
+ return (await client).load_css(...args);
24
+ }
@@ -0,0 +1,17 @@
1
+ /** @import {SvelteKitPayload} from 'types'; */
2
+
3
+ /**
4
+ * Code inside the SvelteKit client runtime should only use this, not the global,
5
+ * so that the file hashes stay stable between rebuilds as long as the SvelteKit runtime doesn't change
6
+ */
7
+ export let payload = __SVELTEKIT_PAYLOAD__ ?? /** @type {SvelteKitPayload} */ ({});
8
+
9
+ /** @param {SvelteKitPayload} value */
10
+ export function set_payload(value) {
11
+ payload = value;
12
+ }
13
+
14
+ // this makes Vite inject its dev client code as this module's first dependency
15
+ // so that global constant replacements are done before any other module evaluates.
16
+ // For build, it's inert.
17
+ import.meta.hot;
@@ -8,8 +8,8 @@ import {
8
8
  query_responses,
9
9
  _goto,
10
10
  set_nearest_error_page,
11
- invalidateAll,
12
- handle_error
11
+ handle_error,
12
+ refreshAll
13
13
  } from '../client.js';
14
14
  import { tick } from 'svelte';
15
15
  import { categorize_updates, remote_request } from './shared.svelte.js';
@@ -227,14 +227,14 @@ export function form(id) {
227
227
 
228
228
  // if the developer took control of updates via `.updates(...)` (even with
229
229
  // no arguments), or the server performed explicit refreshes, don't invalidateAll
230
- const should_invalidate = refreshes === null && !response.r;
230
+ const should_refresh = refreshes === null && !response.r;
231
231
 
232
232
  if (response.redirect) {
233
233
  // Use internal version to allow redirects to external URLs
234
234
  void _goto(
235
235
  response.redirect,
236
236
  {
237
- invalidateAll: should_invalidate
237
+ refreshAll: should_refresh
238
238
  },
239
239
  0
240
240
  );
@@ -244,8 +244,8 @@ export function form(id) {
244
244
  const succeeded = raw_issues.length === 0;
245
245
 
246
246
  if (succeeded) {
247
- if (should_invalidate) {
248
- void invalidateAll();
247
+ if (should_refresh) {
248
+ void refreshAll();
249
249
  }
250
250
  } else {
251
251
  if (DEV) {
@@ -11,7 +11,6 @@ export const page = new (class Page {
11
11
  route = $state.raw({ id: null });
12
12
  state = $state.raw({});
13
13
  status = $state.raw(-1);
14
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
15
14
  url = $state.raw(new URL('a:'));
16
15
  })();
17
16
 
@@ -10,6 +10,7 @@ import {
10
10
  Uses
11
11
  } from 'types';
12
12
  import { Page, ParamMatcher } from '@sveltejs/kit';
13
+ import { RenderNode } from '../types.js';
13
14
 
14
15
  export interface SvelteKitApp {
15
16
  /**
@@ -57,8 +58,6 @@ export interface SvelteKitApp {
57
58
  */
58
59
  hash: boolean;
59
60
 
60
- root: typeof SvelteComponent;
61
-
62
61
  /**
63
62
  * Lazily loads the contents of src/error.html, used as a last-resort
64
63
  * error page when the root layout's load function throws during client-side rendering.
@@ -91,13 +90,10 @@ export type NavigationFinished = {
91
90
  type: 'loaded';
92
91
  state: NavigationState;
93
92
  props: {
94
- constructors: Array<typeof SvelteComponent>;
95
- errors?: Array<typeof SvelteComponent | undefined>;
96
- components?: SvelteComponent[];
97
93
  page: Page;
98
94
  form?: Record<string, any> | null;
99
95
  error?: App.Error;
100
- [key: `data_${number}`]: Record<string, any>;
96
+ tree: RenderNode;
101
97
  };
102
98
  };
103
99
 
@@ -0,0 +1,66 @@
1
+ <script lang="ts">
2
+ import { afterNavigate } from '$app/navigation';
3
+ import type { Page } from '@sveltejs/kit';
4
+ import type { RenderNode } from '../types.js';
5
+
6
+ interface Props {
7
+ page: Page;
8
+ tree: RenderNode;
9
+ components: any[];
10
+ resetters: Array<(() => void) | undefined>;
11
+ form?: any;
12
+ error?: App.Error;
13
+ }
14
+
15
+ const { page, components, resetters, tree, form, error }: Props = $props();
16
+
17
+ let mounted = $state(false);
18
+ let navigated = $state(false);
19
+ let title = $state('');
20
+
21
+ afterNavigate(() => {
22
+ if (mounted) {
23
+ navigated = true;
24
+ title = document.title || 'untitled page';
25
+ } else {
26
+ mounted = true;
27
+ }
28
+ });
29
+ </script>
30
+
31
+ {#snippet node(n: RenderNode, depth: number)}
32
+ {const Component = $derived(n.component)}
33
+ {const Error = $derived(n.error)}
34
+ {const data = $derived(n.data)}
35
+
36
+ <svelte:boundary onerror={(_, reset) => (resetters[depth] = reset)}>
37
+ {#if n.child}
38
+ <!-- svelte-ignore binding_property_non_reactive -->
39
+ <Component bind:this={components[depth]} {data} {form} params={page.params}>
40
+ {@render node(n.child, depth + 1)}
41
+ </Component>
42
+ {:else}
43
+ <!-- svelte-ignore binding_property_non_reactive -->
44
+ <Component bind:this={components[depth]} {data} {form} params={page.params} {error} />
45
+ {/if}
46
+
47
+ {#snippet failed(error)}
48
+ <Error {error} />
49
+ {/snippet}
50
+ </svelte:boundary>
51
+ {/snippet}
52
+
53
+ {@render node(tree, 0)}
54
+
55
+ {#if mounted}
56
+ <div
57
+ id="svelte-announcer"
58
+ aria-live="assertive"
59
+ aria-atomic="true"
60
+ style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px"
61
+ >
62
+ {#if navigated}
63
+ {title}
64
+ {/if}
65
+ </div>
66
+ {/if}
@@ -0,0 +1,7 @@
1
+ import { DEV } from 'esm-env';
2
+ import * as env from '../../app/env/private.js';
3
+ export { env };
4
+
5
+ if (DEV) {
6
+ console.warn('`$env/dynamic/private` is deprecated, use `$app/env/private` instead');
7
+ }
@@ -0,0 +1,7 @@
1
+ import { DEV } from 'esm-env';
2
+ import * as env from '../../app/env/public/index.js';
3
+ export { env };
4
+
5
+ if (DEV) {
6
+ console.warn('`$env/dynamic/public` is deprecated, use `$app/env/public` instead');
7
+ }
@@ -0,0 +1,6 @@
1
+ import { DEV } from 'esm-env';
2
+ export * from '../../app/env/private.js';
3
+
4
+ if (DEV) {
5
+ console.warn('`$env/static/private` is deprecated, use `$app/env/private` instead');
6
+ }
@@ -0,0 +1,6 @@
1
+ import { DEV } from 'esm-env';
2
+ export * from '../../app/env/public/index.js';
3
+
4
+ if (DEV) {
5
+ console.warn('`$env/static/public` is deprecated, use `$app/env/public` instead');
6
+ }
@@ -150,10 +150,7 @@ export async function deserialize_binary_form(request) {
150
150
  throw deserialize_error('no body');
151
151
  }
152
152
 
153
- // TODO: remove this workaround once we upgrade to TS 6.0
154
- const reader = /** @type {ReadableStreamDefaultReader<Uint8Array<ArrayBuffer>>} */ (
155
- request.body.getReader()
156
- );
153
+ const reader = request.body.getReader();
157
154
 
158
155
  /** @type {Array<Promise<Uint8Array<ArrayBuffer> | undefined>>} */
159
156
  const chunks = [];
@@ -12,10 +12,11 @@ import { text_encoder } from '../utils.js';
12
12
  const cookie_paths = {};
13
13
 
14
14
  /**
15
- * Cookies that are larger than this size (including the name and other
16
- * attributes) are discarded by browsers
15
+ * Cookies whose name and value combined are larger than this size are
16
+ * discarded by browsers. This is the limit codified for the name/value pair
17
+ * in RFC 6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20#section-5.6-7.5.1
17
18
  */
18
- const MAX_COOKIE_SIZE = 4129;
19
+ const MAX_COOKIE_SIZE = 4096;
19
20
 
20
21
  /**
21
22
  * Generates a unique key for a cookie based on its domain, path, and name in
@@ -42,6 +43,25 @@ export function get_cookies(request, url) {
42
43
  parseCookie(header, { decode: (value) => value })
43
44
  );
44
45
 
46
+ /** @type {ReturnType<typeof parseCookie> | undefined} */
47
+ let default_cookies;
48
+
49
+ /**
50
+ * The header never changes during the request, so the default-decode parse is cached
51
+ * @param {import('cookie').ParseOptions} [opts]
52
+ */
53
+ function parse_header(opts) {
54
+ return opts?.decode ? parseCookie(header, opts) : (default_cookies ??= parseCookie(header));
55
+ }
56
+
57
+ /** @param {import('./page/types.js').Cookie} cookie */
58
+ function matches_url(cookie) {
59
+ return (
60
+ domain_matches(url.hostname, cookie.options.domain) &&
61
+ path_matches(url.pathname, cookie.options.path)
62
+ );
63
+ }
64
+
45
65
  /** @type {string | undefined} */
46
66
  let normalized_url;
47
67
 
@@ -65,22 +85,23 @@ export function get_cookies(request, url) {
65
85
 
66
86
  get(name, opts) {
67
87
  // Look for the most specific matching cookie from new_cookies
68
- const best_match = Array.from(new_cookies.values())
69
- .filter((c) => {
70
- return (
71
- c.name === name &&
72
- domain_matches(url.hostname, c.options.domain) &&
73
- path_matches(url.pathname, c.options.path)
74
- );
75
- })
76
- .sort((a, b) => b.options.path.length - a.options.path.length)[0];
88
+ /** @type {import('./page/types.js').Cookie | undefined} */
89
+ let best_match;
90
+ for (const c of new_cookies.values()) {
91
+ if (
92
+ c.name === name &&
93
+ matches_url(c) &&
94
+ (!best_match || c.options.path.length > best_match.options.path.length)
95
+ ) {
96
+ best_match = c;
97
+ }
98
+ }
77
99
 
78
100
  if (best_match) {
79
101
  return best_match.options.maxAge === 0 ? undefined : best_match.value;
80
102
  }
81
103
 
82
- const req_cookies = parseCookie(header, { decode: opts?.decode });
83
- const cookie = req_cookies[name]; // the decoded string or undefined
104
+ const cookie = parse_header(opts)[name]; // the decoded string or undefined
84
105
 
85
106
  // in development, if the cookie was set during this session with `cookies.set`,
86
107
  // but at a different path, warn the user. (ignore cookies from request headers,
@@ -103,16 +124,14 @@ export function get_cookies(request, url) {
103
124
  },
104
125
 
105
126
  getAll(opts) {
106
- const cookies = parseCookie(header, { decode: opts?.decode });
127
+ // copy, so the cached parse isn't mutated below
128
+ const cookies = { ...parse_header(opts) };
107
129
 
108
130
  // Group cookies by name and find the most specific one for each name
109
131
  const lookup = new Map();
110
132
 
111
133
  for (const c of new_cookies.values()) {
112
- if (
113
- domain_matches(url.hostname, c.options.domain) &&
114
- path_matches(url.pathname, c.options.path)
115
- ) {
134
+ if (matches_url(c)) {
116
135
  const existing = lookup.get(c.name);
117
136
 
118
137
  // If no existing cookie or this one has a more specific (longer) path, use this one
@@ -124,7 +143,13 @@ export function get_cookies(request, url) {
124
143
 
125
144
  // Add the most specific cookies to the result
126
145
  for (const c of lookup.values()) {
127
- cookies[c.name] = c.value;
146
+ // tombstones (deleted cookies) shadow request-header cookies,
147
+ // mirroring the behavior of `get()`
148
+ if (c.options.maxAge === 0) {
149
+ delete cookies[c.name];
150
+ } else {
151
+ cookies[c.name] = c.value;
152
+ }
128
153
  }
129
154
 
130
155
  return /** @type {Array<{ name: string; value: string }>} */ (
@@ -219,9 +244,12 @@ export function get_cookies(request, url) {
219
244
  new_cookies.set(cookie_key, cookie);
220
245
 
221
246
  if (DEV) {
222
- const { encode, ...rest } = cookie.options;
223
- const serialized = stringifySetCookie({ name, value, ...rest }, { encode });
224
- if (text_encoder.encode(serialized).byteLength > MAX_COOKIE_SIZE) {
247
+ const size =
248
+ // only the name/value pair counts towards MAX_COOKIE_SIZE, not the other attributes
249
+ text_encoder.encode(name).byteLength +
250
+ text_encoder.encode((options.encode ?? encodeURIComponent)(value)).byteLength;
251
+
252
+ if (size > MAX_COOKIE_SIZE) {
225
253
  throw new Error(`Cookie "${name}" is too large, and will be discarded by the browser`);
226
254
  }
227
255
 
@@ -36,7 +36,7 @@ export function get_self_origin(paths_origin, url_origin) {
36
36
  */
37
37
  export function is_csrf_forbidden({ request, request_origin, self_origin, trusted_origins }) {
38
38
  return (
39
- is_form_content_type(request) &&
39
+ (!request.headers.get('content-type') || is_form_content_type(request)) &&
40
40
  mutating_form_methods.has(request.method) &&
41
41
  request_origin !== self_origin &&
42
42
  (!request_origin || !trusted_origins.includes(request_origin))
@@ -96,29 +96,25 @@ export async function render_data(
96
96
  return fn();
97
97
  });
98
98
 
99
- let length = promises.length;
100
- const nodes = await Promise.all(
101
- promises.map((p, i) =>
102
- p.catch(async (error) => {
99
+ const data_serializer = server_data_serializer_json(event, event_state, options);
100
+ await Promise.all(
101
+ promises.map(async (p, i) => {
102
+ const node = await p.catch(async (error) => {
103
103
  if (error instanceof Redirect) {
104
104
  throw error;
105
105
  }
106
106
 
107
- // Math.min because array isn't guaranteed to resolve in order
108
- length = Math.min(length, i + 1);
109
-
110
107
  const transformed = await handle_error_and_jsonify(event, event_state, options, error);
111
108
 
112
109
  return /** @type {import('types').ServerErrorNode} */ ({
113
110
  type: 'error',
114
111
  error: transformed
115
112
  });
116
- })
117
- )
118
- );
113
+ });
119
114
 
120
- const data_serializer = server_data_serializer_json(event, event_state, options);
121
- for (let i = 0; i < nodes.length; i++) data_serializer.add_node(i, nodes[i]);
115
+ data_serializer.add_node(i, node);
116
+ })
117
+ );
122
118
  const { data, chunks } = data_serializer.get_data();
123
119
 
124
120
  if (!chunks) {
@@ -1,5 +1,6 @@
1
+ /** @import { Component } from 'svelte' */
1
2
  /** @import { ActionResult, RequestEvent, SSRManifest } from '@sveltejs/kit' */
2
- /** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRComponent, SSRNode, SSROptions, SSRState } from 'types' */
3
+ /** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRNode, SSROptions, SSRState } from 'types' */
3
4
  import { text } from '@sveltejs/kit';
4
5
  import { HttpError, Redirect } from '@sveltejs/kit/internal';
5
6
  import { compact } from '../../../utils/array.js';
@@ -316,13 +317,7 @@ export async function render_page(
316
317
  },
317
318
  status,
318
319
  error,
319
- error_components: await load_error_components(
320
- options,
321
- ssr,
322
- error_branch,
323
- page,
324
- manifest
325
- ),
320
+ error_components: await load_error_components(ssr, error_branch, page, manifest),
326
321
  branch: error_branch,
327
322
  fetched,
328
323
  data_serializer
@@ -374,7 +369,7 @@ export async function render_page(
374
369
  action_result,
375
370
  fetched,
376
371
  data_serializer: !ssr ? server_data_serializer(event, event_state, options) : data_serializer,
377
- error_components: await load_error_components(options, ssr, branch, page, manifest)
372
+ error_components: await load_error_components(ssr, branch, page, manifest)
378
373
  });
379
374
  } catch (e) {
380
375
  // a remote function could have thrown a redirect during render
@@ -398,18 +393,16 @@ export async function render_page(
398
393
  }
399
394
 
400
395
  /**
401
- *
402
- * @param {SSROptions} options
403
396
  * @param {boolean} ssr
404
397
  * @param {Array<import('./types.js').Loaded | null>} branch
405
398
  * @param {PageNodeIndexes} page
406
399
  * @param {SSRManifest} manifest
407
400
  */
408
- async function load_error_components(options, ssr, branch, page, manifest) {
409
- /** @type {Array<SSRComponent | undefined> | undefined} */
401
+ async function load_error_components(ssr, branch, page, manifest) {
402
+ /** @type {Array<Component | undefined> | undefined} */
410
403
  let error_components;
411
404
 
412
- if (options.server_error_boundaries && ssr) {
405
+ if (ssr) {
413
406
  let last_idx = -1;
414
407
  error_components = await Promise.all(
415
408
  // eslint-disable-next-line @typescript-eslint/await-thenable