@angular/ssr 19.2.0-next.0 → 19.2.0-next.2
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/fesm2022/node.mjs +4 -1
- package/fesm2022/node.mjs.map +1 -1
- package/fesm2022/ssr.mjs +286 -153
- package/fesm2022/ssr.mjs.map +1 -1
- package/index.d.ts +68 -30
- package/package.json +10 -8
- package/third_party/beasties/index.js +80 -78
- package/third_party/beasties/index.js.map +1 -1
package/fesm2022/ssr.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
|
|
2
2
|
import { ɵConsole as _Console, InjectionToken, makeEnvironmentProviders, runInInjectionContext, ɵENABLE_ROOT_COMPONENT_BOOTSTRAP as _ENABLE_ROOT_COMPONENT_BOOTSTRAP, ApplicationRef, Compiler, REQUEST, REQUEST_CONTEXT, RESPONSE_INIT, LOCALE_ID, ɵresetCompiledComponents as _resetCompiledComponents } from '@angular/core';
|
|
3
3
|
import { ɵSERVER_CONTEXT as _SERVER_CONTEXT, renderModule, renderApplication, platformServer, INITIAL_CONFIG } from '@angular/platform-server';
|
|
4
|
-
import { ɵloadChildren as _loadChildren, Router } from '@angular/router';
|
|
4
|
+
import { ROUTES, ɵloadChildren as _loadChildren, Router } from '@angular/router';
|
|
5
5
|
import Beasties from '../third_party/beasties/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -399,9 +399,23 @@ function promiseWithAbort(promise, signal, errorMessagePrefix) {
|
|
|
399
399
|
});
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
/**
|
|
403
|
+
* The internal path used for the app shell route.
|
|
404
|
+
* @internal
|
|
405
|
+
*/
|
|
406
|
+
const APP_SHELL_ROUTE = 'ng-app-shell';
|
|
407
|
+
/**
|
|
408
|
+
* Identifies a particular kind of `ServerRoutesFeatureKind`.
|
|
409
|
+
* @see {@link ServerRoutesFeature}
|
|
410
|
+
* @developerPreview
|
|
411
|
+
*/
|
|
412
|
+
var ServerRoutesFeatureKind;
|
|
413
|
+
(function (ServerRoutesFeatureKind) {
|
|
414
|
+
ServerRoutesFeatureKind[ServerRoutesFeatureKind["AppShell"] = 0] = "AppShell";
|
|
415
|
+
})(ServerRoutesFeatureKind || (ServerRoutesFeatureKind = {}));
|
|
402
416
|
/**
|
|
403
417
|
* Different rendering modes for server routes.
|
|
404
|
-
* @see {@link
|
|
418
|
+
* @see {@link provideServerRouting}
|
|
405
419
|
* @see {@link ServerRoute}
|
|
406
420
|
* @developerPreview
|
|
407
421
|
*/
|
|
@@ -455,6 +469,8 @@ const SERVER_ROUTES_CONFIG = new InjectionToken('SERVER_ROUTES_CONFIG');
|
|
|
455
469
|
*
|
|
456
470
|
* @see {@link ServerRoute}
|
|
457
471
|
* @see {@link ServerRoutesConfigOptions}
|
|
472
|
+
* @see {@link provideServerRouting}
|
|
473
|
+
* @deprecated use `provideServerRouting`. This will be removed in version 20.
|
|
458
474
|
* @developerPreview
|
|
459
475
|
*/
|
|
460
476
|
function provideServerRoutesConfig(routes, options) {
|
|
@@ -468,6 +484,75 @@ function provideServerRoutesConfig(routes, options) {
|
|
|
468
484
|
},
|
|
469
485
|
]);
|
|
470
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Sets up the necessary providers for configuring server routes.
|
|
489
|
+
* This function accepts an array of server routes and optional configuration
|
|
490
|
+
* options, returning an `EnvironmentProviders` object that encapsulates
|
|
491
|
+
* the server routes and configuration settings.
|
|
492
|
+
*
|
|
493
|
+
* @param routes - An array of server routes to be provided.
|
|
494
|
+
* @param features - (Optional) server routes features.
|
|
495
|
+
* @returns An `EnvironmentProviders` instance with the server routes configuration.
|
|
496
|
+
*
|
|
497
|
+
* @see {@link ServerRoute}
|
|
498
|
+
* @see {@link withAppShell}
|
|
499
|
+
* @developerPreview
|
|
500
|
+
*/
|
|
501
|
+
function provideServerRouting(routes, ...features) {
|
|
502
|
+
const config = { routes };
|
|
503
|
+
const hasAppShell = features.some((f) => f.ɵkind === ServerRoutesFeatureKind.AppShell);
|
|
504
|
+
if (hasAppShell) {
|
|
505
|
+
config.appShellRoute = APP_SHELL_ROUTE;
|
|
506
|
+
}
|
|
507
|
+
const providers = [
|
|
508
|
+
{
|
|
509
|
+
provide: SERVER_ROUTES_CONFIG,
|
|
510
|
+
useValue: config,
|
|
511
|
+
},
|
|
512
|
+
];
|
|
513
|
+
for (const feature of features) {
|
|
514
|
+
providers.push(...feature.ɵproviders);
|
|
515
|
+
}
|
|
516
|
+
return makeEnvironmentProviders(providers);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Configures the app shell route with the provided component.
|
|
520
|
+
*
|
|
521
|
+
* The app shell serves as the main entry point for the application and is commonly used
|
|
522
|
+
* to enable server-side rendering (SSR) of the application shell. It handles requests
|
|
523
|
+
* that do not match any specific server route, providing a fallback mechanism and improving
|
|
524
|
+
* perceived performance during navigation.
|
|
525
|
+
*
|
|
526
|
+
* This configuration is particularly useful in applications leveraging Progressive Web App (PWA)
|
|
527
|
+
* patterns, such as service workers, to deliver a seamless user experience.
|
|
528
|
+
*
|
|
529
|
+
* @param component The Angular component to render for the app shell route.
|
|
530
|
+
* @returns A server routes feature configuration for the app shell.
|
|
531
|
+
*
|
|
532
|
+
* @see {@link provideServerRouting}
|
|
533
|
+
* @see {@link https://angular.dev/ecosystem/service-workers/app-shell | App shell pattern on Angular.dev}
|
|
534
|
+
*/
|
|
535
|
+
function withAppShell(component) {
|
|
536
|
+
const routeConfig = {
|
|
537
|
+
path: APP_SHELL_ROUTE,
|
|
538
|
+
};
|
|
539
|
+
if ('ɵcmp' in component) {
|
|
540
|
+
routeConfig.component = component;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
routeConfig.loadComponent = component;
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
ɵkind: ServerRoutesFeatureKind.AppShell,
|
|
547
|
+
ɵproviders: [
|
|
548
|
+
{
|
|
549
|
+
provide: ROUTES,
|
|
550
|
+
useValue: routeConfig,
|
|
551
|
+
multi: true,
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
};
|
|
555
|
+
}
|
|
471
556
|
|
|
472
557
|
/**
|
|
473
558
|
* A route tree implementation that supports efficient route matching, including support for wildcard routes.
|
|
@@ -482,12 +567,6 @@ class RouteTree {
|
|
|
482
567
|
* All routes are stored and accessed relative to this root node.
|
|
483
568
|
*/
|
|
484
569
|
root = this.createEmptyRouteTreeNode();
|
|
485
|
-
/**
|
|
486
|
-
* A counter that tracks the order of route insertion.
|
|
487
|
-
* This ensures that routes are matched in the order they were defined,
|
|
488
|
-
* with earlier routes taking precedence.
|
|
489
|
-
*/
|
|
490
|
-
insertionIndexCounter = 0;
|
|
491
570
|
/**
|
|
492
571
|
* Inserts a new route into the route tree.
|
|
493
572
|
* The route is broken down into segments, and each segment is added to the tree.
|
|
@@ -516,7 +595,6 @@ class RouteTree {
|
|
|
516
595
|
...metadata,
|
|
517
596
|
route: addLeadingSlash(normalizedSegments.join('/')),
|
|
518
597
|
};
|
|
519
|
-
node.insertionIndex = this.insertionIndexCounter++;
|
|
520
598
|
}
|
|
521
599
|
/**
|
|
522
600
|
* Matches a given route against the route tree and returns the best matching route's metadata.
|
|
@@ -579,7 +657,7 @@ class RouteTree {
|
|
|
579
657
|
* @returns An array of path segments.
|
|
580
658
|
*/
|
|
581
659
|
getPathSegments(route) {
|
|
582
|
-
return
|
|
660
|
+
return route.split('/').filter(Boolean);
|
|
583
661
|
}
|
|
584
662
|
/**
|
|
585
663
|
* Recursively traverses the route tree from a given node, attempting to match the remaining route segments.
|
|
@@ -588,52 +666,39 @@ class RouteTree {
|
|
|
588
666
|
* This function prioritizes exact segment matches first, followed by wildcard matches (`*`),
|
|
589
667
|
* and finally deep wildcard matches (`**`) that consume all segments.
|
|
590
668
|
*
|
|
591
|
-
* @param
|
|
592
|
-
* @param node - The current node in the route tree to start traversal from.
|
|
669
|
+
* @param segments - The array of route path segments to match against the route tree.
|
|
670
|
+
* @param node - The current node in the route tree to start traversal from. Defaults to the root node.
|
|
671
|
+
* @param currentIndex - The index of the segment in `remainingSegments` currently being matched.
|
|
672
|
+
* Defaults to `0` (the first segment).
|
|
593
673
|
*
|
|
594
674
|
* @returns The node that best matches the remaining segments or `undefined` if no match is found.
|
|
595
675
|
*/
|
|
596
|
-
traverseBySegments(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (!remainingSegments.length) {
|
|
600
|
-
return metadata ? node : node.children.get('**');
|
|
601
|
-
}
|
|
602
|
-
// If the node has no children, end the traversal
|
|
603
|
-
if (!children.size) {
|
|
604
|
-
return;
|
|
676
|
+
traverseBySegments(segments, node = this.root, currentIndex = 0) {
|
|
677
|
+
if (currentIndex >= segments.length) {
|
|
678
|
+
return node.metadata ? node : node.children.get('**');
|
|
605
679
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
// 1. Exact segment match
|
|
609
|
-
const exactMatchNode = node.children.get(segment);
|
|
610
|
-
currentBestMatchNode = this.getHigherPriorityNode(currentBestMatchNode, this.traverseBySegments(restSegments, exactMatchNode));
|
|
611
|
-
// 2. Wildcard segment match (`*`)
|
|
612
|
-
const wildcardNode = node.children.get('*');
|
|
613
|
-
currentBestMatchNode = this.getHigherPriorityNode(currentBestMatchNode, this.traverseBySegments(restSegments, wildcardNode));
|
|
614
|
-
// 3. Deep wildcard segment match (`**`)
|
|
615
|
-
const deepWildcardNode = node.children.get('**');
|
|
616
|
-
currentBestMatchNode = this.getHigherPriorityNode(currentBestMatchNode, deepWildcardNode);
|
|
617
|
-
return currentBestMatchNode;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Compares two nodes and returns the node with higher priority based on insertion index.
|
|
621
|
-
* A node with a lower insertion index is prioritized as it was defined earlier.
|
|
622
|
-
*
|
|
623
|
-
* @param currentBestMatchNode - The current best match node.
|
|
624
|
-
* @param candidateNode - The node being evaluated for higher priority based on insertion index.
|
|
625
|
-
* @returns The node with higher priority (i.e., lower insertion index). If one of the nodes is `undefined`, the other node is returned.
|
|
626
|
-
*/
|
|
627
|
-
getHigherPriorityNode(currentBestMatchNode, candidateNode) {
|
|
628
|
-
if (!candidateNode) {
|
|
629
|
-
return currentBestMatchNode;
|
|
680
|
+
if (!node.children.size) {
|
|
681
|
+
return undefined;
|
|
630
682
|
}
|
|
631
|
-
|
|
632
|
-
|
|
683
|
+
const segment = segments[currentIndex];
|
|
684
|
+
// 1. Attempt exact match with the current segment.
|
|
685
|
+
const exactMatch = node.children.get(segment);
|
|
686
|
+
if (exactMatch) {
|
|
687
|
+
const match = this.traverseBySegments(segments, exactMatch, currentIndex + 1);
|
|
688
|
+
if (match) {
|
|
689
|
+
return match;
|
|
690
|
+
}
|
|
633
691
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
692
|
+
// 2. Attempt wildcard match ('*').
|
|
693
|
+
const wildcardMatch = node.children.get('*');
|
|
694
|
+
if (wildcardMatch) {
|
|
695
|
+
const match = this.traverseBySegments(segments, wildcardMatch, currentIndex + 1);
|
|
696
|
+
if (match) {
|
|
697
|
+
return match;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// 3. Attempt double wildcard match ('**').
|
|
701
|
+
return node.children.get('**');
|
|
637
702
|
}
|
|
638
703
|
/**
|
|
639
704
|
* Creates an empty route tree node.
|
|
@@ -643,7 +708,6 @@ class RouteTree {
|
|
|
643
708
|
*/
|
|
644
709
|
createEmptyRouteTreeNode() {
|
|
645
710
|
return {
|
|
646
|
-
insertionIndex: -1,
|
|
647
711
|
children: new Map(),
|
|
648
712
|
};
|
|
649
713
|
}
|
|
@@ -662,6 +726,73 @@ const URL_PARAMETER_REGEXP = /(?<!\\):([^/]+)/g;
|
|
|
662
726
|
* An set of HTTP status codes that are considered valid for redirect responses.
|
|
663
727
|
*/
|
|
664
728
|
const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
|
|
729
|
+
/**
|
|
730
|
+
* Handles a single route within the route tree and yields metadata or errors.
|
|
731
|
+
*
|
|
732
|
+
* @param options - Configuration options for handling the route.
|
|
733
|
+
* @returns An async iterable iterator yielding `RouteTreeNodeMetadata` or an error object.
|
|
734
|
+
*/
|
|
735
|
+
async function* handleRoute(options) {
|
|
736
|
+
try {
|
|
737
|
+
const { metadata, currentRoutePath, route, compiler, parentInjector, serverConfigRouteTree, entryPointToBrowserMapping, invokeGetPrerenderParams, includePrerenderFallbackRoutes, } = options;
|
|
738
|
+
const { redirectTo, loadChildren, loadComponent, children, ɵentryName } = route;
|
|
739
|
+
if (ɵentryName && loadComponent) {
|
|
740
|
+
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, true);
|
|
741
|
+
}
|
|
742
|
+
if (metadata.renderMode === RenderMode.Prerender) {
|
|
743
|
+
yield* handleSSGRoute(serverConfigRouteTree, typeof redirectTo === 'string' ? redirectTo : undefined, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes);
|
|
744
|
+
}
|
|
745
|
+
else if (typeof redirectTo === 'string') {
|
|
746
|
+
if (metadata.status && !VALID_REDIRECT_RESPONSE_CODES.has(metadata.status)) {
|
|
747
|
+
yield {
|
|
748
|
+
error: `The '${metadata.status}' status code is not a valid redirect response code. ` +
|
|
749
|
+
`Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
yield { ...metadata, redirectTo: resolveRedirectTo(metadata.route, redirectTo) };
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
yield metadata;
|
|
758
|
+
}
|
|
759
|
+
// Recursively process child routes
|
|
760
|
+
if (children?.length) {
|
|
761
|
+
yield* traverseRoutesConfig({
|
|
762
|
+
...options,
|
|
763
|
+
routes: children,
|
|
764
|
+
parentRoute: currentRoutePath,
|
|
765
|
+
parentPreloads: metadata.preload,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
// Load and process lazy-loaded child routes
|
|
769
|
+
if (loadChildren) {
|
|
770
|
+
if (ɵentryName) {
|
|
771
|
+
// When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
|
|
772
|
+
// As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
|
|
773
|
+
// across different child routes. In contrast, `loadComponent` only loads a single component, which allows
|
|
774
|
+
// for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
|
|
775
|
+
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, false);
|
|
776
|
+
}
|
|
777
|
+
const loadedChildRoutes = await _loadChildren(route, compiler, parentInjector).toPromise();
|
|
778
|
+
if (loadedChildRoutes) {
|
|
779
|
+
const { routes: childRoutes, injector = parentInjector } = loadedChildRoutes;
|
|
780
|
+
yield* traverseRoutesConfig({
|
|
781
|
+
...options,
|
|
782
|
+
routes: childRoutes,
|
|
783
|
+
parentInjector: injector,
|
|
784
|
+
parentRoute: currentRoutePath,
|
|
785
|
+
parentPreloads: metadata.preload,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
yield {
|
|
792
|
+
error: `Error in handleRoute for '${options.currentRoutePath}': ${error.message}`,
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
665
796
|
/**
|
|
666
797
|
* Traverses an array of route configurations to generate route tree node metadata.
|
|
667
798
|
*
|
|
@@ -672,32 +803,60 @@ const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
|
|
|
672
803
|
* @returns An async iterable iterator yielding either route tree node metadata or an error object with an error message.
|
|
673
804
|
*/
|
|
674
805
|
async function* traverseRoutesConfig(options) {
|
|
675
|
-
const { routes
|
|
676
|
-
for (const route of
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (matcher) {
|
|
684
|
-
// Only issue this error when SSR routing is used.
|
|
685
|
-
yield {
|
|
686
|
-
error: `The route '${stripLeadingSlash(currentRoutePath)}' uses a route matcher that is not supported.`,
|
|
687
|
-
};
|
|
806
|
+
const { routes: routeConfigs, parentPreloads, parentRoute, serverConfigRouteTree } = options;
|
|
807
|
+
for (const route of routeConfigs) {
|
|
808
|
+
const { matcher, path = matcher ? '**' : '' } = route;
|
|
809
|
+
const currentRoutePath = joinUrlParts(parentRoute, path);
|
|
810
|
+
if (matcher && serverConfigRouteTree) {
|
|
811
|
+
let foundMatch = false;
|
|
812
|
+
for (const matchedMetaData of serverConfigRouteTree.traverse()) {
|
|
813
|
+
if (!matchedMetaData.route.startsWith(currentRoutePath)) {
|
|
688
814
|
continue;
|
|
689
815
|
}
|
|
690
|
-
|
|
691
|
-
|
|
816
|
+
foundMatch = true;
|
|
817
|
+
matchedMetaData.presentInClientRouter = true;
|
|
818
|
+
if (matchedMetaData.renderMode === RenderMode.Prerender) {
|
|
692
819
|
yield {
|
|
693
|
-
error: `The '${stripLeadingSlash(currentRoutePath)}'
|
|
694
|
-
|
|
820
|
+
error: `The route '${stripLeadingSlash(currentRoutePath)}' is set for prerendering but has a defined matcher. ` +
|
|
821
|
+
`Routes with matchers cannot use prerendering. Please specify a different 'renderMode'.`,
|
|
695
822
|
};
|
|
696
823
|
continue;
|
|
697
824
|
}
|
|
698
|
-
|
|
825
|
+
yield* handleRoute({
|
|
826
|
+
...options,
|
|
827
|
+
currentRoutePath,
|
|
828
|
+
route,
|
|
829
|
+
metadata: {
|
|
830
|
+
...matchedMetaData,
|
|
831
|
+
preload: parentPreloads,
|
|
832
|
+
route: matchedMetaData.route,
|
|
833
|
+
presentInClientRouter: undefined,
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
if (!foundMatch) {
|
|
838
|
+
yield {
|
|
839
|
+
error: `The route '${stripLeadingSlash(currentRoutePath)}' has a defined matcher but does not ` +
|
|
840
|
+
'match any route in the server routing configuration. Please ensure this route is added to the server routing configuration.',
|
|
841
|
+
};
|
|
699
842
|
}
|
|
700
|
-
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
let matchedMetaData;
|
|
846
|
+
if (serverConfigRouteTree) {
|
|
847
|
+
matchedMetaData = serverConfigRouteTree.match(currentRoutePath);
|
|
848
|
+
if (!matchedMetaData) {
|
|
849
|
+
yield {
|
|
850
|
+
error: `The '${stripLeadingSlash(currentRoutePath)}' route does not match any route defined in the server routing configuration. ` +
|
|
851
|
+
'Please ensure this route is added to the server routing configuration.',
|
|
852
|
+
};
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
matchedMetaData.presentInClientRouter = true;
|
|
856
|
+
}
|
|
857
|
+
yield* handleRoute({
|
|
858
|
+
...options,
|
|
859
|
+
metadata: {
|
|
701
860
|
renderMode: RenderMode.Prerender,
|
|
702
861
|
...matchedMetaData,
|
|
703
862
|
preload: parentPreloads,
|
|
@@ -706,64 +865,10 @@ async function* traverseRoutesConfig(options) {
|
|
|
706
865
|
// ['one', 'two', 'three'] -> 'one/two/three'
|
|
707
866
|
route: path === '' ? addTrailingSlash(currentRoutePath) : currentRoutePath,
|
|
708
867
|
presentInClientRouter: undefined,
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (metadata.renderMode === RenderMode.Prerender) {
|
|
714
|
-
// Handle SSG routes
|
|
715
|
-
yield* handleSSGRoute(typeof redirectTo === 'string' ? redirectTo : undefined, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes);
|
|
716
|
-
}
|
|
717
|
-
else if (typeof redirectTo === 'string') {
|
|
718
|
-
// Handle redirects
|
|
719
|
-
if (metadata.status && !VALID_REDIRECT_RESPONSE_CODES.has(metadata.status)) {
|
|
720
|
-
yield {
|
|
721
|
-
error: `The '${metadata.status}' status code is not a valid redirect response code. ` +
|
|
722
|
-
`Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,
|
|
723
|
-
};
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
yield { ...metadata, redirectTo: resolveRedirectTo(metadata.route, redirectTo) };
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
yield metadata;
|
|
730
|
-
}
|
|
731
|
-
// Recursively process child routes
|
|
732
|
-
if (children?.length) {
|
|
733
|
-
yield* traverseRoutesConfig({
|
|
734
|
-
...options,
|
|
735
|
-
routes: children,
|
|
736
|
-
parentRoute: currentRoutePath,
|
|
737
|
-
parentPreloads: metadata.preload,
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
// Load and process lazy-loaded child routes
|
|
741
|
-
if (loadChildren) {
|
|
742
|
-
if (ɵentryName) {
|
|
743
|
-
// When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
|
|
744
|
-
// As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
|
|
745
|
-
// across different child routes. In contrast, `loadComponent` only loads a single component, which allows
|
|
746
|
-
// for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
|
|
747
|
-
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, false);
|
|
748
|
-
}
|
|
749
|
-
const loadedChildRoutes = await _loadChildren(route, compiler, parentInjector).toPromise();
|
|
750
|
-
if (loadedChildRoutes) {
|
|
751
|
-
const { routes: childRoutes, injector = parentInjector } = loadedChildRoutes;
|
|
752
|
-
yield* traverseRoutesConfig({
|
|
753
|
-
...options,
|
|
754
|
-
routes: childRoutes,
|
|
755
|
-
parentInjector: injector,
|
|
756
|
-
parentRoute: currentRoutePath,
|
|
757
|
-
parentPreloads: metadata.preload,
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
catch (error) {
|
|
763
|
-
yield {
|
|
764
|
-
error: `Error processing route '${stripLeadingSlash(route.path ?? '')}': ${error.message}`,
|
|
765
|
-
};
|
|
766
|
-
}
|
|
868
|
+
},
|
|
869
|
+
currentRoutePath,
|
|
870
|
+
route,
|
|
871
|
+
});
|
|
767
872
|
}
|
|
768
873
|
}
|
|
769
874
|
/**
|
|
@@ -774,23 +879,32 @@ async function* traverseRoutesConfig(options) {
|
|
|
774
879
|
* preloads to a predefined maximum.
|
|
775
880
|
*/
|
|
776
881
|
function appendPreloadToMetadata(entryName, entryPointToBrowserMapping, metadata, includeDynamicImports) {
|
|
777
|
-
|
|
882
|
+
const existingPreloads = metadata.preload ?? [];
|
|
883
|
+
if (!entryPointToBrowserMapping || existingPreloads.length >= MODULE_PRELOAD_MAX) {
|
|
778
884
|
return;
|
|
779
885
|
}
|
|
780
886
|
const preload = entryPointToBrowserMapping[entryName];
|
|
781
|
-
if (preload?.length) {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
887
|
+
if (!preload?.length) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
// Merge existing preloads with new ones, ensuring uniqueness and limiting the total to the maximum allowed.
|
|
891
|
+
const combinedPreloads = new Set(existingPreloads);
|
|
892
|
+
for (const { dynamicImport, path } of preload) {
|
|
893
|
+
if (dynamicImport && !includeDynamicImports) {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
combinedPreloads.add(path);
|
|
897
|
+
if (combinedPreloads.size === MODULE_PRELOAD_MAX) {
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
788
900
|
}
|
|
901
|
+
metadata.preload = Array.from(combinedPreloads);
|
|
789
902
|
}
|
|
790
903
|
/**
|
|
791
904
|
* Handles SSG (Static Site Generation) routes by invoking `getPrerenderParams` and yielding
|
|
792
905
|
* all parameterized paths, returning any errors encountered.
|
|
793
906
|
*
|
|
907
|
+
* @param serverConfigRouteTree - The tree representing the server's routing setup.
|
|
794
908
|
* @param redirectTo - Optional path to redirect to, if specified.
|
|
795
909
|
* @param metadata - The metadata associated with the route tree node.
|
|
796
910
|
* @param parentInjector - The dependency injection container for the parent route.
|
|
@@ -798,7 +912,7 @@ function appendPreloadToMetadata(entryName, entryPointToBrowserMapping, metadata
|
|
|
798
912
|
* @param includePrerenderFallbackRoutes - A flag indicating whether to include fallback routes in the result.
|
|
799
913
|
* @returns An async iterable iterator that yields route tree node metadata for each SSG path or errors.
|
|
800
914
|
*/
|
|
801
|
-
async function* handleSSGRoute(redirectTo, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes) {
|
|
915
|
+
async function* handleSSGRoute(serverConfigRouteTree, redirectTo, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes) {
|
|
802
916
|
if (metadata.renderMode !== RenderMode.Prerender) {
|
|
803
917
|
throw new Error(`'handleSSGRoute' was called for a route which rendering mode is not prerender.`);
|
|
804
918
|
}
|
|
@@ -827,6 +941,18 @@ async function* handleSSGRoute(redirectTo, metadata, parentInjector, invokeGetPr
|
|
|
827
941
|
};
|
|
828
942
|
return;
|
|
829
943
|
}
|
|
944
|
+
if (serverConfigRouteTree) {
|
|
945
|
+
// Automatically resolve dynamic parameters for nested routes.
|
|
946
|
+
const catchAllRoutePath = joinUrlParts(currentRoutePath, '**');
|
|
947
|
+
const match = serverConfigRouteTree.match(catchAllRoutePath);
|
|
948
|
+
if (match && match.renderMode === RenderMode.Prerender && !('getPrerenderParams' in match)) {
|
|
949
|
+
serverConfigRouteTree.insert(catchAllRoutePath, {
|
|
950
|
+
...match,
|
|
951
|
+
presentInClientRouter: true,
|
|
952
|
+
getPrerenderParams,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}
|
|
830
956
|
const parameters = await runInInjectionContext(parentInjector, () => getPrerenderParams());
|
|
831
957
|
try {
|
|
832
958
|
for (const params of parameters) {
|
|
@@ -912,6 +1038,10 @@ function buildServerConfigRouteTree({ routes, appShellRoute }) {
|
|
|
912
1038
|
errors.push(`Invalid '${path}' route configuration: the path cannot start with a slash.`);
|
|
913
1039
|
continue;
|
|
914
1040
|
}
|
|
1041
|
+
if (path.includes('*') && 'getPrerenderParams' in metadata) {
|
|
1042
|
+
errors.push(`Invalid '${path}' route configuration: 'getPrerenderParams' cannot be used with a '*' or '**' route.`);
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
915
1045
|
serverConfigRouteTree.insert(path, metadata);
|
|
916
1046
|
}
|
|
917
1047
|
return { serverConfigRouteTree, errors };
|
|
@@ -973,13 +1103,10 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
973
1103
|
router.navigationTransitions.afterPreactivation()?.next?.();
|
|
974
1104
|
// Wait until the application is stable.
|
|
975
1105
|
await applicationRef.whenStable();
|
|
976
|
-
const routesResults = [];
|
|
977
1106
|
const errors = [];
|
|
978
|
-
|
|
1107
|
+
const rawBaseHref = injector.get(APP_BASE_HREF, null, { optional: true }) ??
|
|
979
1108
|
injector.get(PlatformLocation).getBaseHrefFromDOM();
|
|
980
|
-
|
|
981
|
-
baseHref = baseHref.slice(2);
|
|
982
|
-
}
|
|
1109
|
+
const { pathname: baseHref } = new URL(rawBaseHref, 'http://localhost');
|
|
983
1110
|
const compiler = injector.get(Compiler);
|
|
984
1111
|
const serverRoutesConfig = injector.get(SERVER_ROUTES_CONFIG, null, { optional: true });
|
|
985
1112
|
let serverConfigRouteTree;
|
|
@@ -991,10 +1118,11 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
991
1118
|
if (errors.length) {
|
|
992
1119
|
return {
|
|
993
1120
|
baseHref,
|
|
994
|
-
routes:
|
|
1121
|
+
routes: [],
|
|
995
1122
|
errors,
|
|
996
1123
|
};
|
|
997
1124
|
}
|
|
1125
|
+
const routesResults = [];
|
|
998
1126
|
if (router.config.length) {
|
|
999
1127
|
// Retrieve all routes from the Angular router configuration.
|
|
1000
1128
|
const traverseRoutes = traverseRoutesConfig({
|
|
@@ -1007,12 +1135,18 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
1007
1135
|
includePrerenderFallbackRoutes,
|
|
1008
1136
|
entryPointToBrowserMapping,
|
|
1009
1137
|
});
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1138
|
+
const seenRoutes = new Set();
|
|
1139
|
+
for await (const routeMetadata of traverseRoutes) {
|
|
1140
|
+
if ('error' in routeMetadata) {
|
|
1141
|
+
errors.push(routeMetadata.error);
|
|
1142
|
+
continue;
|
|
1013
1143
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1144
|
+
// If a result already exists for the exact same route, subsequent matches should be ignored.
|
|
1145
|
+
// This aligns with Angular's app router behavior, which prioritizes the first route.
|
|
1146
|
+
const routePath = routeMetadata.route;
|
|
1147
|
+
if (!seenRoutes.has(routePath)) {
|
|
1148
|
+
routesResults.push(routeMetadata);
|
|
1149
|
+
seenRoutes.add(routePath);
|
|
1016
1150
|
}
|
|
1017
1151
|
}
|
|
1018
1152
|
// This timeout is necessary to prevent 'adev' from hanging in production builds.
|
|
@@ -2154,8 +2288,8 @@ class AngularAppEngine {
|
|
|
2154
2288
|
redirectBasedOnAcceptLanguage(request) {
|
|
2155
2289
|
const { basePath, supportedLocales } = this.manifest;
|
|
2156
2290
|
// If the request is not for the base path, it's not our responsibility to handle it.
|
|
2157
|
-
const
|
|
2158
|
-
if (
|
|
2291
|
+
const { pathname } = new URL(request.url);
|
|
2292
|
+
if (pathname !== basePath) {
|
|
2159
2293
|
return null;
|
|
2160
2294
|
}
|
|
2161
2295
|
// For requests to the base path (typically '/'), attempt to extract the preferred locale
|
|
@@ -2164,11 +2298,10 @@ class AngularAppEngine {
|
|
|
2164
2298
|
if (preferredLocale) {
|
|
2165
2299
|
const subPath = supportedLocales[preferredLocale];
|
|
2166
2300
|
if (subPath !== undefined) {
|
|
2167
|
-
url.pathname = joinUrlParts(url.pathname, subPath);
|
|
2168
2301
|
return new Response(null, {
|
|
2169
2302
|
status: 302, // Use a 302 redirect as language preference may change.
|
|
2170
2303
|
headers: {
|
|
2171
|
-
'Location':
|
|
2304
|
+
'Location': joinUrlParts(pathname, subPath),
|
|
2172
2305
|
'Vary': 'Accept-Language',
|
|
2173
2306
|
},
|
|
2174
2307
|
});
|
|
@@ -2272,5 +2405,5 @@ function createRequestHandler(handler) {
|
|
|
2272
2405
|
return handler;
|
|
2273
2406
|
}
|
|
2274
2407
|
|
|
2275
|
-
export { AngularAppEngine, PrerenderFallback, RenderMode, createRequestHandler, provideServerRoutesConfig, InlineCriticalCssProcessor as ɵInlineCriticalCssProcessor, destroyAngularServerApp as ɵdestroyAngularServerApp, extractRoutesAndCreateRouteTree as ɵextractRoutesAndCreateRouteTree, getOrCreateAngularServerApp as ɵgetOrCreateAngularServerApp, getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, setAngularAppManifest as ɵsetAngularAppManifest };
|
|
2408
|
+
export { AngularAppEngine, PrerenderFallback, RenderMode, createRequestHandler, provideServerRoutesConfig, provideServerRouting, withAppShell, InlineCriticalCssProcessor as ɵInlineCriticalCssProcessor, destroyAngularServerApp as ɵdestroyAngularServerApp, extractRoutesAndCreateRouteTree as ɵextractRoutesAndCreateRouteTree, getOrCreateAngularServerApp as ɵgetOrCreateAngularServerApp, getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, setAngularAppManifest as ɵsetAngularAppManifest };
|
|
2276
2409
|
//# sourceMappingURL=ssr.mjs.map
|