@sveltejs/kit 1.0.0-next.52 → 1.0.0-next.520

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