@angular/ssr 18.2.1 → 19.0.0-next.1
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/esm2022/private_export.mjs +10 -0
- package/esm2022/public_api.mjs +2 -1
- package/esm2022/src/app-engine.mjs +84 -0
- package/esm2022/src/app.mjs +167 -0
- package/esm2022/src/assets.mjs +44 -0
- package/esm2022/src/console.mjs +34 -0
- package/esm2022/src/hooks.mjs +94 -0
- package/esm2022/src/i18n.mjs +41 -0
- package/esm2022/src/manifest.mjs +59 -0
- package/esm2022/src/request.mjs +63 -0
- package/esm2022/src/response.mjs +58 -0
- package/esm2022/src/routes/ng-routes.mjs +157 -0
- package/esm2022/src/routes/route-tree.mjs +180 -0
- package/esm2022/src/routes/router.mjs +88 -0
- package/esm2022/src/tokens.mjs +20 -0
- package/esm2022/src/utils/ng.mjs +51 -0
- package/esm2022/src/utils/url.mjs +85 -0
- package/fesm2022/ssr.mjs +895 -4
- package/fesm2022/ssr.mjs.map +1 -1
- package/index.d.ts +336 -0
- package/package.json +13 -3
- /package/esm2022/src/{common-engine.mjs → common-engine/common-engine.mjs} +0 -0
- /package/esm2022/src/{inline-css-processor.mjs → common-engine/inline-css-processor.mjs} +0 -0
- /package/esm2022/src/{peformance-profiler.mjs → common-engine/peformance-profiler.mjs} +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
export { getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig } from './src/routes/ng-routes';
|
|
9
|
+
export { ServerRenderContext as ɵServerRenderContext, getOrCreateAngularServerApp as ɵgetOrCreateAngularServerApp, destroyAngularServerApp as ɵdestroyAngularServerApp, } from './src/app';
|
|
10
|
+
export { setAngularAppManifest as ɵsetAngularAppManifest, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, } from './src/manifest';
|
package/esm2022/public_api.mjs
CHANGED
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
|
-
export { CommonEngine, } from './src/common-engine';
|
|
8
|
+
export { CommonEngine, } from './src/common-engine/common-engine';
|
|
9
|
+
export * from './private_export';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
import { Hooks } from './hooks';
|
|
9
|
+
import { getPotentialLocaleIdFromUrl } from './i18n';
|
|
10
|
+
import { getAngularAppEngineManifest } from './manifest';
|
|
11
|
+
/**
|
|
12
|
+
* Angular server application engine.
|
|
13
|
+
* Manages Angular server applications (including localized ones), handles rendering requests,
|
|
14
|
+
* and optionally transforms index HTML before rendering.
|
|
15
|
+
*/
|
|
16
|
+
export class AngularAppEngine {
|
|
17
|
+
/**
|
|
18
|
+
* Hooks for extending or modifying the behavior of the server application.
|
|
19
|
+
* These hooks are used by the Angular CLI when running the development server and
|
|
20
|
+
* provide extensibility points for the application lifecycle.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
static hooks = new Hooks();
|
|
25
|
+
/**
|
|
26
|
+
* Provides access to the hooks for extending or modifying the server application's behavior.
|
|
27
|
+
* This allows attaching custom functionality to various server application lifecycle events.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
get hooks() {
|
|
32
|
+
return AngularAppEngine.hooks;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The manifest for the server application.
|
|
36
|
+
*/
|
|
37
|
+
manifest = getAngularAppEngineManifest();
|
|
38
|
+
/**
|
|
39
|
+
* Renders a response for the given HTTP request using the server application.
|
|
40
|
+
*
|
|
41
|
+
* This method processes the request, determines the appropriate route and rendering context,
|
|
42
|
+
* and returns an HTTP response.
|
|
43
|
+
*
|
|
44
|
+
* If the request URL appears to be for a file (excluding `/index.html`), the method returns `null`.
|
|
45
|
+
* A request to `https://www.example.com/page/index.html` will render the Angular route
|
|
46
|
+
* corresponding to `https://www.example.com/page`.
|
|
47
|
+
*
|
|
48
|
+
* @param request - The incoming HTTP request object to be rendered.
|
|
49
|
+
* @param requestContext - Optional additional context for the request, such as metadata.
|
|
50
|
+
* @returns A promise that resolves to a Response object, or `null` if the request URL represents a file (e.g., `./logo.png`)
|
|
51
|
+
* rather than an application route.
|
|
52
|
+
*/
|
|
53
|
+
async render(request, requestContext) {
|
|
54
|
+
// Skip if the request looks like a file but not `/index.html`.
|
|
55
|
+
const url = new URL(request.url);
|
|
56
|
+
const entryPoint = this.getEntryPointFromUrl(url);
|
|
57
|
+
if (!entryPoint) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await entryPoint();
|
|
61
|
+
const serverApp = getOrCreateAngularServerApp();
|
|
62
|
+
serverApp.hooks = this.hooks;
|
|
63
|
+
return serverApp.render(request, requestContext);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves the entry point path and locale for the Angular server application based on the provided URL.
|
|
67
|
+
*
|
|
68
|
+
* This method determines the appropriate entry point and locale for rendering the application by examining the URL.
|
|
69
|
+
* If there is only one entry point available, it is returned regardless of the URL.
|
|
70
|
+
* Otherwise, the method extracts a potential locale identifier from the URL and looks up the corresponding entry point.
|
|
71
|
+
*
|
|
72
|
+
* @param url - The URL used to derive the locale and determine the appropriate entry point.
|
|
73
|
+
* @returns A function that returns a promise resolving to an object with the `EntryPointExports` type,
|
|
74
|
+
* or `undefined` if no matching entry point is found for the extracted locale.
|
|
75
|
+
*/
|
|
76
|
+
getEntryPointFromUrl(url) {
|
|
77
|
+
const { entryPoints, basePath } = this.manifest;
|
|
78
|
+
if (entryPoints.size === 1) {
|
|
79
|
+
return entryPoints.values().next().value;
|
|
80
|
+
}
|
|
81
|
+
const potentialLocale = getPotentialLocaleIdFromUrl(url, basePath);
|
|
82
|
+
return entryPoints.get(potentialLocale);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
import { ɵConsole, ɵresetCompiledComponents } from '@angular/core';
|
|
9
|
+
import { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server';
|
|
10
|
+
import { ServerAssets } from './assets';
|
|
11
|
+
import { Console } from './console';
|
|
12
|
+
import { Hooks } from './hooks';
|
|
13
|
+
import { getAngularAppManifest } from './manifest';
|
|
14
|
+
import { ServerRouter } from './routes/router';
|
|
15
|
+
import { REQUEST, REQUEST_CONTEXT, RESPONSE_INIT } from './tokens';
|
|
16
|
+
import { renderAngular } from './utils/ng';
|
|
17
|
+
/**
|
|
18
|
+
* Enum representing the different contexts in which server rendering can occur.
|
|
19
|
+
*/
|
|
20
|
+
export var ServerRenderContext;
|
|
21
|
+
(function (ServerRenderContext) {
|
|
22
|
+
ServerRenderContext["SSR"] = "ssr";
|
|
23
|
+
ServerRenderContext["SSG"] = "ssg";
|
|
24
|
+
ServerRenderContext["AppShell"] = "app-shell";
|
|
25
|
+
})(ServerRenderContext || (ServerRenderContext = {}));
|
|
26
|
+
/**
|
|
27
|
+
* Represents a locale-specific Angular server application managed by the server application engine.
|
|
28
|
+
*
|
|
29
|
+
* The `AngularServerApp` class handles server-side rendering and asset management for a specific locale.
|
|
30
|
+
*/
|
|
31
|
+
export class AngularServerApp {
|
|
32
|
+
/**
|
|
33
|
+
* Hooks for extending or modifying the behavior of the server application.
|
|
34
|
+
* This instance can be used to attach custom functionality to various events in the server application lifecycle.
|
|
35
|
+
*/
|
|
36
|
+
hooks = new Hooks();
|
|
37
|
+
/**
|
|
38
|
+
* The manifest associated with this server application.
|
|
39
|
+
*/
|
|
40
|
+
manifest = getAngularAppManifest();
|
|
41
|
+
/**
|
|
42
|
+
* An instance of ServerAsset that handles server-side asset.
|
|
43
|
+
*/
|
|
44
|
+
assets = new ServerAssets(this.manifest);
|
|
45
|
+
/**
|
|
46
|
+
* The router instance used for route matching and handling.
|
|
47
|
+
*/
|
|
48
|
+
router;
|
|
49
|
+
/**
|
|
50
|
+
* Renders a response for the given HTTP request using the server application.
|
|
51
|
+
*
|
|
52
|
+
* This method processes the request and returns a response based on the specified rendering context.
|
|
53
|
+
*
|
|
54
|
+
* @param request - The incoming HTTP request to be rendered.
|
|
55
|
+
* @param requestContext - Optional additional context for rendering, such as request metadata.
|
|
56
|
+
* @param serverContext - The rendering context.
|
|
57
|
+
*
|
|
58
|
+
* @returns A promise that resolves to the HTTP response object resulting from the rendering, or null if no match is found.
|
|
59
|
+
*/
|
|
60
|
+
render(request, requestContext, serverContext = ServerRenderContext.SSR) {
|
|
61
|
+
return Promise.race([
|
|
62
|
+
this.createAbortPromise(request),
|
|
63
|
+
this.handleRendering(request, requestContext, serverContext),
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a promise that rejects when the request is aborted.
|
|
68
|
+
*
|
|
69
|
+
* @param request - The HTTP request to monitor for abortion.
|
|
70
|
+
* @returns A promise that never resolves but rejects with an `AbortError` if the request is aborted.
|
|
71
|
+
*/
|
|
72
|
+
createAbortPromise(request) {
|
|
73
|
+
return new Promise((_, reject) => {
|
|
74
|
+
request.signal.addEventListener('abort', () => {
|
|
75
|
+
const abortError = new Error(`Request for: ${request.url} was aborted.\n${request.signal.reason}`);
|
|
76
|
+
abortError.name = 'AbortError';
|
|
77
|
+
reject(abortError);
|
|
78
|
+
}, { once: true });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Handles the server-side rendering process for the given HTTP request.
|
|
83
|
+
* This method matches the request URL to a route and performs rendering if a matching route is found.
|
|
84
|
+
*
|
|
85
|
+
* @param request - The incoming HTTP request to be processed.
|
|
86
|
+
* @param requestContext - Optional additional context for rendering, such as request metadata.
|
|
87
|
+
* @param serverContext - The rendering context. Defaults to server-side rendering (SSR).
|
|
88
|
+
*
|
|
89
|
+
* @returns A promise that resolves to the rendered response, or null if no matching route is found.
|
|
90
|
+
*/
|
|
91
|
+
async handleRendering(request, requestContext, serverContext = ServerRenderContext.SSR) {
|
|
92
|
+
const url = new URL(request.url);
|
|
93
|
+
this.router ??= await ServerRouter.from(this.manifest, url);
|
|
94
|
+
const matchedRoute = this.router.match(url);
|
|
95
|
+
if (!matchedRoute) {
|
|
96
|
+
// Not a known Angular route.
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const { redirectTo } = matchedRoute;
|
|
100
|
+
if (redirectTo !== undefined) {
|
|
101
|
+
// 302 Found is used by default for redirections
|
|
102
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
|
|
103
|
+
return Response.redirect(new URL(redirectTo, url), 302);
|
|
104
|
+
}
|
|
105
|
+
const platformProviders = [
|
|
106
|
+
{
|
|
107
|
+
provide: SERVER_CONTEXT,
|
|
108
|
+
useValue: serverContext,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
// An Angular Console Provider that does not print a set of predefined logs.
|
|
112
|
+
provide: ɵConsole,
|
|
113
|
+
// Using `useClass` would necessitate decorating `Console` with `@Injectable`,
|
|
114
|
+
// which would require switching from `ts_library` to `ng_module`. This change
|
|
115
|
+
// would also necessitate various patches of `@angular/bazel` to support ESM.
|
|
116
|
+
useFactory: () => new Console(),
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
const isSsrMode = serverContext === ServerRenderContext.SSR;
|
|
120
|
+
const responseInit = {};
|
|
121
|
+
if (isSsrMode) {
|
|
122
|
+
platformProviders.push({
|
|
123
|
+
provide: REQUEST,
|
|
124
|
+
useValue: request,
|
|
125
|
+
}, {
|
|
126
|
+
provide: REQUEST_CONTEXT,
|
|
127
|
+
useValue: requestContext,
|
|
128
|
+
}, {
|
|
129
|
+
provide: RESPONSE_INIT,
|
|
130
|
+
useValue: responseInit,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
134
|
+
// Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
|
|
135
|
+
// Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
|
|
136
|
+
// See: https://github.com/angular/angular-cli/issues/25924
|
|
137
|
+
ɵresetCompiledComponents();
|
|
138
|
+
}
|
|
139
|
+
const { manifest, hooks, assets } = this;
|
|
140
|
+
let html = await assets.getIndexServerHtml();
|
|
141
|
+
// Skip extra microtask if there are no pre hooks.
|
|
142
|
+
if (hooks.has('html:transform:pre')) {
|
|
143
|
+
html = await hooks.run('html:transform:pre', { html });
|
|
144
|
+
}
|
|
145
|
+
return new Response(await renderAngular(html, manifest.bootstrap(), new URL(request.url), platformProviders), responseInit);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
let angularServerApp;
|
|
149
|
+
/**
|
|
150
|
+
* Retrieves or creates an instance of `AngularServerApp`.
|
|
151
|
+
* - If an instance of `AngularServerApp` already exists, it will return the existing one.
|
|
152
|
+
* - If no instance exists, it will create a new one with the provided options.
|
|
153
|
+
* @returns The existing or newly created instance of `AngularServerApp`.
|
|
154
|
+
*/
|
|
155
|
+
export function getOrCreateAngularServerApp() {
|
|
156
|
+
return (angularServerApp ??= new AngularServerApp());
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Destroys the existing `AngularServerApp` instance, releasing associated resources and resetting the
|
|
160
|
+
* reference to `undefined`.
|
|
161
|
+
*
|
|
162
|
+
* This function is primarily used to enable the recreation of the `AngularServerApp` instance,
|
|
163
|
+
* typically when server configuration or application state needs to be refreshed.
|
|
164
|
+
*/
|
|
165
|
+
export function destroyAngularServerApp() {
|
|
166
|
+
angularServerApp = undefined;
|
|
167
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Manages server-side assets.
|
|
10
|
+
*/
|
|
11
|
+
export class ServerAssets {
|
|
12
|
+
manifest;
|
|
13
|
+
/**
|
|
14
|
+
* Creates an instance of ServerAsset.
|
|
15
|
+
*
|
|
16
|
+
* @param manifest - The manifest containing the server assets.
|
|
17
|
+
*/
|
|
18
|
+
constructor(manifest) {
|
|
19
|
+
this.manifest = manifest;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Retrieves the content of a server-side asset using its path.
|
|
23
|
+
*
|
|
24
|
+
* @param path - The path to the server asset.
|
|
25
|
+
* @returns A promise that resolves to the asset content as a string.
|
|
26
|
+
* @throws Error If the asset path is not found in the manifest, an error is thrown.
|
|
27
|
+
*/
|
|
28
|
+
async getServerAsset(path) {
|
|
29
|
+
const asset = this.manifest.assets.get(path);
|
|
30
|
+
if (!asset) {
|
|
31
|
+
throw new Error(`Server asset '${path}' does not exist.`);
|
|
32
|
+
}
|
|
33
|
+
return asset();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Retrieves and caches the content of 'index.server.html'.
|
|
37
|
+
*
|
|
38
|
+
* @returns A promise that resolves to the content of 'index.server.html'.
|
|
39
|
+
* @throws Error If there is an issue retrieving the asset.
|
|
40
|
+
*/
|
|
41
|
+
getIndexServerHtml() {
|
|
42
|
+
return this.getServerAsset('index.server.html');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
import { ɵConsole } from '@angular/core';
|
|
9
|
+
/**
|
|
10
|
+
* Custom implementation of the Angular Console service that filters out specific log messages.
|
|
11
|
+
*
|
|
12
|
+
* This class extends the internal Angular `ɵConsole` class to provide customized logging behavior.
|
|
13
|
+
* It overrides the `log` method to suppress logs that match certain predefined messages.
|
|
14
|
+
*/
|
|
15
|
+
export class Console extends ɵConsole {
|
|
16
|
+
/**
|
|
17
|
+
* A set of log messages that should be ignored and not printed to the console.
|
|
18
|
+
*/
|
|
19
|
+
ignoredLogs = new Set(['Angular is running in development mode.']);
|
|
20
|
+
/**
|
|
21
|
+
* Logs a message to the console if it is not in the set of ignored messages.
|
|
22
|
+
*
|
|
23
|
+
* @param message - The message to log to the console.
|
|
24
|
+
*
|
|
25
|
+
* This method overrides the `log` method of the `ɵConsole` class. It checks if the
|
|
26
|
+
* message is in the `ignoredLogs` set. If it is not, it delegates the logging to
|
|
27
|
+
* the parent class's `log` method. Otherwise, the message is suppressed.
|
|
28
|
+
*/
|
|
29
|
+
log(message) {
|
|
30
|
+
if (!this.ignoredLogs.has(message)) {
|
|
31
|
+
super.log(message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Manages a collection of hooks and provides methods to register and execute them.
|
|
10
|
+
* Hooks are functions that can be invoked with specific arguments to allow modifications or enhancements.
|
|
11
|
+
*/
|
|
12
|
+
export class Hooks {
|
|
13
|
+
/**
|
|
14
|
+
* A map of hook names to arrays of hook functions.
|
|
15
|
+
* Each hook name can have multiple associated functions, which are executed in sequence.
|
|
16
|
+
*/
|
|
17
|
+
store = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Executes all hooks associated with the specified name, passing the given argument to each hook function.
|
|
20
|
+
* The hooks are invoked sequentially, and the argument may be modified by each hook.
|
|
21
|
+
*
|
|
22
|
+
* @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
|
|
23
|
+
* @param name - The name of the hook whose functions will be executed.
|
|
24
|
+
* @param context - The input value to be passed to each hook function. The value is mutated by each hook function.
|
|
25
|
+
* @returns A promise that resolves once all hook functions have been executed.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const hooks = new Hooks();
|
|
30
|
+
* hooks.on('html:transform:pre', async (ctx) => {
|
|
31
|
+
* ctx.html = ctx.html.replace(/foo/g, 'bar');
|
|
32
|
+
* return ctx.html;
|
|
33
|
+
* });
|
|
34
|
+
* const result = await hooks.run('html:transform:pre', { html: '<div>foo</div>' });
|
|
35
|
+
* console.log(result); // '<div>bar</div>'
|
|
36
|
+
* ```
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
async run(name, context) {
|
|
40
|
+
const hooks = this.store.get(name);
|
|
41
|
+
switch (name) {
|
|
42
|
+
case 'html:transform:pre': {
|
|
43
|
+
if (!hooks) {
|
|
44
|
+
return context.html;
|
|
45
|
+
}
|
|
46
|
+
const ctx = { ...context };
|
|
47
|
+
for (const hook of hooks) {
|
|
48
|
+
ctx.html = await hook(ctx);
|
|
49
|
+
}
|
|
50
|
+
return ctx.html;
|
|
51
|
+
}
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`Running hook "${name}" is not supported.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Registers a new hook function under the specified hook name.
|
|
58
|
+
* This function should be a function that takes an argument of type `T` and returns a `string` or `Promise<string>`.
|
|
59
|
+
*
|
|
60
|
+
* @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
|
|
61
|
+
* @param name - The name of the hook under which the function will be registered.
|
|
62
|
+
* @param handler - A function to be executed when the hook is triggered. The handler will be called with an argument
|
|
63
|
+
* that may be modified by the hook functions.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* - If there are existing handlers registered under the given hook name, the new handler will be added to the list.
|
|
67
|
+
* - If no handlers are registered under the given hook name, a new list will be created with the handler as its first element.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* hooks.on('html:transform:pre', async (ctx) => {
|
|
72
|
+
* return ctx.html.replace(/foo/g, 'bar');
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
on(name, handler) {
|
|
77
|
+
const hooks = this.store.get(name);
|
|
78
|
+
if (hooks) {
|
|
79
|
+
hooks.push(handler);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.store.set(name, [handler]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Checks if there are any hooks registered under the specified name.
|
|
87
|
+
*
|
|
88
|
+
* @param name - The name of the hook to check.
|
|
89
|
+
* @returns `true` if there are hooks registered under the specified name, otherwise `false`.
|
|
90
|
+
*/
|
|
91
|
+
has(name) {
|
|
92
|
+
return !!this.store.get(name)?.length;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extracts a potential locale ID from a given URL based on the specified base path.
|
|
10
|
+
*
|
|
11
|
+
* This function parses the URL to locate a potential locale identifier that immediately
|
|
12
|
+
* follows the base path segment in the URL's pathname. If the URL does not contain a valid
|
|
13
|
+
* locale ID, an empty string is returned.
|
|
14
|
+
*
|
|
15
|
+
* @param url - The full URL from which to extract the locale ID.
|
|
16
|
+
* @param basePath - The base path used as the reference point for extracting the locale ID.
|
|
17
|
+
* @returns The extracted locale ID if present, or an empty string if no valid locale ID is found.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```js
|
|
21
|
+
* const url = new URL('https://example.com/base/en/page');
|
|
22
|
+
* const basePath = '/base';
|
|
23
|
+
* const localeId = getPotentialLocaleIdFromUrl(url, basePath);
|
|
24
|
+
* console.log(localeId); // Output: 'en'
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function getPotentialLocaleIdFromUrl(url, basePath) {
|
|
28
|
+
const { pathname } = url;
|
|
29
|
+
// Move forward of the base path section.
|
|
30
|
+
let start = basePath.length;
|
|
31
|
+
if (pathname[start] === '/') {
|
|
32
|
+
start++;
|
|
33
|
+
}
|
|
34
|
+
// Find the next forward slash.
|
|
35
|
+
let end = pathname.indexOf('/', start);
|
|
36
|
+
if (end === -1) {
|
|
37
|
+
end = pathname.length;
|
|
38
|
+
}
|
|
39
|
+
// Extract the potential locale id.
|
|
40
|
+
return pathname.slice(start, end);
|
|
41
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The Angular app manifest object.
|
|
10
|
+
* This is used internally to store the current Angular app manifest.
|
|
11
|
+
*/
|
|
12
|
+
let angularAppManifest;
|
|
13
|
+
/**
|
|
14
|
+
* Sets the Angular app manifest.
|
|
15
|
+
*
|
|
16
|
+
* @param manifest - The manifest object to set for the Angular application.
|
|
17
|
+
*/
|
|
18
|
+
export function setAngularAppManifest(manifest) {
|
|
19
|
+
angularAppManifest = manifest;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Gets the Angular app manifest.
|
|
23
|
+
*
|
|
24
|
+
* @returns The Angular app manifest.
|
|
25
|
+
* @throws Will throw an error if the Angular app manifest is not set.
|
|
26
|
+
*/
|
|
27
|
+
export function getAngularAppManifest() {
|
|
28
|
+
if (!angularAppManifest) {
|
|
29
|
+
throw new Error('Angular app manifest is not set. ' +
|
|
30
|
+
`Please ensure you are using the '@angular/build:application' builder to build your server application.`);
|
|
31
|
+
}
|
|
32
|
+
return angularAppManifest;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The Angular app engine manifest object.
|
|
36
|
+
* This is used internally to store the current Angular app engine manifest.
|
|
37
|
+
*/
|
|
38
|
+
let angularAppEngineManifest;
|
|
39
|
+
/**
|
|
40
|
+
* Sets the Angular app engine manifest.
|
|
41
|
+
*
|
|
42
|
+
* @param manifest - The engine manifest object to set.
|
|
43
|
+
*/
|
|
44
|
+
export function setAngularAppEngineManifest(manifest) {
|
|
45
|
+
angularAppEngineManifest = manifest;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gets the Angular app engine manifest.
|
|
49
|
+
*
|
|
50
|
+
* @returns The Angular app engine manifest.
|
|
51
|
+
* @throws Will throw an error if the Angular app engine manifest is not set.
|
|
52
|
+
*/
|
|
53
|
+
export function getAngularAppEngineManifest() {
|
|
54
|
+
if (!angularAppEngineManifest) {
|
|
55
|
+
throw new Error('Angular app engine manifest is not set. ' +
|
|
56
|
+
`Please ensure you are using the '@angular/build:application' builder to build your server application.`);
|
|
57
|
+
}
|
|
58
|
+
return angularAppEngineManifest;
|
|
59
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Converts a Node.js `IncomingMessage` into a Web Standard `Request`.
|
|
10
|
+
*
|
|
11
|
+
* @param nodeRequest - The Node.js `IncomingMessage` object to convert.
|
|
12
|
+
* @returns A Web Standard `Request` object.
|
|
13
|
+
*/
|
|
14
|
+
export function createWebRequestFromNodeRequest(nodeRequest) {
|
|
15
|
+
const { headers, method = 'GET' } = nodeRequest;
|
|
16
|
+
const withBody = method !== 'GET' && method !== 'HEAD';
|
|
17
|
+
return new Request(createRequestUrl(nodeRequest), {
|
|
18
|
+
method,
|
|
19
|
+
headers: createRequestHeaders(headers),
|
|
20
|
+
body: withBody ? nodeRequest : undefined,
|
|
21
|
+
duplex: withBody ? 'half' : undefined,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a `Headers` object from Node.js `IncomingHttpHeaders`.
|
|
26
|
+
*
|
|
27
|
+
* @param nodeHeaders - The Node.js `IncomingHttpHeaders` object to convert.
|
|
28
|
+
* @returns A `Headers` object containing the converted headers.
|
|
29
|
+
*/
|
|
30
|
+
function createRequestHeaders(nodeHeaders) {
|
|
31
|
+
const headers = new Headers();
|
|
32
|
+
for (const [name, value] of Object.entries(nodeHeaders)) {
|
|
33
|
+
if (typeof value === 'string') {
|
|
34
|
+
headers.append(name, value);
|
|
35
|
+
}
|
|
36
|
+
else if (Array.isArray(value)) {
|
|
37
|
+
for (const item of value) {
|
|
38
|
+
headers.append(name, item);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return headers;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Creates a `URL` object from a Node.js `IncomingMessage`, taking into account the protocol, host, and port.
|
|
46
|
+
*
|
|
47
|
+
* @param nodeRequest - The Node.js `IncomingMessage` object to extract URL information from.
|
|
48
|
+
* @returns A `URL` object representing the request URL.
|
|
49
|
+
*/
|
|
50
|
+
function createRequestUrl(nodeRequest) {
|
|
51
|
+
const { headers, socket, url = '' } = nodeRequest;
|
|
52
|
+
const protocol = headers['x-forwarded-proto'] ?? ('encrypted' in socket && socket.encrypted ? 'https' : 'http');
|
|
53
|
+
const hostname = headers['x-forwarded-host'] ?? headers.host ?? headers[':authority'];
|
|
54
|
+
const port = headers['x-forwarded-port'] ?? socket.localPort;
|
|
55
|
+
if (Array.isArray(hostname)) {
|
|
56
|
+
throw new Error('host value cannot be an array.');
|
|
57
|
+
}
|
|
58
|
+
let hostnameWithPort = hostname;
|
|
59
|
+
if (port && !hostname?.includes(':')) {
|
|
60
|
+
hostnameWithPort += `:${port}`;
|
|
61
|
+
}
|
|
62
|
+
return new URL(url, `${protocol}://${hostnameWithPort}`);
|
|
63
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Streams a web-standard `Response` into a Node.js `ServerResponse`.
|
|
10
|
+
*
|
|
11
|
+
* @param source - The web-standard `Response` object to stream from.
|
|
12
|
+
* @param destination - The Node.js `ServerResponse` object to stream into.
|
|
13
|
+
* @returns A promise that resolves once the streaming operation is complete.
|
|
14
|
+
*/
|
|
15
|
+
export async function writeResponseToNodeResponse(source, destination) {
|
|
16
|
+
const { status, headers, body } = source;
|
|
17
|
+
destination.statusCode = status;
|
|
18
|
+
let cookieHeaderSet = false;
|
|
19
|
+
for (const [name, value] of headers.entries()) {
|
|
20
|
+
if (name === 'set-cookie') {
|
|
21
|
+
if (cookieHeaderSet) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Sets the 'set-cookie' header only once to ensure it is correctly applied.
|
|
25
|
+
// Concatenating 'set-cookie' values can lead to incorrect behavior, so we use a single value from `headers.getSetCookie()`.
|
|
26
|
+
destination.setHeader(name, headers.getSetCookie());
|
|
27
|
+
cookieHeaderSet = true;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
destination.setHeader(name, value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!body) {
|
|
34
|
+
destination.end();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const reader = body.getReader();
|
|
39
|
+
destination.on('close', () => {
|
|
40
|
+
reader.cancel().catch((error) => {
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.error(`An error occurred while writing the response body for: ${destination.req.url}.`, error);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
// eslint-disable-next-line no-constant-condition
|
|
46
|
+
while (true) {
|
|
47
|
+
const { done, value } = await reader.read();
|
|
48
|
+
if (done) {
|
|
49
|
+
destination.end();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
destination.write(value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
destination.end('Internal server error.');
|
|
57
|
+
}
|
|
58
|
+
}
|