@angular/ssr 22.2.2 → 22.2.4
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/_validation-chunk.mjs.map +1 -1
- package/fesm2022/node.mjs.map +1 -1
- package/fesm2022/ssr.mjs +115 -48
- package/fesm2022/ssr.mjs.map +1 -1
- package/package.json +7 -7
- package/types/ssr.d.ts +2 -0
package/fesm2022/ssr.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeTrustProxyHeaders, sanitizeRequestHeaders, validateRequest } from './_validation-chunk.mjs';
|
|
1
|
+
import { getFirstHeaderValue, normalizeTrustProxyHeaders, sanitizeRequestHeaders, validateRequest } from './_validation-chunk.mjs';
|
|
2
2
|
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
|
|
3
3
|
import { ɵConsole as _Console, ApplicationRef, REQUEST, makeEnvironmentProviders, provideEnvironmentInitializer, inject, InjectionToken, ɵENABLE_ROOT_COMPONENT_BOOTSTRAP as _ENABLE_ROOT_COMPONENT_BOOTSTRAP, Compiler, createEnvironmentInjector, EnvironmentInjector, runInInjectionContext, ɵresetCompiledComponents as _resetCompiledComponents, REQUEST_CONTEXT, RESPONSE_INIT, LOCALE_ID } from '@angular/core';
|
|
4
4
|
import { platformServer, INITIAL_CONFIG, ɵSERVER_CONTEXT as _SERVER_CONTEXT, ɵrenderInternal as _renderInternal, provideServerRendering as provideServerRendering$1 } from '@angular/platform-server';
|
|
@@ -95,6 +95,7 @@ function stripIndexHtmlFromURL(url) {
|
|
|
95
95
|
}
|
|
96
96
|
return url;
|
|
97
97
|
}
|
|
98
|
+
const POSITIONAL_PLACEHOLDER_REGEXP = /^\*(\d+)$/;
|
|
98
99
|
function buildPathWithParams(toPath, fromPath) {
|
|
99
100
|
if (toPath[0] !== '/') {
|
|
100
101
|
throw new Error(`Invalid toPath: The string must start with a '/'. Received: '${toPath}'`);
|
|
@@ -102,13 +103,35 @@ function buildPathWithParams(toPath, fromPath) {
|
|
|
102
103
|
if (fromPath[0] !== '/') {
|
|
103
104
|
throw new Error(`Invalid fromPath: The string must start with a '/'. Received: '${fromPath}'`);
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
+
const [path] = toPath.split(/[?#]/, 1);
|
|
107
|
+
if (!path.includes('/*')) {
|
|
106
108
|
return toPath;
|
|
107
109
|
}
|
|
108
110
|
const fromPathParts = fromPath.split('/');
|
|
109
|
-
const toPathParts =
|
|
110
|
-
const resolvedParts = toPathParts.map((part, index) =>
|
|
111
|
-
return joinUrlParts(...resolvedParts);
|
|
111
|
+
const toPathParts = path.split('/');
|
|
112
|
+
const resolvedParts = toPathParts.map((part, index) => part === '*' ? fromPathParts[index] : part.replace(POSITIONAL_PLACEHOLDER_REGEXP, (_, position) => fromPathParts[position]));
|
|
113
|
+
return joinUrlParts(...resolvedParts) + toPath.slice(path.length);
|
|
114
|
+
}
|
|
115
|
+
function buildQueryWithParams(toUrl, fromParams) {
|
|
116
|
+
const [, path, query, fragment] = /^([^?#]*)\?([^#]*)(.*)$/.exec(toUrl) ?? [];
|
|
117
|
+
if (query === undefined) {
|
|
118
|
+
return toUrl;
|
|
119
|
+
}
|
|
120
|
+
const params = query.split('&');
|
|
121
|
+
const names = params.map(param => param.split('=', 1)[0]);
|
|
122
|
+
const resolvedQuery = params.map((param, index) => {
|
|
123
|
+
const name = names[index];
|
|
124
|
+
const value = param.slice(name.length + 1);
|
|
125
|
+
if (value[0] !== ':' || names.indexOf(name) !== names.lastIndexOf(name)) {
|
|
126
|
+
return param;
|
|
127
|
+
}
|
|
128
|
+
const sourceValues = fromParams.getAll(value.slice(1));
|
|
129
|
+
return (sourceValues.length ? sourceValues : ['undefined']).map(sourceValue => `${name}=${encodeQueryValue(sourceValue)}`).join('&');
|
|
130
|
+
}).join('&');
|
|
131
|
+
return `${path}?${resolvedQuery}${fragment}`;
|
|
132
|
+
}
|
|
133
|
+
function encodeQueryValue(value) {
|
|
134
|
+
return encodeURIComponent(value).replace(/%(40|3A|24|2C|3B)/g, match => decodeURIComponent(match));
|
|
112
135
|
}
|
|
113
136
|
const MATRIX_PARAMS_REGEX = /;[^/]+/g;
|
|
114
137
|
function stripMatrixParams(pathname) {
|
|
@@ -158,9 +181,9 @@ async function renderAngular(html, bootstrap, url, platformProviders, serverCont
|
|
|
158
181
|
hasNavigationError = false;
|
|
159
182
|
const requestPrefix = envInjector.get(APP_BASE_HREF, null, {
|
|
160
183
|
optional: true
|
|
161
|
-
}) ?? envInjector.get(REQUEST, null, {
|
|
184
|
+
}) ?? getFirstHeaderValue(envInjector.get(REQUEST, null, {
|
|
162
185
|
optional: true
|
|
163
|
-
})?.headers.get('X-Forwarded-Prefix');
|
|
186
|
+
})?.headers.get('X-Forwarded-Prefix'));
|
|
164
187
|
const {
|
|
165
188
|
pathname,
|
|
166
189
|
search,
|
|
@@ -348,7 +371,7 @@ function provideServerRendering(...args) {
|
|
|
348
371
|
providers.push(...ɵproviders);
|
|
349
372
|
}
|
|
350
373
|
if (!hasServerRoutes && hasAppShell) {
|
|
351
|
-
throw new Error(`Configuration error: found 'withAppShell()' without 'withRoutes()' in the same call to 'provideServerRendering()'
|
|
374
|
+
throw new Error(`Configuration error: found 'withAppShell()' without 'withRoutes()' in the same call to 'provideServerRendering()'. ` + `The 'withAppShell()' function requires 'withRoutes()' to be used.`);
|
|
352
375
|
}
|
|
353
376
|
return makeEnvironmentProviders(providers);
|
|
354
377
|
}
|
|
@@ -450,6 +473,8 @@ async function* handleRoute(options) {
|
|
|
450
473
|
const {
|
|
451
474
|
metadata,
|
|
452
475
|
currentRoutePath,
|
|
476
|
+
parentRoute,
|
|
477
|
+
baseHref,
|
|
453
478
|
route,
|
|
454
479
|
compiler,
|
|
455
480
|
parentInjector,
|
|
@@ -465,55 +490,75 @@ async function* handleRoute(options) {
|
|
|
465
490
|
children,
|
|
466
491
|
ɵentryName
|
|
467
492
|
} = route;
|
|
468
|
-
if (ɵentryName && loadComponent) {
|
|
493
|
+
if (ɵentryName && (loadComponent || loadChildren)) {
|
|
469
494
|
appendPreloadToMetadata(ɵentryName, entryPointToBrowserMapping, metadata);
|
|
470
495
|
}
|
|
496
|
+
const parentRoutes = new Map();
|
|
471
497
|
if (metadata.renderMode === RenderMode.Prerender) {
|
|
472
|
-
|
|
498
|
+
for await (const result of handleSSGRoute(serverConfigRouteTree, route, parentRoute, baseHref, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes)) {
|
|
499
|
+
if ('error' in result) {
|
|
500
|
+
yield result;
|
|
501
|
+
} else {
|
|
502
|
+
parentRoutes.set(result.route, result);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
473
505
|
} else if (redirectTo !== undefined) {
|
|
474
506
|
if (metadata.status && !isValidRedirectResponseCode(metadata.status)) {
|
|
475
507
|
yield {
|
|
476
508
|
error: `The '${metadata.status}' status code is not a valid redirect response code. ` + `Please use one of the following redirect response codes: ${[...VALID_REDIRECT_RESPONSE_CODES.values()].join(', ')}.`
|
|
477
509
|
};
|
|
478
510
|
} else if (typeof redirectTo === 'string') {
|
|
479
|
-
|
|
511
|
+
const routeSegments = joinUrlParts(baseHref, currentRoutePath).split('/');
|
|
512
|
+
parentRoutes.set(metadata.route, {
|
|
480
513
|
...metadata,
|
|
481
|
-
redirectTo: resolveRedirectTo(
|
|
482
|
-
|
|
514
|
+
redirectTo: resolveRedirectTo(parentRoute, replaceRedirectParams(route, redirectTo, parameter => `*${routeSegments.lastIndexOf(parameter)}`)),
|
|
515
|
+
relativeRedirect: redirectTo[0] !== '/' ? true : undefined
|
|
516
|
+
});
|
|
483
517
|
} else {
|
|
484
|
-
|
|
518
|
+
parentRoutes.set(metadata.route, metadata);
|
|
485
519
|
}
|
|
486
520
|
} else {
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
if (children?.length) {
|
|
490
|
-
yield* traverseRoutesConfig({
|
|
491
|
-
...options,
|
|
492
|
-
routes: children,
|
|
493
|
-
parentRoute: currentRoutePath,
|
|
494
|
-
parentPreloads: metadata.preload
|
|
495
|
-
});
|
|
521
|
+
parentRoutes.set(metadata.route, metadata);
|
|
496
522
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
523
|
+
let childRoutes = children;
|
|
524
|
+
let childInjector = parentInjector;
|
|
525
|
+
if (!childRoutes?.length && loadChildren) {
|
|
501
526
|
const routeInjector = route.providers ? createEnvironmentInjector(route.providers, parentInjector.get(EnvironmentInjector), `Route: ${route.path}`) : parentInjector;
|
|
502
527
|
const loadedChildRoutes = await _loadChildren(route, compiler, routeInjector);
|
|
503
528
|
if (loadedChildRoutes) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
529
|
+
childRoutes = loadedChildRoutes.routes;
|
|
530
|
+
childInjector = loadedChildRoutes.injector ?? routeInjector;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!childRoutes?.length) {
|
|
534
|
+
yield* parentRoutes.values();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const childResults = [];
|
|
538
|
+
const overriddenParentRoutes = new Set();
|
|
539
|
+
const traverseChildRoutes = traverseRoutesConfig({
|
|
540
|
+
...options,
|
|
541
|
+
routes: childRoutes,
|
|
542
|
+
parentInjector: childInjector,
|
|
543
|
+
parentRoute: currentRoutePath,
|
|
544
|
+
parentPreloads: metadata.preload
|
|
545
|
+
});
|
|
546
|
+
for await (const result of traverseChildRoutes) {
|
|
547
|
+
if ('error' in result) {
|
|
548
|
+
childResults.push(result);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (parentRoutes.has(result.route)) {
|
|
552
|
+
if (!overriddenParentRoutes.has(result.route)) {
|
|
553
|
+
overriddenParentRoutes.add(result.route);
|
|
554
|
+
parentRoutes.set(result.route, result);
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
childResults.push(result);
|
|
515
558
|
}
|
|
516
559
|
}
|
|
560
|
+
yield* parentRoutes.values();
|
|
561
|
+
yield* childResults;
|
|
517
562
|
} catch (error) {
|
|
518
563
|
yield {
|
|
519
564
|
error: `Error in handleRoute for '${options.currentRoutePath}': ${error.message}`
|
|
@@ -590,7 +635,7 @@ async function* traverseRoutesConfig(options) {
|
|
|
590
635
|
renderMode: RenderMode.Prerender,
|
|
591
636
|
...matchedMetaData,
|
|
592
637
|
preload: parentPreloads,
|
|
593
|
-
route:
|
|
638
|
+
route: currentRoutePath,
|
|
594
639
|
presentInClientRouter: undefined
|
|
595
640
|
},
|
|
596
641
|
currentRoutePath,
|
|
@@ -616,7 +661,7 @@ function appendPreloadToMetadata(entryName, entryPointToBrowserMapping, metadata
|
|
|
616
661
|
}
|
|
617
662
|
metadata.preload = Array.from(combinedPreloads);
|
|
618
663
|
}
|
|
619
|
-
async function* handleSSGRoute(serverConfigRouteTree,
|
|
664
|
+
async function* handleSSGRoute(serverConfigRouteTree, route, parentRoute, baseHref, metadata, parentInjector, invokeGetPrerenderParams, includePrerenderFallbackRoutes) {
|
|
620
665
|
if (metadata.renderMode !== RenderMode.Prerender) {
|
|
621
666
|
throw new Error(`'handleSSGRoute' was called for a route which rendering mode is not prerender.`);
|
|
622
667
|
}
|
|
@@ -629,8 +674,11 @@ async function* handleSSGRoute(serverConfigRouteTree, redirectTo, metadata, pare
|
|
|
629
674
|
if ('getPrerenderParams' in meta) {
|
|
630
675
|
delete meta['getPrerenderParams'];
|
|
631
676
|
}
|
|
677
|
+
const redirectTo = typeof route.redirectTo === 'string' ? route.redirectTo : undefined;
|
|
632
678
|
if (redirectTo !== undefined) {
|
|
633
|
-
|
|
679
|
+
const routeSegments = joinUrlParts(baseHref, currentRoutePath).split('/');
|
|
680
|
+
meta.redirectTo = resolveRedirectTo(parentRoute, replaceRedirectParams(route, redirectTo, parameter => `*${routeSegments.lastIndexOf(parameter)}`));
|
|
681
|
+
meta.relativeRedirect = redirectTo[0] !== '/' ? true : undefined;
|
|
634
682
|
}
|
|
635
683
|
const isCatchAllRoute = CATCH_ALL_REGEXP.test(currentRoutePath);
|
|
636
684
|
if (isCatchAllRoute && !getPrerenderParams || !isCatchAllRoute && !URL_PARAMETER_REGEXP.test(currentRoutePath)) {
|
|
@@ -666,7 +714,7 @@ async function* handleSSGRoute(serverConfigRouteTree, redirectTo, metadata, pare
|
|
|
666
714
|
yield {
|
|
667
715
|
...meta,
|
|
668
716
|
route: routeWithResolvedParams,
|
|
669
|
-
redirectTo: redirectTo === undefined ? undefined : resolveRedirectTo(
|
|
717
|
+
redirectTo: redirectTo === undefined ? undefined : resolveRedirectTo(parentRoute.replace(URL_PARAMETER_GLOBAL_REGEXP, replacer), replaceRedirectParams(route, redirectTo, parameter => params[parameter.slice(1)]))
|
|
670
718
|
};
|
|
671
719
|
}
|
|
672
720
|
} catch (error) {
|
|
@@ -694,13 +742,29 @@ function handlePrerenderParamsReplacement(params, currentRoutePath) {
|
|
|
694
742
|
return parameterName === '**' ? `/${value}` : value;
|
|
695
743
|
};
|
|
696
744
|
}
|
|
697
|
-
function resolveRedirectTo(
|
|
745
|
+
function resolveRedirectTo(parentRoute, redirectTo) {
|
|
698
746
|
if (redirectTo[0] === '/') {
|
|
699
747
|
return redirectTo;
|
|
700
748
|
}
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
749
|
+
const [path] = redirectTo.split(/[?#]/, 1);
|
|
750
|
+
return joinUrlParts(parentRoute.replace(URL_PARAMETER_GLOBAL_REGEXP, '*'), path);
|
|
751
|
+
}
|
|
752
|
+
function replaceRedirectParams(route, redirectTo, resolve) {
|
|
753
|
+
if (route.matcher) {
|
|
754
|
+
return redirectTo;
|
|
755
|
+
}
|
|
756
|
+
const routeSegments = route.path?.split('/') ?? [];
|
|
757
|
+
const [path] = redirectTo.split(/[?#]/, 1);
|
|
758
|
+
const resolvedPath = path.split('/').map(segment => {
|
|
759
|
+
if (segment[0] !== ':') {
|
|
760
|
+
return segment;
|
|
761
|
+
}
|
|
762
|
+
if (!routeSegments.includes(segment)) {
|
|
763
|
+
throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${segment}'.`);
|
|
764
|
+
}
|
|
765
|
+
return resolve(segment);
|
|
766
|
+
}).join('/');
|
|
767
|
+
return resolvedPath + redirectTo.slice(path.length);
|
|
704
768
|
}
|
|
705
769
|
function buildServerConfigRouteTree({
|
|
706
770
|
routes,
|
|
@@ -800,6 +864,7 @@ async function getRoutesFromAngularRouterConfig(bootstrap, document, url, invoke
|
|
|
800
864
|
compiler,
|
|
801
865
|
parentInjector: injector,
|
|
802
866
|
parentRoute: '',
|
|
867
|
+
baseHref,
|
|
803
868
|
serverConfigRouteTree,
|
|
804
869
|
invokeGetPrerenderParams,
|
|
805
870
|
includePrerenderFallbackRoutes,
|
|
@@ -995,12 +1060,14 @@ class AngularServerApp {
|
|
|
995
1060
|
}
|
|
996
1061
|
const {
|
|
997
1062
|
redirectTo,
|
|
1063
|
+
relativeRedirect,
|
|
998
1064
|
status,
|
|
999
1065
|
renderMode,
|
|
1000
1066
|
headers
|
|
1001
1067
|
} = matchedRoute;
|
|
1002
1068
|
if (redirectTo !== undefined) {
|
|
1003
|
-
|
|
1069
|
+
const location = joinUrlParts(getFirstHeaderValue(request.headers.get('X-Forwarded-Prefix')) ?? '', buildPathWithParams(redirectTo, url.pathname));
|
|
1070
|
+
return createRedirectResponse(relativeRedirect ? location + url.search : buildQueryWithParams(location, url.searchParams), status, headers);
|
|
1004
1071
|
}
|
|
1005
1072
|
if (renderMode === RenderMode.Prerender) {
|
|
1006
1073
|
const response = await this.handleServe(request, matchedRoute);
|
|
@@ -1341,7 +1408,7 @@ class AngularAppEngine {
|
|
|
1341
1408
|
if (preferredLocale) {
|
|
1342
1409
|
const subPath = supportedLocales[preferredLocale];
|
|
1343
1410
|
if (subPath !== undefined) {
|
|
1344
|
-
const prefix = request.headers.get('X-Forwarded-Prefix') ?? '';
|
|
1411
|
+
const prefix = getFirstHeaderValue(request.headers.get('X-Forwarded-Prefix')) ?? '';
|
|
1345
1412
|
return createRedirectResponse(joinUrlParts(prefix, pathname, subPath), 302, {
|
|
1346
1413
|
'Vary': 'Accept-Language'
|
|
1347
1414
|
});
|