@@ -33,6 +33,10 @@ interface Item {
33
33
routeSuffix : string ;
34
34
}
35
35
36
+ interface ManifestRouteData extends RouteData {
37
+ isIndex : boolean ;
38
+ }
39
+
36
40
function countOccurrences ( needle : string , haystack : string ) {
37
41
let count = 0 ;
38
42
for ( const hay of haystack ) {
@@ -134,6 +138,40 @@ function validateSegment(segment: string, file = '') {
134
138
}
135
139
}
136
140
141
+ /**
142
+ * Checks whether two route segments are semantically equivalent.
143
+ *
144
+ * Two segments are equivalent if they would match the same paths. This happens when:
145
+ * - They have the same length.
146
+ * - Each part in the same position is either:
147
+ * - Both static and with the same content (e.g. `/foo` and `/foo`).
148
+ * - Both dynamic, regardless of the content (e.g. `/[bar]` and `/[baz]`).
149
+ * - Both rest parameters, regardless of the content (e.g. `/[...bar]` and `/[...baz]`).
150
+ */
151
+ function isSemanticallyEqualSegment ( segmentA : RoutePart [ ] , segmentB : RoutePart [ ] ) {
152
+ if ( segmentA . length !== segmentB . length ) {
153
+ return false ;
154
+ }
155
+
156
+ for ( const [ index , partA ] of segmentA . entries ( ) ) {
157
+ // Safe to use the index of one segment for the other because the segments have the same length
158
+ const partB = segmentB [ index ] ;
159
+
160
+ if ( partA . dynamic !== partB . dynamic || partA . spread !== partB . spread ) {
161
+ return false ;
162
+ }
163
+
164
+ // Only compare the content on non-dynamic segments
165
+ // `/[bar]` and `/[baz]` are effectively the same route,
166
+ // only bound to a different path parameter.
167
+ if ( ! partA . dynamic && partA . content !== partB . content ) {
168
+ return false ;
169
+ }
170
+ }
171
+
172
+ return true ;
173
+ }
174
+
137
175
/**
138
176
* Comparator for sorting routes in resolution order.
139
177
*
@@ -142,6 +180,8 @@ function validateSegment(segment: string, file = '') {
142
180
* - More specific routes are sorted before less specific routes. Here, "specific" means
143
181
* the number of segments in the route, so a parent route is always sorted after its children.
144
182
* For example, `/foo/bar` is sorted before `/foo`.
183
+ * Index routes, originating from a file named `index.astro`, are considered to have one more
184
+ * segment than the URL they represent.
145
185
* - Static routes are sorted before dynamic routes.
146
186
* For example, `/foo/bar` is sorted before `/foo/[bar]`.
147
187
* - Dynamic routes with single parameters are sorted before dynamic routes with rest parameters.
@@ -153,10 +193,14 @@ function validateSegment(segment: string, file = '') {
153
193
* For example, `/bar` is sorted before `/foo`.
154
194
* The definition of "alphabetically" is dependent on the default locale of the running system.
155
195
*/
156
- function routeComparator ( a : RouteData , b : RouteData ) {
196
+ function routeComparator ( a : ManifestRouteData , b : ManifestRouteData ) {
197
+ // For sorting purposes, an index route is considered to have one more segment than the URL it represents.
198
+ const aLength = a . isIndex ? a . segments . length + 1 : a . segments . length ;
199
+ const bLength = b . isIndex ? b . segments . length + 1 : b . segments . length ;
200
+
157
201
// Sort more specific routes before less specific routes
158
- if ( a . segments . length !== b . segments . length ) {
159
- return a . segments . length > b . segments . length ? - 1 : 1 ;
202
+ if ( aLength !== bLength ) {
203
+ return aLength > bLength ? - 1 : 1 ;
160
204
}
161
205
162
206
const aIsStatic = a . segments . every ( ( segment ) =>
@@ -206,9 +250,9 @@ export interface CreateRouteManifestParams {
206
250
function createFileBasedRoutes (
207
251
{ settings, cwd, fsMod } : CreateRouteManifestParams ,
208
252
logger : Logger
209
- ) : RouteData [ ] {
253
+ ) : ManifestRouteData [ ] {
210
254
const components : string [ ] = [ ] ;
211
- const routes : RouteData [ ] = [ ] ;
255
+ const routes : ManifestRouteData [ ] = [ ] ;
212
256
const validPageExtensions = new Set < string > ( [
213
257
'.astro' ,
214
258
...SUPPORTED_MARKDOWN_FILE_EXTENSIONS ,
@@ -321,6 +365,7 @@ function createFileBasedRoutes(
321
365
. join ( '/' ) } `. toLowerCase ( ) ;
322
366
routes . push ( {
323
367
route,
368
+ isIndex : item . isIndex ,
324
369
type : item . isPage ? 'page' : 'endpoint' ,
325
370
pattern,
326
371
segments,
@@ -348,7 +393,7 @@ function createFileBasedRoutes(
348
393
return routes ;
349
394
}
350
395
351
- type PrioritizedRoutesData = Record < RoutePriorityOverride , RouteData [ ] > ;
396
+ type PrioritizedRoutesData = Record < RoutePriorityOverride , ManifestRouteData [ ] > ;
352
397
353
398
function createInjectedRoutes ( { settings, cwd } : CreateRouteManifestParams ) : PrioritizedRoutesData {
354
399
const { config } = settings ;
@@ -398,6 +443,8 @@ function createInjectedRoutes({ settings, cwd }: CreateRouteManifestParams): Pri
398
443
399
444
routes [ priority ] . push ( {
400
445
type,
446
+ // For backwards compatibility, an injected route is never considered an index route.
447
+ isIndex : false ,
401
448
route,
402
449
pattern,
403
450
segments,
@@ -468,6 +515,8 @@ function createRedirectRoutes(
468
515
469
516
routes [ priority ] . push ( {
470
517
type : 'redirect' ,
518
+ // For backwards compatibility, a redirect is never considered an index route.
519
+ isIndex : false ,
471
520
route,
472
521
pattern,
473
522
segments,
@@ -492,40 +541,6 @@ function isStaticSegment(segment: RoutePart[]) {
492
541
return segment . every ( ( part ) => ! part . dynamic && ! part . spread ) ;
493
542
}
494
543
495
- /**
496
- * Checks whether two route segments are semantically equivalent.
497
- *
498
- * Two segments are equivalent if they would match the same paths. This happens when:
499
- * - They have the same length.
500
- * - Each part in the same position is either:
501
- * - Both static and with the same content (e.g. `/foo` and `/foo`).
502
- * - Both dynamic, regardless of the content (e.g. `/[bar]` and `/[baz]`).
503
- * - Both rest parameters, regardless of the content (e.g. `/[...bar]` and `/[...baz]`).
504
- */
505
- function isSemanticallyEqualSegment ( segmentA : RoutePart [ ] , segmentB : RoutePart [ ] ) {
506
- if ( segmentA . length !== segmentB . length ) {
507
- return false ;
508
- }
509
-
510
- for ( const [ index , partA ] of segmentA . entries ( ) ) {
511
- // Safe to use the index of one segment for the other because the segments have the same length
512
- const partB = segmentB [ index ] ;
513
-
514
- if ( partA . dynamic !== partB . dynamic || partA . spread !== partB . spread ) {
515
- return false ;
516
- }
517
-
518
- // Only compare the content on non-dynamic segments
519
- // `/[bar]` and `/[baz]` are effectively the same route,
520
- // only bound to a different path parameter.
521
- if ( ! partA . dynamic && partA . content !== partB . content ) {
522
- return false ;
523
- }
524
- }
525
-
526
- return true ;
527
- }
528
-
529
544
/**
530
545
* Check whether two are sure to collide in clearly unintended ways report appropriately.
531
546
*
@@ -624,7 +639,7 @@ export function createRouteManifest(
624
639
625
640
const redirectRoutes = createRedirectRoutes ( params , routeMap , logger ) ;
626
641
627
- const routes : RouteData [ ] = [
642
+ const routes : ManifestRouteData [ ] = [
628
643
...injectedRoutes [ 'legacy' ] . sort ( routeComparator ) ,
629
644
...[ ...fileBasedRoutes , ...injectedRoutes [ 'normal' ] , ...redirectRoutes [ 'normal' ] ] . sort (
630
645
routeComparator
@@ -660,8 +675,8 @@ export function createRouteManifest(
660
675
661
676
// In this block of code we group routes based on their locale
662
677
663
- // A map like: locale => RouteData []
664
- const routesByLocale = new Map < string , RouteData [ ] > ( ) ;
678
+ // A map like: locale => ManifestRouteData []
679
+ const routesByLocale = new Map < string , ManifestRouteData [ ] > ( ) ;
665
680
// This type is here only as a helper. We copy the routes and make them unique, so we don't "process" the same route twice.
666
681
// The assumption is that a route in the file system belongs to only one locale.
667
682
const setRoutes = new Set ( routes . filter ( ( route ) => route . type === 'page' ) ) ;
0 commit comments