@angular/ssr 19.2.0-next.1 → 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/ssr.mjs +149 -96
- package/fesm2022/ssr.mjs.map +1 -1
- package/package.json +7 -7
- package/third_party/beasties/index.js +80 -78
- package/third_party/beasties/index.js.map +1 -1
package/fesm2022/ssr.mjs
CHANGED
|
@@ -726,6 +726,73 @@ const URL_PARAMETER_REGEXP = /(?<!\\):([^/]+)/g;
|
|
|
726
726
|
* An set of HTTP status codes that are considered valid for redirect responses.
|
|
727
727
|
*/
|
|
728
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
|
+
}
|
|
729
796
|
/**
|
|
730
797
|
* Traverses an array of route configurations to generate route tree node metadata.
|
|
731
798
|
*
|
|
@@ -736,32 +803,60 @@ const VALID_REDIRECT_RESPONSE_CODES = new Set([301, 302, 303, 307, 308]);
|
|
|
736
803
|
* @returns An async iterable iterator yielding either route tree node metadata or an error object with an error message.
|
|
737
804
|
*/
|
|
738
805
|
async function* traverseRoutesConfig(options) {
|
|
739
|
-
const { routes
|
|
740
|
-
for (const route of
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (matcher) {
|
|
748
|
-
// Only issue this error when SSR routing is used.
|
|
749
|
-
yield {
|
|
750
|
-
error: `The route '${stripLeadingSlash(currentRoutePath)}' uses a route matcher that is not supported.`,
|
|
751
|
-
};
|
|
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)) {
|
|
752
814
|
continue;
|
|
753
815
|
}
|
|
754
|
-
|
|
755
|
-
|
|
816
|
+
foundMatch = true;
|
|
817
|
+
matchedMetaData.presentInClientRouter = true;
|
|
818
|
+
if (matchedMetaData.renderMode === RenderMode.Prerender) {
|
|
756
819
|
yield {
|
|
757
|
-
error: `The '${stripLeadingSlash(currentRoutePath)}'
|
|
758
|
-
|
|
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'.`,
|
|
759
822
|
};
|
|
760
823
|
continue;
|
|
761
824
|
}
|
|
762
|
-
|
|
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
|
+
};
|
|
842
|
+
}
|
|
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;
|
|
763
854
|
}
|
|
764
|
-
|
|
855
|
+
matchedMetaData.presentInClientRouter = true;
|
|
856
|
+
}
|
|
857
|
+
yield* handleRoute({
|
|
858
|
+
...options,
|
|
859
|
+
metadata: {
|
|
765
860
|
renderMode: RenderMode.Prerender,
|
|
766
861
|
...matchedMetaData,
|
|
767
862
|
preload: parentPreloads,
|
|
@@ -770,64 +865,10 @@ async function* traverseRoutesConfig(options) {
|
|
|
770
865
|
// ['one', 'two', 'three'] -> 'one/two/three'
|
|
771
866
|
route: path === '' ? addTrailingSlash(currentRoutePath) : currentRoutePath,
|
|
772
867
|
presentInClientRouter: undefined,
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (metadata.renderMode === RenderMode.Prerender) {
|
|
778
|
-
// Handle SSG routes
|
|
779
|
-
yield* handleSSGRoute(serverConfigRouteTree, typeof redirectTo === 'string' ? redirectTo : undefined, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes);
|
|
780
|
-
}
|
|
781
|
-
else if (typeof redirectTo === 'string') {
|
|
782
|
-
// Handle redirects
|
|
783
|
-
if (metadata.status && !VALID_REDIRECT_RESPONSE_CODES.has(metadata.status)) {
|
|
784
|
-
yield {
|
|
785
|
-
error: `The '${metadata.status}' status code is not a valid redirect response code. ` +
|
|
786
|
-
`Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`,
|
|
787
|
-
};
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
|
-
yield { ...metadata, redirectTo: resolveRedirectTo(metadata.route, redirectTo) };
|
|
791
|
-
}
|
|
792
|
-
else {
|
|
793
|
-
yield metadata;
|
|
794
|
-
}
|
|
795
|
-
// Recursively process child routes
|
|
796
|
-
if (children?.length) {
|
|
797
|
-
yield* traverseRoutesConfig({
|
|
798
|
-
...options,
|
|
799
|
-
routes: children,
|
|
800
|
-
parentRoute: currentRoutePath,
|
|
801
|
-
parentPreloads: metadata.preload,
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
// Load and process lazy-loaded child routes
|
|
805
|
-
if (loadChildren) {
|
|
806
|
-
if (ɵentryName) {
|
|
807
|
-
// When using `loadChildren`, the entire feature area (including multiple routes) is loaded.
|
|
808
|
-
// As a result, we do not want all dynamic-import dependencies to be preload, because it involves multiple dependencies
|
|
809
|
-
// across different child routes. In contrast, `loadComponent` only loads a single component, which allows
|
|
810
|
-
// for precise control over preloading, ensuring that the files preloaded are exactly those required for that specific route.
|
|
811
|
-
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata, false);
|
|
812
|
-
}
|
|
813
|
-
const loadedChildRoutes = await _loadChildren(route, compiler, parentInjector).toPromise();
|
|
814
|
-
if (loadedChildRoutes) {
|
|
815
|
-
const { routes: childRoutes, injector = parentInjector } = loadedChildRoutes;
|
|
816
|
-
yield* traverseRoutesConfig({
|
|
817
|
-
...options,
|
|
818
|
-
routes: childRoutes,
|
|
819
|
-
parentInjector: injector,
|
|
820
|
-
parentRoute: currentRoutePath,
|
|
821
|
-
parentPreloads: metadata.preload,
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
catch (error) {
|
|
827
|
-
yield {
|
|
828
|
-
error: `Error processing route '${stripLeadingSlash(route.path ?? '')}': ${error.message}`,
|
|
829
|
-
};
|
|
830
|
-
}
|
|
868
|
+
},
|
|
869
|
+
currentRoutePath,
|
|
870
|
+
route,
|
|
871
|
+
});
|
|
831
872
|
}
|
|
832
873
|
}
|
|
833
874
|
/**
|
|
@@ -838,18 +879,26 @@ async function* traverseRoutesConfig(options) {
|
|
|
838
879
|
* preloads to a predefined maximum.
|
|
839
880
|
*/
|
|
840
881
|
function appendPreloadToMetadata(entryName, entryPointToBrowserMapping, metadata, includeDynamicImports) {
|
|
841
|
-
|
|
882
|
+
const existingPreloads = metadata.preload ?? [];
|
|
883
|
+
if (!entryPointToBrowserMapping || existingPreloads.length >= MODULE_PRELOAD_MAX) {
|
|
842
884
|
return;
|
|
843
885
|
}
|
|
844
886
|
const preload = entryPointToBrowserMapping[entryName];
|
|
845
|
-
if (preload?.length) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
+
}
|
|
852
900
|
}
|
|
901
|
+
metadata.preload = Array.from(combinedPreloads);
|
|
853
902
|
}
|
|
854
903
|
/**
|
|
855
904
|
* Handles SSG (Static Site Generation) routes by invoking `getPrerenderParams` and yielding
|
|
@@ -1054,13 +1103,10 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
1054
1103
|
router.navigationTransitions.afterPreactivation()?.next?.();
|
|
1055
1104
|
// Wait until the application is stable.
|
|
1056
1105
|
await applicationRef.whenStable();
|
|
1057
|
-
const routesResults = [];
|
|
1058
1106
|
const errors = [];
|
|
1059
|
-
|
|
1107
|
+
const rawBaseHref = injector.get(APP_BASE_HREF, null, { optional: true }) ??
|
|
1060
1108
|
injector.get(PlatformLocation).getBaseHrefFromDOM();
|
|
1061
|
-
|
|
1062
|
-
baseHref = baseHref.slice(2);
|
|
1063
|
-
}
|
|
1109
|
+
const { pathname: baseHref } = new URL(rawBaseHref, 'http://localhost');
|
|
1064
1110
|
const compiler = injector.get(Compiler);
|
|
1065
1111
|
const serverRoutesConfig = injector.get(SERVER_ROUTES_CONFIG, null, { optional: true });
|
|
1066
1112
|
let serverConfigRouteTree;
|
|
@@ -1072,10 +1118,11 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
1072
1118
|
if (errors.length) {
|
|
1073
1119
|
return {
|
|
1074
1120
|
baseHref,
|
|
1075
|
-
routes:
|
|
1121
|
+
routes: [],
|
|
1076
1122
|
errors,
|
|
1077
1123
|
};
|
|
1078
1124
|
}
|
|
1125
|
+
const routesResults = [];
|
|
1079
1126
|
if (router.config.length) {
|
|
1080
1127
|
// Retrieve all routes from the Angular router configuration.
|
|
1081
1128
|
const traverseRoutes = traverseRoutesConfig({
|
|
@@ -1088,12 +1135,18 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
1088
1135
|
includePrerenderFallbackRoutes,
|
|
1089
1136
|
entryPointToBrowserMapping,
|
|
1090
1137
|
});
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1138
|
+
const seenRoutes = new Set();
|
|
1139
|
+
for await (const routeMetadata of traverseRoutes) {
|
|
1140
|
+
if ('error' in routeMetadata) {
|
|
1141
|
+
errors.push(routeMetadata.error);
|
|
1142
|
+
continue;
|
|
1094
1143
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
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);
|
|
1097
1150
|
}
|
|
1098
1151
|
}
|
|
1099
1152
|
// This timeout is necessary to prevent 'adev' from hanging in production builds.
|