@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,5 +1,5 @@
1
+ /** @import { RenderNode } from '../../types.js' */
1
2
  import * as devalue from 'devalue';
2
- import { readable, writable } from 'svelte/store';
3
3
  import { DEV } from 'esm-env';
4
4
  import { isRedirect, text } from '@sveltejs/kit';
5
5
  import * as paths from '$app/paths/internal/server';
@@ -22,14 +22,11 @@ import {
22
22
  } from '../utils.js';
23
23
  import * as env from '__sveltekit/env';
24
24
  import { collect_remote_data } from '../remote.js';
25
+ import Root from '../../components/root.svelte';
26
+ import { render } from 'svelte/server';
25
27
 
26
28
  // TODO rename this function/module
27
29
 
28
- const updated = {
29
- ...readable(false),
30
- check: () => false
31
- };
32
-
33
30
  /**
34
31
  * Creates the HTML response.
35
32
  * @param {{
@@ -46,7 +43,7 @@ const updated = {
46
43
  * resolve_opts: import('types').RequiredResolveOptions;
47
44
  * action_result?: import('@sveltejs/kit').ActionResult;
48
45
  * data_serializer: import('./types.js').ServerDataSerializer;
49
- * error_components?: Array<import('types').SSRComponent | undefined>
46
+ * error_components?: Array<import('svelte').Component | undefined>
50
47
  * }} opts
51
48
  */
52
49
  export async function render_response({
@@ -91,7 +88,8 @@ export async function render_response({
91
88
  // TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
92
89
  const inline_styles = new Map();
93
90
 
94
- /** @type {Awaited<ReturnType<typeof options.root.render>>} */
91
+ // TODO `svelte/server` should expose `RenderOutput`
92
+ /** @type {{ head: string, body: string, hashes: { script: string[] } }} */
95
93
  let rendered;
96
94
 
97
95
  const form_value =
@@ -142,49 +140,47 @@ export async function render_response({
142
140
  if (page_config.ssr) {
143
141
  /** @type {Record<string, any>} */
144
142
  const props = {
145
- stores: {
146
- page: writable(null),
147
- navigating: writable(null),
148
- updated
149
- },
150
- constructors: await Promise.all(
151
- branch.map(({ node }) => {
152
- if (!node.component) {
153
- // Can only be the leaf, layouts have a fallback component generated
154
- throw new Error(`Missing +page.svelte component for route ${event.route.id}`);
155
- }
156
- return node.component();
157
- })
158
- ),
159
- form: form_value
160
- };
161
-
162
- if (error_components) {
163
- if (error) {
164
- props.error = error;
143
+ components: [],
144
+ resetters: [],
145
+ form: form_value,
146
+ tree: /** @type {RenderNode} */ ({}),
147
+ error,
148
+ page: {
149
+ error,
150
+ params: /** @type {Record<string, any>} */ (event.params),
151
+ route: event.route,
152
+ status,
153
+ url: event.url,
154
+ data: {},
155
+ form: form_value,
156
+ state: {}
165
157
  }
166
- props.errors = error_components;
167
- }
158
+ };
168
159
 
169
- let data = {};
160
+ let current_node = props.tree;
161
+ let data = props.page.data;
170
162
 
171
- // props_n (instead of props[n]) makes it easy to avoid
172
- // unnecessary updates for layout components
173
163
  for (let i = 0; i < branch.length; i += 1) {
174
- data = { ...data, ...branch[i].data };
175
- props[`data_${i}`] = data;
164
+ const node = branch[i];
165
+
166
+ data = { ...data, ...node.data };
167
+
168
+ // TODO this is undefined sometimes... where does the default error component come from?
169
+ const error = error_components?.slice(0, i + 1).findLast((x) => x);
170
+
171
+ current_node.error = error;
172
+ current_node.component = await node.node.component?.();
173
+ current_node.data = data;
174
+
175
+ if (i < branch.length - 1) {
176
+ current_node.child = /** @type {import('../../types.js').RenderNode} */ ({});
177
+ current_node = current_node.child;
178
+ }
176
179
  }
177
180
 
178
- props.page = {
179
- error,
180
- params: /** @type {Record<string, any>} */ (event.params),
181
- route: event.route,
182
- status,
183
- url: event.url,
184
- data,
185
- form: form_value,
186
- state: {}
187
- };
181
+ props.page.data = data;
182
+
183
+ const render_state = { ...event_state, is_in_render: true };
188
184
 
189
185
  const render_opts = {
190
186
  context: new Map([
@@ -197,15 +193,28 @@ export async function render_response({
197
193
  ]),
198
194
  csp: csp.script_needs_nonce ? { nonce: csp.nonce } : { hash: csp.script_needs_hash },
199
195
  transformError: error_components
200
- ? /** @param {unknown} e */ async (e) => {
196
+ ? /** @param {unknown} e */ (e) => {
201
197
  if (isRedirect(e)) {
202
198
  throw e;
203
199
  }
204
200
 
205
- const transformed = await handle_error_and_jsonify(event, event_state, options, e);
206
- props.page.error = props.error = error = transformed;
207
- props.page.status = status = transformed.status;
208
- return transformed;
201
+ const handled = handle_error_and_jsonify(event, render_state, options, e);
202
+
203
+ // TODO 4.0 make this an async function and await `handled`
204
+ if (handled instanceof Promise) {
205
+ return handled.then((e) => {
206
+ error = e;
207
+ props.page.error = error;
208
+ props.page.status = status = error.status;
209
+ return error;
210
+ });
211
+ }
212
+
213
+ error = handled;
214
+ props.page.error = error;
215
+ props.page.status = status = error.status;
216
+
217
+ return error;
209
218
  }
210
219
  : undefined
211
220
  };
@@ -231,44 +240,27 @@ export async function render_response({
231
240
  };
232
241
  }
233
242
 
234
- const state = { ...event_state, is_in_render: true };
235
-
236
- rendered = await with_request_store({ event, state }, async () => {
237
- // use relative paths during rendering, so that the resulting HTML is as
238
- // portable as possible, but reset afterwards
239
- if (paths.relative) paths.override({ base, assets });
240
-
243
+ rendered = await with_request_store({ event, state: render_state }, async () => {
241
244
  // We have to invoke .then eagerly here in order to kick off rendering: it's only starting on access,
242
245
  // and `await maybe_promise` would eagerly access the .then property but call its function only after a tick, which is too late
243
246
  // for the paths.reset() below and for any eager getRequestEvent() calls during rendering without AsyncLocalStorage available.
244
- // TODO use render from 'svelte/server' here
245
- const rendered = options.root.render(props, render_opts).then((r) => r);
246
-
247
- // we reset this synchronously, rather than after async rendering is complete,
248
- // to avoid cross-talk between requests. This is a breaking change for
249
- // anyone who opts into async SSR, since `base` and `assets` will no
250
- // longer be relative to the current pathname.
251
- // TODO 3.0 remove `base` and `assets` in favour of `resolve(...)` and `asset(...)`
252
- paths.reset();
247
+ const rendered = render(Root, { ...render_opts, props });
253
248
 
254
- // @ts-expect-error the legacy `render` API only returns html still, but the new API uses body
255
- const { head, html: body, css, hashes } = await rendered;
249
+ const { head, body, hashes } = await rendered;
256
250
 
257
251
  if (hashes) {
258
252
  csp.add_script_hashes(hashes.script);
259
253
  }
260
254
 
261
- return { head, body, css, hashes };
255
+ return { head, body, hashes };
262
256
  });
263
257
  } finally {
264
258
  if (DEV) {
265
259
  globalThis.fetch = fetch;
266
260
  }
267
-
268
- paths.reset(); // just in case `options.root.render(...)` failed
269
261
  }
270
262
  } else {
271
- rendered = { head: '', body: '', css: { code: '', map: null }, hashes: { script: [] } };
263
+ rendered = { head: '', body: '', hashes: { script: [] } };
272
264
  }
273
265
 
274
266
  for (const { node } of branch) {
@@ -412,7 +404,7 @@ export async function render_response({
412
404
 
413
405
  const blocks = [];
414
406
 
415
- const properties = [`base: ${base_expression}`];
407
+ const properties = [`base: ${base_expression}`, `version: ${s(__SVELTEKIT_APP_VERSION__)}`];
416
408
 
417
409
  if (paths.assets) {
418
410
  properties.push(`assets: ${s(paths.assets)}`);
@@ -435,7 +427,9 @@ export async function render_response({
435
427
  if (client.inline) {
436
428
  app_declaration = `const app = ${global}.app.app;`;
437
429
  } else if (client.app) {
438
- app_declaration = `const app = await import(${s(prefixed(client.app))});`;
430
+ app_declaration = `const kit = await import(${s(prefixed(client.start))});
431
+ kit.init(${global});
432
+ const app = await import(${s(prefixed(client.app))});`;
439
433
  } else {
440
434
  app_declaration = `const { app } = await import(${s(prefixed(client.start))});`;
441
435
  }
@@ -529,10 +523,9 @@ export async function render_response({
529
523
 
530
524
  ${serialized_data}${global}.app.start(${args.join(', ')});`
531
525
  : client.app
532
- ? `Promise.all([
533
- import(${s(prefixed(client.start))}),
534
- import(${s(prefixed(client.app))})
535
- ]).then(([kit, app]) => {
526
+ ? `import(${s(prefixed(client.start))}).then(async (kit) => {
527
+ kit.init(${global});
528
+ const app = await import(${s(prefixed(client.app))});
536
529
  ${serialized_data}kit.start(app, ${args.join(', ')});
537
530
  });`
538
531
  : `import(${s(prefixed(client.start))}).then((app) => {
@@ -361,7 +361,8 @@ export async function collect_remote_data(data, event, state, options) {
361
361
  * @returns {Promise<App.Error>}
362
362
  */
363
363
  function convert_error(error) {
364
- return handle_error_and_jsonify(event, state, options, error);
364
+ // TODO 4.0 remove the `Promise.resolve(...)`
365
+ return Promise.resolve(handle_error_and_jsonify(event, state, options, error));
365
366
  }
366
367
 
367
368
  /** @type {Promise<any>[]} */
@@ -70,7 +70,7 @@ export async function internal_respond(request, options, manifest, state) {
70
70
  const is_data_request = has_data_suffix(url.pathname);
71
71
  const remote_id = get_remote_id(url);
72
72
 
73
- if (!DEV) {
73
+ if (!__SVELTEKIT_DEV__) {
74
74
  const request_origin = request.headers.get('origin');
75
75
  const self_origin = get_self_origin(options.paths_origin, url.origin);
76
76
 
@@ -98,9 +98,9 @@ export async function handle_fatal_error(event, state, options, error) {
98
98
  * @param {import('types').RequestState} state
99
99
  * @param {import('types').SSROptions} options
100
100
  * @param {any} error
101
- * @returns {Promise<App.Error>}
101
+ * @returns {App.Error | Promise<App.Error>}
102
102
  */
103
- export async function handle_error_and_jsonify(event, state, options, error) {
103
+ export function handle_error_and_jsonify(event, state, options, error) {
104
104
  if (error instanceof HttpError) {
105
105
  // @ts-expect-error custom user errors may not have a message field if App.Error is overwritten
106
106
  return { message: 'Unknown Error', ...error.body };
@@ -113,11 +113,34 @@ export async function handle_error_and_jsonify(event, state, options, error) {
113
113
  const status = get_status(error);
114
114
  const message = get_message(error);
115
115
 
116
- const body = (await with_request_store({ event, state }, () =>
116
+ // TODO 4.0 await this, rather than handling the non-Promise case
117
+ const result = with_request_store({ event, state }, () =>
117
118
  options.hooks.handleError({ error, event, status, message })
118
- )) ?? { message };
119
+ ) ?? { status, message };
120
+
121
+ if (result instanceof Promise) {
122
+ if (!__SVELTEKIT_SUPPORTS_ASYNC__ && state.is_in_render) {
123
+ console.warn(
124
+ `To use an async \`handleError\` hook to handle errors that occur during rendering, you must enable \`compilerOptions.experimental.async\` in the SvelteKit plugin of your Vite config. The returned error has been replaced with a generic object`
125
+ );
126
+
127
+ // we're discarding the result, but we still need to prevent an unhandled
128
+ // rejection if the user's async `handleError` hook rejects
129
+ result.catch(() => {});
130
+
131
+ return {
132
+ status,
133
+ message: 'Internal Error'
134
+ };
135
+ }
136
+
137
+ return result.then((body) => {
138
+ body ??= { status, message };
139
+ return { ...body, status: get_status(body, error) };
140
+ });
141
+ }
119
142
 
120
- return { ...body, status: get_status(body, error) };
143
+ return { ...result, status: get_status(result, error) };
121
144
  }
122
145
 
123
146
  /**
@@ -0,0 +1,8 @@
1
+ import { Component } from 'svelte';
2
+
3
+ export interface RenderNode {
4
+ component: Component;
5
+ error: Component;
6
+ data: Record<string, any>;
7
+ child?: RenderNode;
8
+ }
@@ -1,4 +1,4 @@
1
- import { RemoteFunctionData } from 'types';
1
+ import { SvelteKitPayload } from 'types';
2
2
 
3
3
  declare global {
4
4
  const __SVELTEKIT_ADAPTER_NAME__: string;
@@ -33,26 +33,20 @@ declare global {
33
33
  * Used for treeshaking universal load code from client bundles when no universal loads exist.
34
34
  */
35
35
  const __SVELTEKIT_HAS_UNIVERSAL_LOAD__: boolean;
36
- /** The `__sveltekit_abc123` object in the init `<script>` */
37
- const __SVELTEKIT_PAYLOAD__: {
38
- /** The basepath, usually relative to the current page */
39
- base: string;
40
- /** Path to externally-hosted assets */
41
- assets?: string;
42
- /** Public environment variables */
43
- env?: Record<string, string>;
44
- /** Serialized data from query/form/command functions */
45
- data?: RemoteFunctionData;
46
- /** Create a placeholder promise */
47
- defer?: (id: number) => Promise<any>;
48
- /** Resolve a placeholder promise */
49
- resolve?: (data: { id: number; data: any; error: any }) => void;
50
- };
36
+ /**
37
+ * The `__sveltekit_abc123` object in the init `<script>`.
38
+ * Should only be used when bundleStrategy !== 'inline' to avoid SvelteKit runtime changing on every build, preventing cacheability.
39
+ */
40
+ const __SVELTEKIT_PAYLOAD__: SvelteKitPayload;
51
41
  /**
52
42
  * The Vite `root` setting used to construct paths to nodes and components
53
43
  * for the SSR manifest during development
54
44
  */
55
45
  const __SVELTEKIT_ROOT__: string;
46
+ /**
47
+ * Whether the `experimental.async` flag is applied
48
+ */
49
+ const __SVELTEKIT_SUPPORTS_ASYNC__: boolean;
56
50
  /**
57
51
  * This makes the use of specific features visible at both dev and build time, in such a
58
52
  * way that we can error when they are not supported by the target platform.
@@ -68,7 +62,6 @@ declare global {
68
62
  * to throw an error if the feature would fail in production.
69
63
  */
70
64
  var __SVELTEKIT_TRACK__: (label: string) => void;
71
- var __SVELTEKIT_EXPERIMENTAL_USE_TRANSFORM_ERROR__: boolean;
72
65
  var Bun: object;
73
66
  var Deno: object;
74
67
  }
@@ -1,4 +1,4 @@
1
- import { SvelteComponent } from 'svelte';
1
+ import { Component } from 'svelte';
2
2
  import {
3
3
  Config,
4
4
  ServerLoad,
@@ -113,7 +113,7 @@ export interface BuildData {
113
113
  }
114
114
 
115
115
  export interface CSRPageNode {
116
- component: typeof SvelteComponent;
116
+ component: Component;
117
117
  universal: {
118
118
  load?: Load;
119
119
  trailingSlash?: TrailingSlash;
@@ -417,27 +417,7 @@ export interface ServerMetadata {
417
417
  remotes: Map<string, Map<string, { type: RemoteInternals['type']; dynamic: boolean }>>;
418
418
  }
419
419
 
420
- // TODO get rid of this in favor us using just import('svelte').Component<any, any, any>
421
- export interface SSRComponent {
422
- default: {
423
- render(
424
- props: Record<string, any>,
425
- opts: { context: Map<any, any>; csp?: { nonce?: string; hash?: boolean } }
426
- ): Promise<{
427
- body: string;
428
- head: string;
429
- css: {
430
- code: string;
431
- map: any; // TODO
432
- };
433
- hashes: {
434
- script: Array<`sha256-${string}`>;
435
- };
436
- }>;
437
- };
438
- }
439
-
440
- export type SSRComponentLoader = () => Promise<SSRComponent>;
420
+ export type SSRComponentLoader = () => Promise<Component>;
441
421
 
442
422
  export interface UniversalNode {
443
423
  /** Is `null` in case static analysis succeeds but the node is ssr=false */
@@ -476,7 +456,10 @@ export interface SSRNode {
476
456
 
477
457
  /**
478
458
  * During development, all styles are inlined for the page to avoid FOUC.
479
- * But in production, this stores styles that are below the inline threshold
459
+ * But in production, this stores styles that are below the inline threshold.
460
+ * It returns a Promise during development because Vite needs to load the
461
+ * modules on demand. But in production, the contents have been precomputed
462
+ * during the build, so it can return synchronously.
480
463
  */
481
464
  inline_styles?(): MaybePromise<
482
465
  Record<string, string | ((assets: string, base: string) => string)>
@@ -501,10 +484,8 @@ export interface SSROptions {
501
484
  hooks: ServerHooks;
502
485
  link_header_preload: ValidatedConfig['kit']['output']['linkHeaderPreload'];
503
486
  paths_origin: string | undefined;
504
- root: SSRComponent['default'];
505
487
  service_worker: boolean;
506
488
  service_worker_options: RegistrationOptions;
507
- server_error_boundaries: boolean;
508
489
  templates: {
509
490
  app(values: {
510
491
  head: string;
@@ -764,5 +745,23 @@ export interface RequestStore {
764
745
  state: RequestState;
765
746
  }
766
747
 
748
+ /** Type of the `__sveltekit_abc123` object in the init `<script>` */
749
+ export interface SvelteKitPayload {
750
+ /** The application version */
751
+ version: string;
752
+ /** The basepath, usually relative to the current page */
753
+ base: string;
754
+ /** Path to externally-hosted assets */
755
+ assets?: string;
756
+ /** Public environment variables */
757
+ env?: Record<string, string>;
758
+ /** Serialized data from query/form/command functions */
759
+ data?: RemoteFunctionData;
760
+ /** Create a placeholder promise */
761
+ defer?: (id: number) => Promise<any>;
762
+ /** Resolve a placeholder promise */
763
+ resolve?: (data: { id: number; data: any; error: any }) => void;
764
+ }
765
+
767
766
  export * from '../exports/index.js';
768
767
  export * from './private.js';
@@ -2,7 +2,12 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
4
  /**
5
- * Resolves a peer dependency relative to the current working directory. Duplicated with `packages/adapter-auto`
5
+ * Resolves a peer dependency relative to the current working directory.
6
+ *
7
+ * Mainly used to resolve the correct Vite package when an app's SvelteKit is a
8
+ * linked local repository.
9
+ *
10
+ * Duplicated with `packages/adapter-auto`
6
11
  * @param {string} dependency
7
12
  * @param {string} root
8
13
  */
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Reads the `imports` field from the package.json at the given directory.
6
+ * @param {string} cwd
7
+ * @returns {Record<string, string | Record<string, string>> | undefined}
8
+ */
9
+ export function read_package_imports(cwd) {
10
+ const pkg_path = path.resolve(cwd, 'package.json');
11
+ if (fs.existsSync(pkg_path)) {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(pkg_path, 'utf-8')).imports;
14
+ } catch {
15
+ // malformed package.json — ignore, the user will see other errors
16
+ }
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Normalizes an import value to a string path, handling both string values
22
+ * and conditional export objects (using the `default` key).
23
+ * @param {string | Record<string, string>} value
24
+ * @returns {string | null}
25
+ */
26
+ export function normalize_import_value(value) {
27
+ if (typeof value === 'string') {
28
+ return value.replace(/^\.\//, '');
29
+ }
30
+ if (value && typeof value === 'object' && typeof value.default === 'string') {
31
+ return value.default.replace(/^\.\//, '');
32
+ }
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * Returns the `#`-prefixed import keys from the package.json `imports` field.
38
+ * @param {string} cwd
39
+ * @returns {string[]}
40
+ */
41
+ export function get_hash_import_keys(cwd) {
42
+ const imports = read_package_imports(cwd);
43
+ if (!imports) return [];
44
+ return Object.keys(imports).filter((key) => key.startsWith('#'));
45
+ }
46
+
47
+ /**
48
+ * Computes alias entries for `normalize_id` from the package.json `imports` field.
49
+ * Each entry maps a `#xx` alias to its absolute target path. Entries are sorted
50
+ * by path length descending so that longer/more-specific paths are matched first.
51
+ *
52
+ * @param {string} root
53
+ * @param {(p: string) => string} [normalize] - optional path normalizer (e.g. `vite.normalizePath`)
54
+ * @returns {Array<{ alias: string, path: string }>}
55
+ */
56
+ export function get_import_aliases(root, normalize) {
57
+ const imports = read_package_imports(root);
58
+ if (!imports) return [];
59
+
60
+ /** @type {Array<{ alias: string, path: string }>} */
61
+ const aliases = [];
62
+
63
+ for (const [key, raw_value] of Object.entries(imports)) {
64
+ if (!key.startsWith('#')) continue;
65
+
66
+ const value = normalize_import_value(raw_value);
67
+ if (!value) continue;
68
+
69
+ // For `#xx/*` imports, the target directory is the value without `/*`
70
+ // For `#xx` imports, the target is the file itself
71
+ const target = value.endsWith('/*') ? value.slice(0, -2) : value;
72
+ if (target.includes('*')) continue; // not sure if this is even allowed in Node, anyway it's too niche/complex to support here
73
+ const abs = path.resolve(root, target);
74
+ const alias = key.endsWith('/*') ? key.slice(0, -2) : key;
75
+
76
+ aliases.push({ alias, path: normalize ? normalize(abs) : abs });
77
+ }
78
+
79
+ // Sort by path length descending so longer/more-specific paths match first
80
+ aliases.sort((a, b) => b.path.length - a.path.length);
81
+
82
+ return aliases;
83
+ }
@@ -1,6 +1,6 @@
1
1
  import { BROWSER } from 'esm-env';
2
2
 
3
- const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
3
+ const param_pattern = /^(\[)?(\.\.\.)?([\w-]+)(?:=([\w-]+))?(\])?$/;
4
4
 
5
5
  const root_group_pattern = /^\/\((?:[^)]+)\)$/;
6
6
 
@@ -19,7 +19,7 @@ export function parse_route_id(id) {
19
19
  `^${get_route_segments(id)
20
20
  .map((segment) => {
21
21
  // special case — /[...rest]/ could contain zero segments
22
- const rest_match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
22
+ const rest_match = /^\[\.\.\.([\w-]+)(?:=([\w-]+))?\]$/.exec(segment);
23
23
  if (rest_match) {
24
24
  params.push({
25
25
  name: rest_match[1],
@@ -31,7 +31,7 @@ export function parse_route_id(id) {
31
31
  return '(?:/([^]*))?';
32
32
  }
33
33
  // special case — /[[optional]]/ could contain zero segments
34
- const optional_match = /^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(segment);
34
+ const optional_match = /^\[\[([\w-]+)(?:=([\w-]+))?\]\]$/.exec(segment);
35
35
  if (optional_match) {
36
36
  params.push({
37
37
  name: optional_match[1],
@@ -72,7 +72,7 @@ export function parse_route_id(id) {
72
72
  const match = /** @type {RegExpExecArray} */ (param_pattern.exec(content));
73
73
  if (!BROWSER && !match) {
74
74
  throw new Error(
75
- `Invalid param: ${content}. Params and matcher names can only have underscores and alphanumeric characters.`
75
+ `Invalid param: ${content}. Params and matcher names can only have underscores, hyphens, and alphanumeric characters.`
76
76
  );
77
77
  }
78
78
 
@@ -103,7 +103,7 @@ export function parse_route_id(id) {
103
103
  return { pattern, params };
104
104
  }
105
105
 
106
- const optional_param_regex = /\/\[\[\w+?(?:=\w+)?\]\]/;
106
+ const optional_param_regex = /\/\[\[[\w-]+?(?:=[\w-]+)?\]\]/;
107
107
 
108
108
  /**
109
109
  * Removes optional params from a route ID.
@@ -260,7 +260,7 @@ function escape(str) {
260
260
  );
261
261
  }
262
262
 
263
- const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
263
+ const basic_param_pattern = /\[(\[)?(\.\.\.)?([\w-]+?)(?:=([\w-]+))?\]\]?/g;
264
264
 
265
265
  /**
266
266
  * Populate a route ID with params to resolve a pathname.
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 = '3.0.0-next.7';
4
+ export const VERSION = '3.0.0-next.9';