@sveltejs/kit 1.8.7 → 1.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "1.8.7",
3
+ "version": "1.9.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -43,14 +43,15 @@ export function create_builder({
43
43
  * we expose a stable type that adapters can use to group/filter routes
44
44
  */
45
45
  const routes = route_data.map((route) => {
46
- const methods =
47
- /** @type {import('types').HttpMethod[]} */
48
- (server_metadata.routes.get(route.id)?.methods);
49
- const config = server_metadata.routes.get(route.id)?.config;
46
+ const { config, methods, page, api } = /** @type {import('types').ServerMetadataRoute} */ (
47
+ server_metadata.routes.get(route.id)
48
+ );
50
49
 
51
50
  /** @type {import('types').RouteDefinition} */
52
51
  const facade = {
53
52
  id: route.id,
53
+ api,
54
+ page,
54
55
  segments: get_route_segments(route.id).map((segment) => ({
55
56
  dynamic: segment.includes('['),
56
57
  rest: segment.includes('[...'),
@@ -171,6 +171,13 @@ const options = object(
171
171
  }
172
172
  }
173
173
 
174
+ return input;
175
+ }),
176
+ relative: validate(undefined, (input, keypath) => {
177
+ if (typeof input !== 'boolean') {
178
+ throw new Error(`${keypath} option must be a boolean or undefined`);
179
+ }
180
+
174
181
  return input;
175
182
  })
176
183
  }),
@@ -62,8 +62,11 @@ async function analyse({ manifest_path, env }) {
62
62
 
63
63
  // analyse routes
64
64
  for (const route of manifest._.routes) {
65
- /** @type {Set<import('types').HttpMethod>} */
66
- const methods = new Set();
65
+ /** @type {Array<'GET' | 'POST'>} */
66
+ const page_methods = [];
67
+
68
+ /** @type {import('types').HttpMethod[]} */
69
+ const api_methods = [];
67
70
 
68
71
  /** @type {import('types').PrerenderOption | undefined} */
69
72
  let prerender = undefined;
@@ -84,12 +87,12 @@ async function analyse({ manifest_path, env }) {
84
87
  prerender = mod.prerender;
85
88
  }
86
89
 
87
- if (mod.GET) methods.add('GET');
88
- if (mod.POST) methods.add('POST');
89
- if (mod.PUT) methods.add('PUT');
90
- if (mod.PATCH) methods.add('PATCH');
91
- if (mod.DELETE) methods.add('DELETE');
92
- if (mod.OPTIONS) methods.add('OPTIONS');
90
+ if (mod.GET) api_methods.push('GET');
91
+ if (mod.POST) api_methods.push('POST');
92
+ if (mod.PUT) api_methods.push('PUT');
93
+ if (mod.PATCH) api_methods.push('PATCH');
94
+ if (mod.DELETE) api_methods.push('DELETE');
95
+ if (mod.OPTIONS) api_methods.push('OPTIONS');
93
96
 
94
97
  config = mod.config;
95
98
  }
@@ -112,8 +115,8 @@ async function analyse({ manifest_path, env }) {
112
115
  }
113
116
 
114
117
  if (page) {
115
- methods.add('GET');
116
- if (page.server?.actions) methods.add('POST');
118
+ page_methods.push('GET');
119
+ if (page.server?.actions) page_methods.push('POST');
117
120
 
118
121
  validate_page_server_exports(page.server, page.server_id);
119
122
  validate_common_exports(page.universal, page.universal_id);
@@ -133,9 +136,15 @@ async function analyse({ manifest_path, env }) {
133
136
  }
134
137
 
135
138
  metadata.routes.set(route.id, {
136
- prerender,
137
139
  config,
138
- methods: Array.from(methods)
140
+ methods: Array.from(new Set([...page_methods, ...api_methods])),
141
+ page: {
142
+ methods: page_methods
143
+ },
144
+ api: {
145
+ methods: api_methods
146
+ },
147
+ prerender
139
148
  });
140
149
  }
141
150
 
@@ -27,7 +27,8 @@ const server_template = ({
27
27
  }) => `
28
28
  import root from '../root.svelte';
29
29
  import { set_building } from '__sveltekit/environment';
30
- import { set_assets, set_private_env, set_public_env } from '${runtime_directory}/shared-server.js';
30
+ import { set_assets } from '__sveltekit/paths';
31
+ import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js';
31
32
 
32
33
  export const options = {
33
34
  app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
@@ -451,15 +451,14 @@ export async function dev(vite, vite_config, svelte_config) {
451
451
  await vite.ssrLoadModule(`${runtime_base}/server/index.js`)
452
452
  );
453
453
 
454
- const { set_assets, set_fix_stack_trace } =
455
- /** @type {import('types').ServerInternalModule} */ (
456
- await vite.ssrLoadModule(`${runtime_base}/shared-server.js`)
457
- );
454
+ const { set_fix_stack_trace } = await vite.ssrLoadModule(
455
+ `${runtime_base}/shared-server.js`
456
+ );
457
+ set_fix_stack_trace(fix_stack_trace);
458
458
 
459
+ const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths');
459
460
  set_assets(assets);
460
461
 
461
- set_fix_stack_trace(fix_stack_trace);
462
-
463
462
  const server = new Server(manifest);
464
463
 
465
464
  await server.init({ env });
@@ -218,21 +218,6 @@ 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
-
236
221
  // dev and preview config can be shared
237
222
  /** @type {import('vite').UserConfig} */
238
223
  const new_config = {
@@ -269,7 +254,23 @@ function kit({ svelte_config }) {
269
254
  ]
270
255
  },
271
256
  ssr: {
272
- noExternal
257
+ noExternal: [
258
+ // This ensures that esm-env is inlined into the server output with the
259
+ // export conditions resolved correctly through Vite. This prevents adapters
260
+ // that bundle later on from resolving the export conditions incorrectly
261
+ // and for example include browser-only code in the server output
262
+ // because they for example use esbuild.build with `platform: 'browser'`
263
+ 'esm-env',
264
+ // We need this for two reasons:
265
+ // 1. Without this, `@sveltejs/kit` imports are kept as-is in the server output,
266
+ // and that causes modules and therefore classes like `Redirect` to be imported twice
267
+ // under different IDs, which breaks a bunch of stuff because of failing instanceof checks.
268
+ // 2. Vitest bypasses Vite when loading external modules, so we bundle
269
+ // when it is detected to keep our virtual modules working.
270
+ // See https://github.com/sveltejs/kit/pull/9172
271
+ // and https://vitest.dev/config/#deps-registernodeloader
272
+ '@sveltejs/kit'
273
+ ]
273
274
  }
274
275
  };
275
276
 
@@ -378,17 +379,34 @@ function kit({ svelte_config }) {
378
379
  case '\0__sveltekit/paths':
379
380
  const { assets, base } = svelte_config.kit.paths;
380
381
 
382
+ // use the values defined in `global`, but fall back to hard-coded values
383
+ // for the sake of things like Vitest which may import this module
384
+ // outside the context of a page
381
385
  if (browser) {
382
- return `export const base = ${s(base)};
383
- export const assets = ${global}.assets;`;
386
+ return `export const base = ${global}?.base ?? ${s(base)};
387
+ export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};`;
384
388
  }
385
389
 
386
- return `export const base = ${s(base)};
390
+ return `export let base = ${s(base)};
387
391
  export let assets = ${assets ? s(assets) : 'base'};
388
392
 
393
+ export const relative = ${svelte_config.kit.paths.relative};
394
+
395
+ const initial = { base, assets };
396
+
397
+ export function override(paths) {
398
+ base = paths.base;
399
+ assets = paths.assets;
400
+ }
401
+
402
+ export function reset() {
403
+ base = initial.base;
404
+ assets = initial.assets;
405
+ }
406
+
389
407
  /** @param {string} path */
390
408
  export function set_assets(path) {
391
- assets = path;
409
+ assets = initial.assets = path;
392
410
  }`;
393
411
 
394
412
  case '\0__sveltekit/environment':
package/src/internal.d.ts CHANGED
@@ -7,7 +7,10 @@ declare module '__sveltekit/environment' {
7
7
 
8
8
  /** Internal version of $app/paths */
9
9
  declare module '__sveltekit/paths' {
10
- export const base: `/${string}`;
11
- export let assets: `https://${string}` | `http://${string}`;
10
+ export let base: '' | `/${string}`;
11
+ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets';
12
+ export let relative: boolean | undefined; // TODO in 2.0, make this a `boolean` that defaults to `true`
13
+ export function reset(): void;
14
+ export function override(paths: { base: string; assets: string }): void;
12
15
  export function set_assets(path: string): void;
13
16
  }
@@ -66,7 +66,8 @@ function get_store(name) {
66
66
  return getStores()[name];
67
67
  } catch (e) {
68
68
  throw new Error(
69
- `Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.`
69
+ `Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.` +
70
+ 'For more information, see https://kit.svelte.dev/docs/state-management#avoid-shared-state-on-the-server'
70
71
  );
71
72
  }
72
73
  }
@@ -1,7 +1,7 @@
1
1
  import * as devalue from 'devalue';
2
2
  import { readable, writable } from 'svelte/store';
3
3
  import { DEV } from 'esm-env';
4
- import { assets, base } from '__sveltekit/paths';
4
+ import * as paths from '__sveltekit/paths';
5
5
  import { hash } from '../../hash.js';
6
6
  import { serialize_data } from './serialize_data.js';
7
7
  import { s } from '../../../utils/misc.js';
@@ -11,6 +11,7 @@ import { clarify_devalue_error, stringify_uses, handle_error_and_jsonify } from
11
11
  import { public_env } from '../../shared-server.js';
12
12
  import { text } from '../../../exports/index.js';
13
13
  import { create_async_iterator } from '../../../utils/streaming.js';
14
+ import { SVELTE_KIT_ASSETS } from '../../../constants.js';
14
15
 
15
16
  // TODO rename this function/module
16
17
 
@@ -80,6 +81,43 @@ export async function render_response({
80
81
  ? action_result.data ?? null
81
82
  : null;
82
83
 
84
+ /** @type {string} */
85
+ let base = paths.base;
86
+
87
+ /** @type {string} */
88
+ let assets = paths.assets;
89
+
90
+ /**
91
+ * An expression that will evaluate in the client to determine the resolved base path.
92
+ * We use a relative path when possible to support IPFS, the internet archive, etc.
93
+ */
94
+ let base_expression = s(paths.base);
95
+
96
+ // if appropriate, use relative paths for greater portability
97
+ if (paths.relative !== false && !state.prerendering?.fallback) {
98
+ const segments = event.url.pathname.slice(paths.base.length).split('/');
99
+
100
+ if (segments.length === 1 && paths.base !== '') {
101
+ // if we're on `/my-base-path`, relative links need to start `./my-base-path` rather than `.`
102
+ base = `./${paths.base.split('/').at(-1)}`;
103
+
104
+ base_expression = `new URL(${s(base)}, location).pathname`;
105
+ } else {
106
+ base =
107
+ segments
108
+ .slice(2)
109
+ .map(() => '..')
110
+ .join('/') || '.';
111
+
112
+ // resolve e.g. '../..' against current location, then remove trailing slash
113
+ base_expression = `new URL(${s(base)}, location).pathname.slice(0, -1)`;
114
+ }
115
+
116
+ if (!paths.assets || (paths.assets[0] === '/' && paths.assets !== SVELTE_KIT_ASSETS)) {
117
+ assets = base;
118
+ }
119
+ }
120
+
83
121
  if (page_config.ssr) {
84
122
  if (__SVELTEKIT_DEV__ && !branch.at(-1)?.node.component) {
85
123
  // Can only be the leaf, layouts have a fallback component generated
@@ -116,6 +154,10 @@ export async function render_response({
116
154
  form: form_value
117
155
  };
118
156
 
157
+ // use relative paths during rendering, so that the resulting HTML is as
158
+ // portable as possible, but reset afterwards
159
+ if (paths.relative) paths.override({ base, assets });
160
+
119
161
  if (__SVELTEKIT_DEV__) {
120
162
  const fetch = globalThis.fetch;
121
163
  let warned = false;
@@ -138,9 +180,14 @@ export async function render_response({
138
180
  rendered = options.root.render(props);
139
181
  } finally {
140
182
  globalThis.fetch = fetch;
183
+ paths.reset();
141
184
  }
142
185
  } else {
143
- rendered = options.root.render(props);
186
+ try {
187
+ rendered = options.root.render(props);
188
+ } finally {
189
+ paths.reset();
190
+ }
144
191
  }
145
192
 
146
193
  for (const { node } of branch) {
@@ -156,35 +203,6 @@ export async function render_response({
156
203
  rendered = { head: '', html: '', css: { code: '', map: null } };
157
204
  }
158
205
 
159
- /**
160
- * The prefix to use for static assets. Replaces `%sveltekit.assets%` in the template
161
- * @type {string}
162
- */
163
- let resolved_assets;
164
-
165
- /**
166
- * An expression that will evaluate in the client to determine the resolved asset path
167
- */
168
- let asset_expression;
169
-
170
- if (assets) {
171
- // if an asset path is specified, use it
172
- resolved_assets = assets;
173
- asset_expression = s(assets);
174
- } else if (state.prerendering?.fallback) {
175
- // if we're creating a fallback page, asset paths need to be root-relative
176
- resolved_assets = base;
177
- asset_expression = s(base);
178
- } else {
179
- // otherwise we want asset paths to be relative to the page, so that they
180
- // will work in odd contexts like IPFS, the internet archive, and so on
181
- const segments = event.url.pathname.slice(base.length).split('/').slice(2);
182
- resolved_assets = segments.length > 0 ? segments.map(() => '..').join('/') : '.';
183
- asset_expression = `new URL(${s(
184
- resolved_assets
185
- )}, location.href).pathname.replace(/^\\\/$/, '')`;
186
- }
187
-
188
206
  let head = '';
189
207
  let body = rendered.html;
190
208
 
@@ -198,9 +216,9 @@ export async function render_response({
198
216
  // Vite makes the start script available through the base path and without it.
199
217
  // We load it via the base path in order to support remote IDE environments which proxy
200
218
  // all URLs under the base path during development.
201
- return base + path;
219
+ return paths.base + path;
202
220
  }
203
- return `${resolved_assets}/${path}`;
221
+ return `${assets}/${path}`;
204
222
  };
205
223
 
206
224
  if (inline_styles.size > 0) {
@@ -286,9 +304,10 @@ export async function render_response({
286
304
 
287
305
  const properties = [
288
306
  `env: ${s(public_env)}`,
289
- `assets: ${asset_expression}`,
307
+ paths.assets && `assets: ${s(paths.assets)}`,
308
+ `base: ${base_expression}`,
290
309
  `element: document.currentScript.parentElement`
291
- ];
310
+ ].filter(Boolean);
292
311
 
293
312
  if (chunks) {
294
313
  blocks.push(`const deferred = new Map();`);
@@ -419,7 +438,7 @@ export async function render_response({
419
438
  const html = options.templates.app({
420
439
  head,
421
440
  body,
422
- assets: resolved_assets,
441
+ assets,
423
442
  nonce: /** @type {string} */ (csp.nonce),
424
443
  env: public_env
425
444
  });
@@ -1,5 +1,3 @@
1
- export { set_assets } from '__sveltekit/paths';
2
-
3
1
  /** @type {Record<string, string>} */
4
2
  export let private_env = {};
5
3
 
@@ -195,7 +195,7 @@ declare module '$app/navigation' {
195
195
  */
196
196
  state?: any;
197
197
  /**
198
- * If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#invalidation for more info on invalidation.
198
+ * If `true`, all `load` functions of the page will be rerun. See https://kit.svelte.dev/docs/load#rerunning-load-functions for more info on invalidation.
199
199
  */
200
200
  invalidateAll?: boolean;
201
201
  }
package/types/index.d.ts CHANGED
@@ -441,12 +441,20 @@ export interface KitConfig {
441
441
  * An absolute path that your app's files are served from. This is useful if your files are served from a storage bucket of some kind.
442
442
  * @default ""
443
443
  */
444
- assets?: string;
444
+ assets?: '' | `http://${string}` | `https://${string}`;
445
445
  /**
446
446
  * A root-relative path that must start, but not end with `/` (e.g. `/base-path`), unless it is the empty string. This specifies where your app is served from and allows the app to live on a non-root path. Note that you need to prepend all your root-relative links with the base value or they will point to the root of your domain, not your `base` (this is how the browser works). You can use [`base` from `$app/paths`](/docs/modules#$app-paths-base) for that: `<a href="{base}/your-page">Link</a>`. If you find yourself writing this often, it may make sense to extract this into a reusable component.
447
447
  * @default ""
448
448
  */
449
- base?: string;
449
+ base?: '' | `/${string}`;
450
+ /**
451
+ * Whether to use relative asset paths. By default, if `paths.assets` is not external, SvelteKit will replace `%sveltekit.assets%` with a relative path and use relative paths to reference build artifacts, but `base` and `assets` imported from `$app/paths` will be as specified in your config.
452
+ *
453
+ * If `true`, `base` and `assets` imported from `$app/paths` will be replaced with relative asset paths during server-side rendering, resulting in portable HTML.
454
+ * If `false`, `%sveltekit.assets%` and references to build artifacts will always be root-relative paths, unless `paths.assets` is an external URL
455
+ * @default undefined
456
+ */
457
+ relative?: boolean | undefined;
450
458
  };
451
459
  /**
452
460
  * See [Prerendering](https://kit.svelte.dev/docs/page-options#prerender).
@@ -906,7 +914,7 @@ export interface RequestEvent<
906
914
  */
907
915
  locals: App.Locals;
908
916
  /**
909
- * The parameters of the current page or endpoint - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object
917
+ * The parameters of the current route - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object
910
918
  */
911
919
  params: Params;
912
920
  /**
@@ -950,7 +958,7 @@ export interface RequestEvent<
950
958
  */
951
959
  setHeaders(headers: Record<string, string>): void;
952
960
  /**
953
- * The URL of the current page or endpoint.
961
+ * The requested URL.
954
962
  */
955
963
  url: URL;
956
964
  /**
@@ -997,6 +1005,12 @@ export interface ResolveOptions {
997
1005
 
998
1006
  export interface RouteDefinition<Config = any> {
999
1007
  id: string;
1008
+ api: {
1009
+ methods: HttpMethod[];
1010
+ };
1011
+ page: {
1012
+ methods: Extract<HttpMethod, 'GET' | 'POST'>[];
1013
+ };
1000
1014
  pattern: RegExp;
1001
1015
  prerender: PrerenderOption;
1002
1016
  segments: RouteSegment[];
@@ -254,16 +254,21 @@ export interface ServerErrorNode {
254
254
  status?: number;
255
255
  }
256
256
 
257
+ export interface ServerMetadataRoute {
258
+ config: any;
259
+ api: {
260
+ methods: HttpMethod[];
261
+ };
262
+ page: {
263
+ methods: Array<'GET' | 'POST'>;
264
+ };
265
+ methods: HttpMethod[];
266
+ prerender: PrerenderOption | undefined;
267
+ }
268
+
257
269
  export interface ServerMetadata {
258
270
  nodes: Array<{ has_server_load: boolean }>;
259
- routes: Map<
260
- string,
261
- {
262
- prerender: PrerenderOption | undefined;
263
- methods: HttpMethod[];
264
- config: any;
265
- }
266
- >;
271
+ routes: Map<string, ServerMetadataRoute>;
267
272
  }
268
273
 
269
274
  export interface SSRComponent {