@sveltejs/kit 1.0.0-next.55 → 1.0.0-next.550

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 (128) hide show
  1. package/README.md +5 -2
  2. package/package.json +93 -67
  3. package/postinstall.js +47 -0
  4. package/scripts/special-types/$env+dynamic+private.md +10 -0
  5. package/scripts/special-types/$env+dynamic+public.md +8 -0
  6. package/scripts/special-types/$env+static+private.md +19 -0
  7. package/scripts/special-types/$env+static+public.md +7 -0
  8. package/scripts/special-types/$lib.md +5 -0
  9. package/src/cli.js +108 -0
  10. package/src/constants.js +5 -0
  11. package/src/core/adapt/builder.js +212 -0
  12. package/src/core/adapt/index.js +31 -0
  13. package/src/core/config/default-error.html +56 -0
  14. package/src/core/config/index.js +110 -0
  15. package/src/core/config/options.js +516 -0
  16. package/src/core/config/types.d.ts +1 -0
  17. package/src/core/env.js +121 -0
  18. package/src/core/generate_manifest/index.js +125 -0
  19. package/src/core/prerender/crawl.js +207 -0
  20. package/src/core/prerender/entities.js +2252 -0
  21. package/src/core/prerender/prerender.js +460 -0
  22. package/src/core/prerender/queue.js +80 -0
  23. package/src/core/sync/create_manifest_data/conflict.js +0 -0
  24. package/src/core/sync/create_manifest_data/index.js +513 -0
  25. package/src/core/sync/create_manifest_data/sort.js +161 -0
  26. package/src/core/sync/create_manifest_data/types.d.ts +37 -0
  27. package/src/core/sync/sync.js +78 -0
  28. package/src/core/sync/utils.js +33 -0
  29. package/src/core/sync/write_ambient.js +53 -0
  30. package/src/core/sync/write_client_manifest.js +106 -0
  31. package/src/core/sync/write_matchers.js +25 -0
  32. package/src/core/sync/write_root.js +91 -0
  33. package/src/core/sync/write_tsconfig.js +195 -0
  34. package/src/core/sync/write_types/index.js +809 -0
  35. package/src/core/utils.js +67 -0
  36. package/src/exports/hooks/index.js +1 -0
  37. package/src/exports/hooks/sequence.js +44 -0
  38. package/src/exports/index.js +45 -0
  39. package/src/exports/node/index.js +172 -0
  40. package/src/exports/node/polyfills.js +28 -0
  41. package/src/exports/vite/build/build_server.js +384 -0
  42. package/src/exports/vite/build/build_service_worker.js +92 -0
  43. package/src/exports/vite/build/utils.js +195 -0
  44. package/src/exports/vite/dev/index.js +588 -0
  45. package/src/exports/vite/graph_analysis/index.js +107 -0
  46. package/src/exports/vite/graph_analysis/types.d.ts +5 -0
  47. package/src/exports/vite/graph_analysis/utils.js +6 -0
  48. package/src/exports/vite/index.js +651 -0
  49. package/src/exports/vite/preview/index.js +193 -0
  50. package/src/exports/vite/types.d.ts +3 -0
  51. package/src/exports/vite/utils.js +171 -0
  52. package/src/runtime/app/env.js +1 -0
  53. package/src/runtime/app/environment.js +11 -0
  54. package/src/runtime/app/forms.js +141 -0
  55. package/src/runtime/app/navigation.js +23 -0
  56. package/src/runtime/app/paths.js +1 -0
  57. package/src/runtime/app/stores.js +102 -0
  58. package/src/runtime/client/ambient.d.ts +30 -0
  59. package/src/runtime/client/client.js +1726 -0
  60. package/src/runtime/client/fetcher.js +121 -0
  61. package/src/runtime/client/parse.js +60 -0
  62. package/src/runtime/client/singletons.js +21 -0
  63. package/src/runtime/client/start.js +43 -0
  64. package/src/runtime/client/types.d.ts +84 -0
  65. package/src/runtime/client/utils.js +166 -0
  66. package/src/runtime/components/error.svelte +16 -0
  67. package/{assets → src/runtime}/components/layout.svelte +0 -0
  68. package/src/runtime/control.js +98 -0
  69. package/src/runtime/env/dynamic/private.js +1 -0
  70. package/src/runtime/env/dynamic/public.js +1 -0
  71. package/src/runtime/env-private.js +6 -0
  72. package/src/runtime/env-public.js +6 -0
  73. package/src/runtime/env.js +6 -0
  74. package/src/runtime/hash.js +20 -0
  75. package/src/runtime/paths.js +11 -0
  76. package/src/runtime/server/cookie.js +231 -0
  77. package/src/runtime/server/data/index.js +144 -0
  78. package/src/runtime/server/endpoint.js +89 -0
  79. package/src/runtime/server/fetch.js +164 -0
  80. package/src/runtime/server/index.js +375 -0
  81. package/src/runtime/server/page/actions.js +258 -0
  82. package/src/runtime/server/page/crypto.js +239 -0
  83. package/src/runtime/server/page/csp.js +250 -0
  84. package/src/runtime/server/page/index.js +303 -0
  85. package/src/runtime/server/page/load_data.js +258 -0
  86. package/src/runtime/server/page/render.js +391 -0
  87. package/src/runtime/server/page/respond_with_error.js +102 -0
  88. package/src/runtime/server/page/serialize_data.js +87 -0
  89. package/src/runtime/server/page/types.d.ts +35 -0
  90. package/src/runtime/server/utils.js +205 -0
  91. package/src/utils/array.js +9 -0
  92. package/src/utils/error.js +22 -0
  93. package/src/utils/escape.js +46 -0
  94. package/src/utils/filesystem.js +166 -0
  95. package/src/utils/functions.js +16 -0
  96. package/src/utils/http.js +72 -0
  97. package/src/utils/misc.js +1 -0
  98. package/src/utils/promises.js +17 -0
  99. package/src/utils/routing.js +168 -0
  100. package/src/utils/unit_test.js +11 -0
  101. package/src/utils/url.js +159 -0
  102. package/svelte-kit.js +1 -1
  103. package/types/ambient.d.ts +469 -0
  104. package/types/index.d.ts +775 -0
  105. package/types/internal.d.ts +381 -0
  106. package/types/private.d.ts +229 -0
  107. package/CHANGELOG.md +0 -519
  108. package/assets/components/error.svelte +0 -13
  109. package/assets/runtime/app/env.js +0 -5
  110. package/assets/runtime/app/navigation.js +0 -48
  111. package/assets/runtime/app/paths.js +0 -1
  112. package/assets/runtime/app/stores.js +0 -93
  113. package/assets/runtime/chunks/utils.js +0 -22
  114. package/assets/runtime/internal/singletons.js +0 -23
  115. package/assets/runtime/internal/start.js +0 -823
  116. package/assets/runtime/paths.js +0 -12
  117. package/dist/chunks/index.js +0 -3544
  118. package/dist/chunks/index2.js +0 -572
  119. package/dist/chunks/index3.js +0 -246
  120. package/dist/chunks/index4.js +0 -569
  121. package/dist/chunks/index5.js +0 -751
  122. package/dist/chunks/index6.js +0 -323
  123. package/dist/chunks/standard.js +0 -99
  124. package/dist/chunks/utils.js +0 -83
  125. package/dist/cli.js +0 -558
  126. package/dist/ssr.js +0 -2620
  127. package/types.d.ts +0 -74
  128. package/types.internal.d.ts +0 -237
@@ -0,0 +1,513 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import mime from 'mime';
4
+ import { runtime_directory } from '../../utils.js';
5
+ import { posixify } from '../../../utils/filesystem.js';
6
+ import { parse_route_id } from '../../../utils/routing.js';
7
+ import { sort_routes } from './sort.js';
8
+
9
+ /**
10
+ * Generates the manifest data used for the client-side manifest and types generation.
11
+ * @param {{
12
+ * config: import('types').ValidatedConfig;
13
+ * fallback?: string;
14
+ * cwd?: string;
15
+ * }} opts
16
+ * @returns {import('types').ManifestData}
17
+ */
18
+ export default function create_manifest_data({
19
+ config,
20
+ fallback = `${runtime_directory}/components`,
21
+ cwd = process.cwd()
22
+ }) {
23
+ const assets = create_assets(config);
24
+ const matchers = create_matchers(config, cwd);
25
+ const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
26
+
27
+ return {
28
+ assets,
29
+ matchers,
30
+ nodes,
31
+ routes
32
+ };
33
+ }
34
+
35
+ /**
36
+ * @param {import('types').ValidatedConfig} config
37
+ */
38
+ export function create_assets(config) {
39
+ return list_files(config.kit.files.assets).map((file) => ({
40
+ file,
41
+ size: fs.statSync(path.resolve(config.kit.files.assets, file)).size,
42
+ type: mime.getType(file)
43
+ }));
44
+ }
45
+
46
+ /**
47
+ * @param {import('types').ValidatedConfig} config
48
+ * @param {string} cwd
49
+ */
50
+ function create_matchers(config, cwd) {
51
+ const params_base = path.relative(cwd, config.kit.files.params);
52
+
53
+ /** @type {Record<string, string>} */
54
+ const matchers = {};
55
+ if (fs.existsSync(config.kit.files.params)) {
56
+ for (const file of fs.readdirSync(config.kit.files.params)) {
57
+ const ext = path.extname(file);
58
+ if (!config.kit.moduleExtensions.includes(ext)) continue;
59
+ const type = file.slice(0, -ext.length);
60
+
61
+ if (/^\w+$/.test(type)) {
62
+ const matcher_file = path.join(params_base, file);
63
+
64
+ // Disallow same matcher with different extensions
65
+ if (matchers[type]) {
66
+ throw new Error(`Duplicate matchers: ${matcher_file} and ${matchers[type]}`);
67
+ } else {
68
+ matchers[type] = matcher_file;
69
+ }
70
+ } else {
71
+ throw new Error(
72
+ `Matcher names can only have underscores and alphanumeric characters — "${file}" is invalid`
73
+ );
74
+ }
75
+ }
76
+ }
77
+
78
+ return matchers;
79
+ }
80
+
81
+ /**
82
+ * @param {import('types').ValidatedConfig} config
83
+ * @param {string} cwd
84
+ * @param {string} fallback
85
+ */
86
+ function create_routes_and_nodes(cwd, config, fallback) {
87
+ /** @type {import('types').RouteData[]} */
88
+ const routes = [];
89
+
90
+ const routes_base = posixify(path.relative(cwd, config.kit.files.routes));
91
+
92
+ const valid_extensions = [...config.extensions, ...config.kit.moduleExtensions];
93
+
94
+ /** @type {import('types').PageNode[]} */
95
+ const nodes = [];
96
+
97
+ if (fs.existsSync(config.kit.files.routes)) {
98
+ /**
99
+ * @param {number} depth
100
+ * @param {string} id
101
+ * @param {string} segment
102
+ * @param {import('types').RouteData | null} parent
103
+ */
104
+ const walk = (depth, id, segment, parent) => {
105
+ const unescaped = id.replace(/\[([ux])\+([^\]]+)\]/gi, (match, type, code) => {
106
+ if (match !== match.toLowerCase()) {
107
+ throw new Error(`Character escape sequence in ${id} must be lowercase`);
108
+ }
109
+
110
+ if (!/[0-9a-f]+/.test(code)) {
111
+ throw new Error(`Invalid character escape sequence in ${id}`);
112
+ }
113
+
114
+ if (type === 'x') {
115
+ if (code.length !== 2) {
116
+ throw new Error(`Hexadecimal escape sequence in ${id} must be two characters`);
117
+ }
118
+
119
+ return String.fromCharCode(parseInt(code, 16));
120
+ } else {
121
+ if (code.length < 4 || code.length > 6) {
122
+ throw new Error(
123
+ `Unicode escape sequence in ${id} must be between four and six characters`
124
+ );
125
+ }
126
+
127
+ return String.fromCharCode(parseInt(code, 16));
128
+ }
129
+ });
130
+
131
+ if (/\]\[/.test(unescaped)) {
132
+ throw new Error(`Invalid route ${id} — parameters must be separated`);
133
+ }
134
+
135
+ if (count_occurrences('[', id) !== count_occurrences(']', id)) {
136
+ throw new Error(`Invalid route ${id} — brackets are unbalanced`);
137
+ }
138
+
139
+ if (/#/.test(segment)) {
140
+ // Vite will barf on files with # in them
141
+ throw new Error(`Route ${id} should be renamed to ${id.replace(/#/g, '[x+23]')}`);
142
+ }
143
+
144
+ if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
145
+ throw new Error(
146
+ `Invalid route ${id} — an [[optional]] route segment cannot follow a [...rest] route segment`
147
+ );
148
+ }
149
+
150
+ if (/\[\[\.\.\./.test(id)) {
151
+ throw new Error(
152
+ `Invalid route ${id} — a rest route segment is always optional, remove the outer square brackets`
153
+ );
154
+ }
155
+
156
+ const { pattern, names, types, optional } = parse_route_id(id);
157
+
158
+ /** @type {import('types').RouteData} */
159
+ const route = {
160
+ id,
161
+ parent,
162
+
163
+ segment,
164
+ pattern,
165
+ names,
166
+ types,
167
+ optional,
168
+
169
+ layout: null,
170
+ error: null,
171
+ leaf: null,
172
+ page: null,
173
+ endpoint: null
174
+ };
175
+
176
+ // important to do this before walking children, so that child
177
+ // routes appear later
178
+ routes.push(route);
179
+
180
+ // if we don't do this, the route map becomes unwieldy to console.log
181
+ Object.defineProperty(route, 'parent', { enumerable: false });
182
+
183
+ const dir = path.join(cwd, routes_base, id);
184
+
185
+ // We can't use withFileTypes because of a NodeJs bug which returns wrong results
186
+ // with isDirectory() in case of symlinks: https://github.com/nodejs/node/issues/30646
187
+ const files = fs.readdirSync(dir).map((name) => ({
188
+ is_dir: fs.statSync(path.join(dir, name)).isDirectory(),
189
+ name
190
+ }));
191
+
192
+ // process files first
193
+ for (const file of files) {
194
+ if (file.is_dir) continue;
195
+ if (!file.name.startsWith('+')) continue;
196
+ if (!valid_extensions.find((ext) => file.name.endsWith(ext))) continue;
197
+
198
+ const project_relative = posixify(path.relative(cwd, path.join(dir, file.name)));
199
+
200
+ const item = analyze(
201
+ project_relative,
202
+ file.name,
203
+ config.extensions,
204
+ config.kit.moduleExtensions
205
+ );
206
+
207
+ if (item.kind === 'component') {
208
+ if (item.is_error) {
209
+ route.error = {
210
+ depth,
211
+ component: project_relative
212
+ };
213
+ } else if (item.is_layout) {
214
+ if (!route.layout) route.layout = { depth, child_pages: [] };
215
+ route.layout.component = project_relative;
216
+ if (item.uses_layout !== undefined) route.layout.parent_id = item.uses_layout;
217
+ } else {
218
+ if (!route.leaf) route.leaf = { depth };
219
+ route.leaf.component = project_relative;
220
+ if (item.uses_layout !== undefined) route.leaf.parent_id = item.uses_layout;
221
+ }
222
+ } else if (item.is_layout) {
223
+ if (!route.layout) route.layout = { depth, child_pages: [] };
224
+ route.layout[item.kind] = project_relative;
225
+ } else if (item.is_page) {
226
+ if (!route.leaf) route.leaf = { depth };
227
+ route.leaf[item.kind] = project_relative;
228
+ } else {
229
+ route.endpoint = {
230
+ file: project_relative
231
+ };
232
+ }
233
+ }
234
+
235
+ // then handle children
236
+ for (const file of files) {
237
+ if (file.is_dir) {
238
+ walk(depth + 1, path.posix.join(id, file.name), file.name, route);
239
+ }
240
+ }
241
+ };
242
+
243
+ walk(0, '/', '', null);
244
+
245
+ if (routes.length === 1) {
246
+ const root = routes[0];
247
+ if (!root.leaf && !root.error && !root.layout && !root.endpoint) {
248
+ throw new Error(
249
+ // TODO adjust this error message for 1.0
250
+ // 'No routes found. If you are using a custom src/routes directory, make sure it is specified in svelte.config.js'
251
+ 'The filesystem router API has changed, see https://github.com/sveltejs/kit/discussions/5774 for details'
252
+ );
253
+ }
254
+ }
255
+ } else {
256
+ // If there's no routes directory, we'll just create a single empty route. This ensures the root layout and
257
+ // error components are included in the manifest, which is needed for subsequent build/dev commands to work
258
+ routes.push({
259
+ id: '/',
260
+ segment: '',
261
+ pattern: /^$/,
262
+ names: [],
263
+ types: [],
264
+ optional: [],
265
+ parent: null,
266
+ layout: null,
267
+ error: null,
268
+ leaf: null,
269
+ page: null,
270
+ endpoint: null
271
+ });
272
+ }
273
+
274
+ prevent_conflicts(routes);
275
+
276
+ const root = routes[0];
277
+
278
+ if (!root.layout?.component) {
279
+ if (!root.layout) root.layout = { depth: 0, child_pages: [] };
280
+ root.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`));
281
+ }
282
+
283
+ if (!root.error?.component) {
284
+ if (!root.error) root.error = { depth: 0 };
285
+ root.error.component = posixify(path.relative(cwd, `${fallback}/error.svelte`));
286
+ }
287
+
288
+ // we do layouts/errors first as they are more likely to be reused,
289
+ // and smaller indexes take fewer bytes. also, this guarantees that
290
+ // the default error/layout are 0/1
291
+ for (const route of routes) {
292
+ if (route.layout) {
293
+ if (!route.layout?.component) {
294
+ route.layout.component = posixify(path.relative(cwd, `${fallback}/layout.svelte`));
295
+ }
296
+ nodes.push(route.layout);
297
+ }
298
+ if (route.error) nodes.push(route.error);
299
+ }
300
+
301
+ for (const route of routes) {
302
+ if (route.leaf) nodes.push(route.leaf);
303
+ }
304
+
305
+ const indexes = new Map(nodes.map((node, i) => [node, i]));
306
+
307
+ for (const route of routes) {
308
+ if (!route.leaf) continue;
309
+
310
+ route.page = {
311
+ layouts: [],
312
+ errors: [],
313
+ leaf: /** @type {number} */ (indexes.get(route.leaf))
314
+ };
315
+
316
+ /** @type {import('types').RouteData | null} */
317
+ let current_route = route;
318
+ let current_node = route.leaf;
319
+ let parent_id = route.leaf.parent_id;
320
+
321
+ while (current_route) {
322
+ if (parent_id === undefined || current_route.segment === parent_id) {
323
+ if (current_route.layout || current_route.error) {
324
+ route.page.layouts.unshift(
325
+ current_route.layout ? indexes.get(current_route.layout) : undefined
326
+ );
327
+ route.page.errors.unshift(
328
+ current_route.error ? indexes.get(current_route.error) : undefined
329
+ );
330
+ }
331
+
332
+ if (current_route.layout) {
333
+ /** @type {import('types').PageNode[]} */ (current_route.layout.child_pages).push(
334
+ route.leaf
335
+ );
336
+ current_node.parent = current_node = current_route.layout;
337
+ parent_id = current_node.parent_id;
338
+ } else {
339
+ parent_id = undefined;
340
+ }
341
+ }
342
+
343
+ current_route = current_route.parent;
344
+ }
345
+
346
+ if (parent_id !== undefined) {
347
+ throw new Error(`${current_node.component} references missing segment "${parent_id}"`);
348
+ }
349
+ }
350
+
351
+ return {
352
+ nodes,
353
+ routes: sort_routes(routes)
354
+ };
355
+ }
356
+
357
+ /**
358
+ * @param {string} project_relative
359
+ * @param {string} file
360
+ * @param {string[]} component_extensions
361
+ * @param {string[]} module_extensions
362
+ * @returns {import('./types').RouteFile}
363
+ */
364
+ function analyze(project_relative, file, component_extensions, module_extensions) {
365
+ const component_extension = component_extensions.find((ext) => file.endsWith(ext));
366
+ if (component_extension) {
367
+ const name = file.slice(0, -component_extension.length);
368
+ const pattern = /^\+(?:(page(?:@(.*))?)|(layout(?:@(.*))?)|(error))$/;
369
+ const match = pattern.exec(name);
370
+ if (!match) {
371
+ // TODO remove for 1.0
372
+ if (/^\+layout-/.test(name)) {
373
+ throw new Error(
374
+ `${project_relative} should be reimplemented with layout groups: https://kit.svelte.dev/docs/advanced-routing#advanced-layouts`
375
+ );
376
+ }
377
+
378
+ throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
379
+ }
380
+
381
+ return {
382
+ kind: 'component',
383
+ is_page: !!match[1],
384
+ is_layout: !!match[3],
385
+ is_error: !!match[5],
386
+ uses_layout: match[2] ?? match[4]
387
+ };
388
+ }
389
+
390
+ const module_extension = module_extensions.find((ext) => file.endsWith(ext));
391
+ if (module_extension) {
392
+ const name = file.slice(0, -module_extension.length);
393
+ const pattern =
394
+ /^\+(?:(server)|(page(?:(@[a-zA-Z0-9_-]*))?(\.server)?)|(layout(?:(@[a-zA-Z0-9_-]*))?(\.server)?))$/;
395
+ const match = pattern.exec(name);
396
+ if (!match) {
397
+ throw new Error(`Files prefixed with + are reserved (saw ${project_relative})`);
398
+ } else if (match[3] || match[6]) {
399
+ throw new Error(
400
+ // prettier-ignore
401
+ `Only Svelte files can reference named layouts. Remove '${match[3] || match[6]}' from ${file} (at ${project_relative})`
402
+ );
403
+ }
404
+
405
+ const kind = !!(match[1] || match[4] || match[7]) ? 'server' : 'shared';
406
+
407
+ return {
408
+ kind,
409
+ is_page: !!match[2],
410
+ is_layout: !!match[5]
411
+ };
412
+ }
413
+
414
+ throw new Error(`Files and directories prefixed with + are reserved (saw ${project_relative})`);
415
+ }
416
+
417
+ /** @param {string} dir */
418
+ function list_files(dir) {
419
+ /** @type {string[]} */
420
+ const files = [];
421
+
422
+ /** @param {string} current */
423
+ function walk(current) {
424
+ for (const file of fs.readdirSync(path.resolve(dir, current))) {
425
+ const child = path.posix.join(current, file);
426
+ if (fs.statSync(path.resolve(dir, child)).isDirectory()) {
427
+ walk(child);
428
+ } else {
429
+ files.push(child);
430
+ }
431
+ }
432
+ }
433
+
434
+ if (fs.existsSync(dir)) walk('');
435
+
436
+ return files;
437
+ }
438
+
439
+ /**
440
+ * @param {string} needle
441
+ * @param {string} haystack
442
+ */
443
+ function count_occurrences(needle, haystack) {
444
+ let count = 0;
445
+ for (let i = 0; i < haystack.length; i += 1) {
446
+ if (haystack[i] === needle) count += 1;
447
+ }
448
+ return count;
449
+ }
450
+
451
+ /** @param {import('types').RouteData[]} routes */
452
+ function prevent_conflicts(routes) {
453
+ /** @type {Map<string, string>} */
454
+ const lookup = new Map();
455
+
456
+ for (const route of routes) {
457
+ if (!route.leaf && !route.endpoint) continue;
458
+
459
+ const normalized = normalize_route_id(route.id);
460
+
461
+ // find all permutations created by optional parameters
462
+ const split = normalized.split(/<\?(.+?)\>/g);
463
+
464
+ let permutations = [/** @type {string} */ (split[0])];
465
+
466
+ // turn `x/[[optional]]/y` into `x/y` and `x/[required]/y`
467
+ for (let i = 1; i < split.length; i += 2) {
468
+ const matcher = split[i];
469
+ const next = split[i + 1];
470
+
471
+ permutations = [
472
+ ...permutations.map((x) => x + next),
473
+ ...permutations.map((x) => x + `<${matcher}>${next}`)
474
+ ];
475
+ }
476
+
477
+ for (const permutation of permutations) {
478
+ // remove leading/trailing/duplicated slashes caused by prior
479
+ // manipulation of optional parameters and (groups)
480
+ const key = permutation
481
+ .replace(/\/{2,}/, '/')
482
+ .replace(/^\//, '')
483
+ .replace(/\/$/, '');
484
+
485
+ if (lookup.has(key)) {
486
+ throw new Error(
487
+ `The "${lookup.get(key)}" and "${route.id}" routes conflict with each other`
488
+ );
489
+ }
490
+
491
+ lookup.set(key, route.id);
492
+ }
493
+ }
494
+ }
495
+
496
+ /** @param {string} id */
497
+ function normalize_route_id(id) {
498
+ return (
499
+ id
500
+ // remove groups
501
+ .replace(/(?<=^|\/)\(.+?\)(?=$|\/)/g, '')
502
+
503
+ .replace(/\[[ux]\+([0-9a-f]+)\]/g, (_, x) =>
504
+ String.fromCharCode(parseInt(x, 16)).replace(/\//g, '%2f')
505
+ )
506
+
507
+ // replace `[param]` with `<*>`, `[param=x]` with `<x>`, and `[[param]]` with `<?*>`
508
+ .replace(
509
+ /\[(?:(\[)|(\.\.\.))?.+?(=.+?)?\]\]?/g,
510
+ (_, optional, rest, matcher) => `<${optional ? '?' : ''}${rest ?? ''}${matcher ?? '*'}>`
511
+ )
512
+ );
513
+ }
@@ -0,0 +1,161 @@
1
+ import { get_route_segments } from '../../../utils/routing.js';
2
+
3
+ /**
4
+ * @typedef {{
5
+ * type: 'static' | 'required' | 'optional' | 'rest';
6
+ * content: string;
7
+ * matched: boolean;
8
+ * }} Part
9
+ */
10
+
11
+ /**
12
+ * @typedef {Part[]} Segment
13
+ */
14
+
15
+ const EMPTY = { type: 'static', content: '', matched: false };
16
+
17
+ /** @param {import('types').RouteData[]} routes */
18
+ export function sort_routes(routes) {
19
+ /** @type {Map<string, Part[]>} */
20
+ const segment_cache = new Map();
21
+
22
+ /** @param {string} segment */
23
+ function get_parts(segment) {
24
+ if (!segment_cache.has(segment)) {
25
+ segment_cache.set(segment, split(segment));
26
+ }
27
+
28
+ return segment_cache.get(segment);
29
+ }
30
+
31
+ /** @param {string} id */
32
+ function split(id) {
33
+ /** @type {Part[]} */
34
+ const parts = [];
35
+
36
+ let i = 0;
37
+ while (i <= id.length) {
38
+ const start = id.indexOf('[', i);
39
+ if (start === -1) {
40
+ parts.push({ type: 'static', content: id.slice(i), matched: false });
41
+ break;
42
+ }
43
+
44
+ parts.push({ type: 'static', content: id.slice(i, start), matched: false });
45
+
46
+ const type = id[start + 1] === '[' ? 'optional' : id[start + 1] === '.' ? 'rest' : 'required';
47
+ const delimiter = type === 'optional' ? ']]' : ']';
48
+ const end = id.indexOf(delimiter, start);
49
+
50
+ if (end === -1) {
51
+ throw new Error(`Invalid route ID ${id}`);
52
+ }
53
+
54
+ const content = id.slice(start, (i = end + delimiter.length));
55
+
56
+ parts.push({
57
+ type,
58
+ content,
59
+ matched: content.includes('=')
60
+ });
61
+ }
62
+
63
+ return parts;
64
+ }
65
+
66
+ return routes.sort((route_a, route_b) => {
67
+ const segments_a = split_route_id(route_a.id).map(get_parts);
68
+ const segments_b = split_route_id(route_b.id).map(get_parts);
69
+
70
+ for (let i = 0; i < Math.max(segments_a.length, segments_b.length); i += 1) {
71
+ const segment_a = segments_a[i] ?? [EMPTY];
72
+ const segment_b = segments_b[i] ?? [EMPTY];
73
+
74
+ for (let j = 0; j < Math.max(segment_a.length, segment_b.length); j += 1) {
75
+ const a = segment_a[j];
76
+ const b = segment_b[j];
77
+
78
+ // first part of each segment is always static
79
+ // (though it may be the empty string), then
80
+ // it alternates between dynamic and static
81
+ // (i.e. [foo][bar] is disallowed)
82
+ const dynamic = j % 2 === 1;
83
+
84
+ if (dynamic) {
85
+ if (!a) return -1;
86
+ if (!b) return +1;
87
+
88
+ // get the next static chunk, so we can handle [...rest] edge cases
89
+ const next_a = segment_a[j + 1].content || segments_a[i + 1]?.[0].content;
90
+ const next_b = segment_b[j + 1].content || segments_b[i + 1]?.[0].content;
91
+
92
+ // `[...rest]/x` outranks `[...rest]`
93
+ if (a.type === 'rest' && b.type === 'rest') {
94
+ if (next_a && next_b) continue;
95
+ if (next_a) return -1;
96
+ if (next_b) return +1;
97
+ }
98
+
99
+ // `[...rest]/x` outranks `[required]` or `[required]/[required]`
100
+ // but not `[required]/x`
101
+ if (a.type === 'rest') {
102
+ return next_a && !next_b ? -1 : +1;
103
+ }
104
+
105
+ if (b.type === 'rest') {
106
+ return next_b && !next_a ? +1 : -1;
107
+ }
108
+
109
+ // part with matcher outranks one without
110
+ if (a.matched !== b.matched) {
111
+ return a.matched ? -1 : +1;
112
+ }
113
+
114
+ if (a.type !== b.type) {
115
+ // `[...rest]` has already been accounted for, so here
116
+ // we're comparing between `[required]` and `[[optional]]`
117
+ if (a.type === 'required') return -1;
118
+ if (b.type === 'required') return +1;
119
+ }
120
+ } else if (a.content !== b.content) {
121
+ // shallower path outranks deeper path
122
+ if (a === EMPTY) return -1;
123
+ if (b === EMPTY) return +1;
124
+
125
+ return sort_static(a.content, b.content);
126
+ }
127
+ }
128
+ }
129
+
130
+ return route_a.id < route_b.id ? +1 : -1;
131
+ });
132
+ }
133
+
134
+ /** @param {string} id */
135
+ function split_route_id(id) {
136
+ return get_route_segments(
137
+ id
138
+ // remove all [[optional]] parts unless they're at the very end
139
+ .replace(/\[\[[^\]]+\]\](?!$)/g, '')
140
+ ).filter(Boolean);
141
+ }
142
+
143
+ /**
144
+ * Sort two strings lexicographically, except `foobar` outranks `foo`
145
+ * @param {string} a
146
+ * @param {string} b
147
+ */
148
+ function sort_static(a, b) {
149
+ if (a === b) return 0;
150
+
151
+ for (let i = 0; true; i += 1) {
152
+ const char_a = a[i];
153
+ const char_b = b[i];
154
+
155
+ if (char_a !== char_b) {
156
+ if (char_a === undefined) return +1;
157
+ if (char_b === undefined) return -1;
158
+ return char_a < char_b ? -1 : +1;
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,37 @@
1
+ import { PageNode } from 'types';
2
+
3
+ interface Part {
4
+ dynamic: boolean;
5
+ optional: boolean;
6
+ rest: boolean;
7
+ type: string | null;
8
+ }
9
+
10
+ interface RouteTreeNode {
11
+ error: PageNode | undefined;
12
+ layout: PageNode | undefined;
13
+ }
14
+
15
+ export type RouteTree = Map<string, RouteTreeNode>;
16
+
17
+ interface RouteComponent {
18
+ kind: 'component';
19
+ is_page: boolean;
20
+ is_layout: boolean;
21
+ is_error: boolean;
22
+ uses_layout: string | undefined;
23
+ }
24
+
25
+ interface RouteSharedModule {
26
+ kind: 'shared';
27
+ is_page: boolean;
28
+ is_layout: boolean;
29
+ }
30
+
31
+ interface RouteServerModule {
32
+ kind: 'server';
33
+ is_page: boolean;
34
+ is_layout: boolean;
35
+ }
36
+
37
+ export type RouteFile = RouteComponent | RouteSharedModule | RouteServerModule;