@angular/ssr 19.0.0-next.2 → 19.0.0-next.3

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 CHANGED
@@ -1,9 +1,46 @@
1
1
  import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
2
- import { ɵConsole as _Console, ɵresetCompiledComponents as _resetCompiledComponents, createPlatformFactory, platformCore, ApplicationRef, ɵwhenStable as _whenStable, Compiler, InjectionToken } from '@angular/core';
2
+ import { ɵConsole as _Console, createPlatformFactory, platformCore, ApplicationRef, ɵwhenStable as _whenStable, Compiler, InjectionToken, ɵresetCompiledComponents as _resetCompiledComponents } from '@angular/core';
3
3
  import { renderModule, renderApplication, INITIAL_CONFIG, ɵINTERNAL_SERVER_PLATFORM_PROVIDERS as _INTERNAL_SERVER_PLATFORM_PROVIDERS, ɵSERVER_CONTEXT as _SERVER_CONTEXT } from '@angular/platform-server';
4
4
  import { ɵloadChildren as _loadChildren, Router } from '@angular/router';
5
5
  import Critters from '../third_party/critters/index.js';
6
6
 
7
+ /**
8
+ * Manages server-side assets.
9
+ */
10
+ class ServerAssets {
11
+ /**
12
+ * Creates an instance of ServerAsset.
13
+ *
14
+ * @param manifest - The manifest containing the server assets.
15
+ */
16
+ constructor(manifest) {
17
+ this.manifest = manifest;
18
+ }
19
+ /**
20
+ * Retrieves the content of a server-side asset using its path.
21
+ *
22
+ * @param path - The path to the server asset.
23
+ * @returns A promise that resolves to the asset content as a string.
24
+ * @throws Error If the asset path is not found in the manifest, an error is thrown.
25
+ */
26
+ async getServerAsset(path) {
27
+ const asset = this.manifest.assets.get(path);
28
+ if (!asset) {
29
+ throw new Error(`Server asset '${path}' does not exist.`);
30
+ }
31
+ return asset();
32
+ }
33
+ /**
34
+ * Retrieves and caches the content of 'index.server.html'.
35
+ *
36
+ * @returns A promise that resolves to the content of 'index.server.html'.
37
+ * @throws Error If there is an issue retrieving the asset.
38
+ */
39
+ getIndexServerHtml() {
40
+ return this.getServerAsset('index.server.html');
41
+ }
42
+ }
43
+
7
44
  /**
8
45
  * Custom implementation of the Angular Console service that filters out specific log messages.
9
46
  *
@@ -34,6 +71,59 @@ class Console extends _Console {
34
71
  }
35
72
  }
36
73
 
74
+ /**
75
+ * The Angular app manifest object.
76
+ * This is used internally to store the current Angular app manifest.
77
+ */
78
+ let angularAppManifest;
79
+ /**
80
+ * Sets the Angular app manifest.
81
+ *
82
+ * @param manifest - The manifest object to set for the Angular application.
83
+ */
84
+ function setAngularAppManifest(manifest) {
85
+ angularAppManifest = manifest;
86
+ }
87
+ /**
88
+ * Gets the Angular app manifest.
89
+ *
90
+ * @returns The Angular app manifest.
91
+ * @throws Will throw an error if the Angular app manifest is not set.
92
+ */
93
+ function getAngularAppManifest() {
94
+ if (!angularAppManifest) {
95
+ throw new Error('Angular app manifest is not set. ' +
96
+ `Please ensure you are using the '@angular/build:application' builder to build your server application.`);
97
+ }
98
+ return angularAppManifest;
99
+ }
100
+ /**
101
+ * The Angular app engine manifest object.
102
+ * This is used internally to store the current Angular app engine manifest.
103
+ */
104
+ let angularAppEngineManifest;
105
+ /**
106
+ * Sets the Angular app engine manifest.
107
+ *
108
+ * @param manifest - The engine manifest object to set.
109
+ */
110
+ function setAngularAppEngineManifest(manifest) {
111
+ angularAppEngineManifest = manifest;
112
+ }
113
+ /**
114
+ * Gets the Angular app engine manifest.
115
+ *
116
+ * @returns The Angular app engine manifest.
117
+ * @throws Will throw an error if the Angular app engine manifest is not set.
118
+ */
119
+ function getAngularAppEngineManifest() {
120
+ if (!angularAppEngineManifest) {
121
+ throw new Error('Angular app engine manifest is not set. ' +
122
+ `Please ensure you are using the '@angular/build:application' builder to build your server application.`);
123
+ }
124
+ return angularAppEngineManifest;
125
+ }
126
+
37
127
  /**
38
128
  * Removes the trailing slash from a URL if it exists.
39
129
  *
@@ -149,335 +239,11 @@ function renderAngular(html, bootstrap, url, platformProviders) {
149
239
  * Angular modules are identified by the presence of the `ɵmod` static property.
150
240
  * This function helps distinguish between Angular modules and bootstrap functions.
151
241
  *
152
- * @param value - The value to be checked.
153
- * @returns True if the value is an Angular module (i.e., it has the `ɵmod` property), false otherwise.
154
- */
155
- function isNgModule(value) {
156
- return 'ɵmod' in value;
157
- }
158
-
159
- /**
160
- * Recursively traverses the Angular router configuration to retrieve routes.
161
- *
162
- * Iterates through the router configuration, yielding each route along with its potential
163
- * redirection or error status. Handles nested routes and lazy-loaded child routes.
164
- *
165
- * @param options - An object containing the parameters for traversing routes.
166
- * @returns An async iterator yielding `RouteResult` objects.
167
- */
168
- async function* traverseRoutesConfig(options) {
169
- const { routes, compiler, parentInjector, parentRoute } = options;
170
- for (const route of routes) {
171
- const { path = '', redirectTo, loadChildren, children } = route;
172
- const currentRoutePath = joinUrlParts(parentRoute, path);
173
- yield {
174
- route: currentRoutePath,
175
- redirectTo: typeof redirectTo === 'string'
176
- ? resolveRedirectTo(currentRoutePath, redirectTo)
177
- : undefined,
178
- };
179
- if (children?.length) {
180
- // Recursively process child routes.
181
- yield* traverseRoutesConfig({
182
- routes: children,
183
- compiler,
184
- parentInjector,
185
- parentRoute: currentRoutePath,
186
- });
187
- }
188
- if (loadChildren) {
189
- // Load and process lazy-loaded child routes.
190
- const loadedChildRoutes = await _loadChildren(route, compiler, parentInjector).toPromise();
191
- if (loadedChildRoutes) {
192
- const { routes: childRoutes, injector = parentInjector } = loadedChildRoutes;
193
- yield* traverseRoutesConfig({
194
- routes: childRoutes,
195
- compiler,
196
- parentInjector: injector,
197
- parentRoute: currentRoutePath,
198
- });
199
- }
200
- }
201
- }
202
- }
203
- /**
204
- * Resolves the `redirectTo` property for a given route.
205
- *
206
- * This function processes the `redirectTo` property to ensure that it correctly
207
- * resolves relative to the current route path. If `redirectTo` is an absolute path,
208
- * it is returned as is. If it is a relative path, it is resolved based on the current route path.
209
- *
210
- * @param routePath - The current route path.
211
- * @param redirectTo - The target path for redirection.
212
- * @returns The resolved redirect path as a string.
213
- */
214
- function resolveRedirectTo(routePath, redirectTo) {
215
- if (redirectTo[0] === '/') {
216
- // If the redirectTo path is absolute, return it as is.
217
- return redirectTo;
218
- }
219
- // Resolve relative redirectTo based on the current route path.
220
- const segments = routePath.split('/');
221
- segments.pop(); // Remove the last segment to make it relative.
222
- return joinUrlParts(...segments, redirectTo);
223
- }
224
- /**
225
- * Retrieves routes from the given Angular application.
226
- *
227
- * This function initializes an Angular platform, bootstraps the application or module,
228
- * and retrieves routes from the Angular router configuration. It handles both module-based
229
- * and function-based bootstrapping. It yields the resulting routes as `RouteResult` objects.
230
- *
231
- * @param bootstrap - A function that returns a promise resolving to an `ApplicationRef` or an Angular module to bootstrap.
232
- * @param document - The initial HTML document used for server-side rendering.
233
- * This document is necessary to render the application on the server.
234
- * @param url - The URL for server-side rendering. The URL is used to configure `ServerPlatformLocation`. This configuration is crucial
235
- * for ensuring that API requests for relative paths succeed, which is essential for accurate route extraction.
236
- * See:
237
- * - https://github.com/angular/angular/blob/d608b857c689d17a7ffa33bbb510301014d24a17/packages/platform-server/src/location.ts#L51
238
- * - https://github.com/angular/angular/blob/6882cc7d9eed26d3caeedca027452367ba25f2b9/packages/platform-server/src/http.ts#L44
239
- * @returns A promise that resolves to an object of type `AngularRouterConfigResult`.
240
- */
241
- async function getRoutesFromAngularRouterConfig(bootstrap, document, url) {
242
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
243
- // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
244
- // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
245
- // See: https://github.com/angular/angular-cli/issues/25924
246
- _resetCompiledComponents();
247
- }
248
- const { protocol, host } = url;
249
- // Create and initialize the Angular platform for server-side rendering.
250
- const platformRef = createPlatformFactory(platformCore, 'server', [
251
- {
252
- provide: INITIAL_CONFIG,
253
- useValue: { document, url: `${protocol}//${host}/` },
254
- },
255
- {
256
- provide: _Console,
257
- useFactory: () => new Console(),
258
- },
259
- ..._INTERNAL_SERVER_PLATFORM_PROVIDERS,
260
- ])();
261
- try {
262
- let applicationRef;
263
- if (isNgModule(bootstrap)) {
264
- const moduleRef = await platformRef.bootstrapModule(bootstrap);
265
- applicationRef = moduleRef.injector.get(ApplicationRef);
266
- }
267
- else {
268
- applicationRef = await bootstrap();
269
- }
270
- // Wait until the application is stable.
271
- await _whenStable(applicationRef);
272
- const injector = applicationRef.injector;
273
- const router = injector.get(Router);
274
- const routesResults = [];
275
- if (router.config.length) {
276
- const compiler = injector.get(Compiler);
277
- // Retrieve all routes from the Angular router configuration.
278
- const traverseRoutes = traverseRoutesConfig({
279
- routes: router.config,
280
- compiler,
281
- parentInjector: injector,
282
- parentRoute: '',
283
- });
284
- for await (const result of traverseRoutes) {
285
- routesResults.push(result);
286
- }
287
- }
288
- else {
289
- routesResults.push({ route: '' });
290
- }
291
- const baseHref = injector.get(APP_BASE_HREF, null, { optional: true }) ??
292
- injector.get(PlatformLocation).getBaseHrefFromDOM();
293
- return {
294
- baseHref,
295
- routes: routesResults,
296
- };
297
- }
298
- finally {
299
- platformRef.destroy();
300
- }
301
- }
302
-
303
- /**
304
- * Manages server-side assets.
305
- */
306
- class ServerAssets {
307
- /**
308
- * Creates an instance of ServerAsset.
309
- *
310
- * @param manifest - The manifest containing the server assets.
311
- */
312
- constructor(manifest) {
313
- this.manifest = manifest;
314
- }
315
- /**
316
- * Retrieves the content of a server-side asset using its path.
317
- *
318
- * @param path - The path to the server asset.
319
- * @returns A promise that resolves to the asset content as a string.
320
- * @throws Error If the asset path is not found in the manifest, an error is thrown.
321
- */
322
- async getServerAsset(path) {
323
- const asset = this.manifest.assets.get(path);
324
- if (!asset) {
325
- throw new Error(`Server asset '${path}' does not exist.`);
326
- }
327
- return asset();
328
- }
329
- /**
330
- * Retrieves and caches the content of 'index.server.html'.
331
- *
332
- * @returns A promise that resolves to the content of 'index.server.html'.
333
- * @throws Error If there is an issue retrieving the asset.
334
- */
335
- getIndexServerHtml() {
336
- return this.getServerAsset('index.server.html');
337
- }
338
- }
339
-
340
- /**
341
- * Manages a collection of hooks and provides methods to register and execute them.
342
- * Hooks are functions that can be invoked with specific arguments to allow modifications or enhancements.
343
- */
344
- class Hooks {
345
- constructor() {
346
- /**
347
- * A map of hook names to arrays of hook functions.
348
- * Each hook name can have multiple associated functions, which are executed in sequence.
349
- */
350
- this.store = new Map();
351
- }
352
- /**
353
- * Executes all hooks associated with the specified name, passing the given argument to each hook function.
354
- * The hooks are invoked sequentially, and the argument may be modified by each hook.
355
- *
356
- * @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
357
- * @param name - The name of the hook whose functions will be executed.
358
- * @param context - The input value to be passed to each hook function. The value is mutated by each hook function.
359
- * @returns A promise that resolves once all hook functions have been executed.
360
- *
361
- * @example
362
- * ```typescript
363
- * const hooks = new Hooks();
364
- * hooks.on('html:transform:pre', async (ctx) => {
365
- * ctx.html = ctx.html.replace(/foo/g, 'bar');
366
- * return ctx.html;
367
- * });
368
- * const result = await hooks.run('html:transform:pre', { html: '<div>foo</div>' });
369
- * console.log(result); // '<div>bar</div>'
370
- * ```
371
- * @internal
372
- */
373
- async run(name, context) {
374
- const hooks = this.store.get(name);
375
- switch (name) {
376
- case 'html:transform:pre': {
377
- if (!hooks) {
378
- return context.html;
379
- }
380
- const ctx = { ...context };
381
- for (const hook of hooks) {
382
- ctx.html = await hook(ctx);
383
- }
384
- return ctx.html;
385
- }
386
- default:
387
- throw new Error(`Running hook "${name}" is not supported.`);
388
- }
389
- }
390
- /**
391
- * Registers a new hook function under the specified hook name.
392
- * This function should be a function that takes an argument of type `T` and returns a `string` or `Promise<string>`.
393
- *
394
- * @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
395
- * @param name - The name of the hook under which the function will be registered.
396
- * @param handler - A function to be executed when the hook is triggered. The handler will be called with an argument
397
- * that may be modified by the hook functions.
398
- *
399
- * @remarks
400
- * - If there are existing handlers registered under the given hook name, the new handler will be added to the list.
401
- * - If no handlers are registered under the given hook name, a new list will be created with the handler as its first element.
402
- *
403
- * @example
404
- * ```typescript
405
- * hooks.on('html:transform:pre', async (ctx) => {
406
- * return ctx.html.replace(/foo/g, 'bar');
407
- * });
408
- * ```
409
- */
410
- on(name, handler) {
411
- const hooks = this.store.get(name);
412
- if (hooks) {
413
- hooks.push(handler);
414
- }
415
- else {
416
- this.store.set(name, [handler]);
417
- }
418
- }
419
- /**
420
- * Checks if there are any hooks registered under the specified name.
421
- *
422
- * @param name - The name of the hook to check.
423
- * @returns `true` if there are hooks registered under the specified name, otherwise `false`.
424
- */
425
- has(name) {
426
- return !!this.store.get(name)?.length;
427
- }
428
- }
429
-
430
- /**
431
- * The Angular app manifest object.
432
- * This is used internally to store the current Angular app manifest.
433
- */
434
- let angularAppManifest;
435
- /**
436
- * Sets the Angular app manifest.
437
- *
438
- * @param manifest - The manifest object to set for the Angular application.
439
- */
440
- function setAngularAppManifest(manifest) {
441
- angularAppManifest = manifest;
442
- }
443
- /**
444
- * Gets the Angular app manifest.
445
- *
446
- * @returns The Angular app manifest.
447
- * @throws Will throw an error if the Angular app manifest is not set.
448
- */
449
- function getAngularAppManifest() {
450
- if (!angularAppManifest) {
451
- throw new Error('Angular app manifest is not set. ' +
452
- `Please ensure you are using the '@angular/build:application' builder to build your server application.`);
453
- }
454
- return angularAppManifest;
455
- }
456
- /**
457
- * The Angular app engine manifest object.
458
- * This is used internally to store the current Angular app engine manifest.
459
- */
460
- let angularAppEngineManifest;
461
- /**
462
- * Sets the Angular app engine manifest.
463
- *
464
- * @param manifest - The engine manifest object to set.
465
- */
466
- function setAngularAppEngineManifest(manifest) {
467
- angularAppEngineManifest = manifest;
468
- }
469
- /**
470
- * Gets the Angular app engine manifest.
471
- *
472
- * @returns The Angular app engine manifest.
473
- * @throws Will throw an error if the Angular app engine manifest is not set.
474
- */
475
- function getAngularAppEngineManifest() {
476
- if (!angularAppEngineManifest) {
477
- throw new Error('Angular app engine manifest is not set. ' +
478
- `Please ensure you are using the '@angular/build:application' builder to build your server application.`);
479
- }
480
- return angularAppEngineManifest;
242
+ * @param value - The value to be checked.
243
+ * @returns True if the value is an Angular module (i.e., it has the `ɵmod` property), false otherwise.
244
+ */
245
+ function isNgModule(value) {
246
+ return 'ɵmod' in value;
481
247
  }
482
248
 
483
249
  /**
@@ -655,6 +421,259 @@ class RouteTree {
655
421
  }
656
422
  }
657
423
 
424
+ /**
425
+ * Recursively traverses the Angular router configuration to retrieve routes.
426
+ *
427
+ * Iterates through the router configuration, yielding each route along with its potential
428
+ * redirection or error status. Handles nested routes and lazy-loaded child routes.
429
+ *
430
+ * @param options - An object containing the parameters for traversing routes.
431
+ * @returns An async iterator yielding `RouteResult` objects.
432
+ */
433
+ async function* traverseRoutesConfig(options) {
434
+ const { routes, compiler, parentInjector, parentRoute } = options;
435
+ for (const route of routes) {
436
+ const { path = '', redirectTo, loadChildren, children } = route;
437
+ const currentRoutePath = joinUrlParts(parentRoute, path);
438
+ yield {
439
+ route: currentRoutePath,
440
+ redirectTo: typeof redirectTo === 'string'
441
+ ? resolveRedirectTo(currentRoutePath, redirectTo)
442
+ : undefined,
443
+ };
444
+ if (children?.length) {
445
+ // Recursively process child routes.
446
+ yield* traverseRoutesConfig({
447
+ routes: children,
448
+ compiler,
449
+ parentInjector,
450
+ parentRoute: currentRoutePath,
451
+ });
452
+ }
453
+ if (loadChildren) {
454
+ // Load and process lazy-loaded child routes.
455
+ const loadedChildRoutes = await _loadChildren(route, compiler, parentInjector).toPromise();
456
+ if (loadedChildRoutes) {
457
+ const { routes: childRoutes, injector = parentInjector } = loadedChildRoutes;
458
+ yield* traverseRoutesConfig({
459
+ routes: childRoutes,
460
+ compiler,
461
+ parentInjector: injector,
462
+ parentRoute: currentRoutePath,
463
+ });
464
+ }
465
+ }
466
+ }
467
+ }
468
+ /**
469
+ * Resolves the `redirectTo` property for a given route.
470
+ *
471
+ * This function processes the `redirectTo` property to ensure that it correctly
472
+ * resolves relative to the current route path. If `redirectTo` is an absolute path,
473
+ * it is returned as is. If it is a relative path, it is resolved based on the current route path.
474
+ *
475
+ * @param routePath - The current route path.
476
+ * @param redirectTo - The target path for redirection.
477
+ * @returns The resolved redirect path as a string.
478
+ */
479
+ function resolveRedirectTo(routePath, redirectTo) {
480
+ if (redirectTo[0] === '/') {
481
+ // If the redirectTo path is absolute, return it as is.
482
+ return redirectTo;
483
+ }
484
+ // Resolve relative redirectTo based on the current route path.
485
+ const segments = routePath.split('/');
486
+ segments.pop(); // Remove the last segment to make it relative.
487
+ return joinUrlParts(...segments, redirectTo);
488
+ }
489
+ /**
490
+ * Retrieves routes from the given Angular application.
491
+ *
492
+ * This function initializes an Angular platform, bootstraps the application or module,
493
+ * and retrieves routes from the Angular router configuration. It handles both module-based
494
+ * and function-based bootstrapping. It yields the resulting routes as `RouteResult` objects.
495
+ *
496
+ * @param bootstrap - A function that returns a promise resolving to an `ApplicationRef` or an Angular module to bootstrap.
497
+ * @param document - The initial HTML document used for server-side rendering.
498
+ * This document is necessary to render the application on the server.
499
+ * @param url - The URL for server-side rendering. The URL is used to configure `ServerPlatformLocation`. This configuration is crucial
500
+ * for ensuring that API requests for relative paths succeed, which is essential for accurate route extraction.
501
+ * See:
502
+ * - https://github.com/angular/angular/blob/d608b857c689d17a7ffa33bbb510301014d24a17/packages/platform-server/src/location.ts#L51
503
+ * - https://github.com/angular/angular/blob/6882cc7d9eed26d3caeedca027452367ba25f2b9/packages/platform-server/src/http.ts#L44
504
+ * @returns A promise that resolves to an object of type `AngularRouterConfigResult`.
505
+ */
506
+ async function getRoutesFromAngularRouterConfig(bootstrap, document, url) {
507
+ const { protocol, host } = url;
508
+ // Create and initialize the Angular platform for server-side rendering.
509
+ const platformRef = createPlatformFactory(platformCore, 'server', [
510
+ {
511
+ provide: INITIAL_CONFIG,
512
+ useValue: { document, url: `${protocol}//${host}/` },
513
+ },
514
+ {
515
+ provide: _Console,
516
+ useFactory: () => new Console(),
517
+ },
518
+ ..._INTERNAL_SERVER_PLATFORM_PROVIDERS,
519
+ ])();
520
+ try {
521
+ let applicationRef;
522
+ if (isNgModule(bootstrap)) {
523
+ const moduleRef = await platformRef.bootstrapModule(bootstrap);
524
+ applicationRef = moduleRef.injector.get(ApplicationRef);
525
+ }
526
+ else {
527
+ applicationRef = await bootstrap();
528
+ }
529
+ // Wait until the application is stable.
530
+ await _whenStable(applicationRef);
531
+ const injector = applicationRef.injector;
532
+ const router = injector.get(Router);
533
+ const routesResults = [];
534
+ if (router.config.length) {
535
+ const compiler = injector.get(Compiler);
536
+ // Retrieve all routes from the Angular router configuration.
537
+ const traverseRoutes = traverseRoutesConfig({
538
+ routes: router.config,
539
+ compiler,
540
+ parentInjector: injector,
541
+ parentRoute: '',
542
+ });
543
+ for await (const result of traverseRoutes) {
544
+ routesResults.push(result);
545
+ }
546
+ }
547
+ else {
548
+ routesResults.push({ route: '' });
549
+ }
550
+ const baseHref = injector.get(APP_BASE_HREF, null, { optional: true }) ??
551
+ injector.get(PlatformLocation).getBaseHrefFromDOM();
552
+ return {
553
+ baseHref,
554
+ routes: routesResults,
555
+ };
556
+ }
557
+ finally {
558
+ platformRef.destroy();
559
+ }
560
+ }
561
+ /**
562
+ * Asynchronously extracts routes from the Angular application configuration
563
+ * and creates a `RouteTree` to manage server-side routing.
564
+ *
565
+ * @param url - The URL for server-side rendering. The URL is used to configure `ServerPlatformLocation`. This configuration is crucial
566
+ * for ensuring that API requests for relative paths succeed, which is essential for accurate route extraction.
567
+ * See:
568
+ * - https://github.com/angular/angular/blob/d608b857c689d17a7ffa33bbb510301014d24a17/packages/platform-server/src/location.ts#L51
569
+ * - https://github.com/angular/angular/blob/6882cc7d9eed26d3caeedca027452367ba25f2b9/packages/platform-server/src/http.ts#L44
570
+ * @param manifest - An optional `AngularAppManifest` that contains the application's routing and configuration details.
571
+ * If not provided, the default manifest is retrieved using `getAngularAppManifest()`.
572
+ *
573
+ * @returns A promise that resolves to a populated `RouteTree` containing all extracted routes from the Angular application.
574
+ */
575
+ async function extractRoutesAndCreateRouteTree(url, manifest = getAngularAppManifest()) {
576
+ const routeTree = new RouteTree();
577
+ const document = await new ServerAssets(manifest).getIndexServerHtml();
578
+ const { baseHref, routes } = await getRoutesFromAngularRouterConfig(manifest.bootstrap(), document, url);
579
+ for (let { route, redirectTo } of routes) {
580
+ route = joinUrlParts(baseHref, route);
581
+ redirectTo = redirectTo === undefined ? undefined : joinUrlParts(baseHref, redirectTo);
582
+ routeTree.insert(route, { redirectTo });
583
+ }
584
+ return routeTree;
585
+ }
586
+
587
+ /**
588
+ * Manages a collection of hooks and provides methods to register and execute them.
589
+ * Hooks are functions that can be invoked with specific arguments to allow modifications or enhancements.
590
+ */
591
+ class Hooks {
592
+ constructor() {
593
+ /**
594
+ * A map of hook names to arrays of hook functions.
595
+ * Each hook name can have multiple associated functions, which are executed in sequence.
596
+ */
597
+ this.store = new Map();
598
+ }
599
+ /**
600
+ * Executes all hooks associated with the specified name, passing the given argument to each hook function.
601
+ * The hooks are invoked sequentially, and the argument may be modified by each hook.
602
+ *
603
+ * @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
604
+ * @param name - The name of the hook whose functions will be executed.
605
+ * @param context - The input value to be passed to each hook function. The value is mutated by each hook function.
606
+ * @returns A promise that resolves once all hook functions have been executed.
607
+ *
608
+ * @example
609
+ * ```typescript
610
+ * const hooks = new Hooks();
611
+ * hooks.on('html:transform:pre', async (ctx) => {
612
+ * ctx.html = ctx.html.replace(/foo/g, 'bar');
613
+ * return ctx.html;
614
+ * });
615
+ * const result = await hooks.run('html:transform:pre', { html: '<div>foo</div>' });
616
+ * console.log(result); // '<div>bar</div>'
617
+ * ```
618
+ * @internal
619
+ */
620
+ async run(name, context) {
621
+ const hooks = this.store.get(name);
622
+ switch (name) {
623
+ case 'html:transform:pre': {
624
+ if (!hooks) {
625
+ return context.html;
626
+ }
627
+ const ctx = { ...context };
628
+ for (const hook of hooks) {
629
+ ctx.html = await hook(ctx);
630
+ }
631
+ return ctx.html;
632
+ }
633
+ default:
634
+ throw new Error(`Running hook "${name}" is not supported.`);
635
+ }
636
+ }
637
+ /**
638
+ * Registers a new hook function under the specified hook name.
639
+ * This function should be a function that takes an argument of type `T` and returns a `string` or `Promise<string>`.
640
+ *
641
+ * @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
642
+ * @param name - The name of the hook under which the function will be registered.
643
+ * @param handler - A function to be executed when the hook is triggered. The handler will be called with an argument
644
+ * that may be modified by the hook functions.
645
+ *
646
+ * @remarks
647
+ * - If there are existing handlers registered under the given hook name, the new handler will be added to the list.
648
+ * - If no handlers are registered under the given hook name, a new list will be created with the handler as its first element.
649
+ *
650
+ * @example
651
+ * ```typescript
652
+ * hooks.on('html:transform:pre', async (ctx) => {
653
+ * return ctx.html.replace(/foo/g, 'bar');
654
+ * });
655
+ * ```
656
+ */
657
+ on(name, handler) {
658
+ const hooks = this.store.get(name);
659
+ if (hooks) {
660
+ hooks.push(handler);
661
+ }
662
+ else {
663
+ this.store.set(name, [handler]);
664
+ }
665
+ }
666
+ /**
667
+ * Checks if there are any hooks registered under the specified name.
668
+ *
669
+ * @param name - The name of the hook to check.
670
+ * @returns `true` if there are hooks registered under the specified name, otherwise `false`.
671
+ */
672
+ has(name) {
673
+ return !!this.store.get(name)?.length;
674
+ }
675
+ }
676
+
658
677
  /**
659
678
  * Manages the application's server routing logic by building and maintaining a route tree.
660
679
  *
@@ -696,22 +715,11 @@ class ServerRouter {
696
715
  }
697
716
  // Create and store a new promise for the build process.
698
717
  // This prevents concurrent builds by re-using the same promise.
699
- ServerRouter.#extractionPromise ??= (async () => {
700
- try {
701
- const routeTree = new RouteTree();
702
- const document = await new ServerAssets(manifest).getIndexServerHtml();
703
- const { baseHref, routes } = await getRoutesFromAngularRouterConfig(manifest.bootstrap(), document, url);
704
- for (let { route, redirectTo } of routes) {
705
- route = joinUrlParts(baseHref, route);
706
- redirectTo = redirectTo === undefined ? undefined : joinUrlParts(baseHref, redirectTo);
707
- routeTree.insert(route, { redirectTo });
708
- }
709
- return new ServerRouter(routeTree);
710
- }
711
- finally {
712
- ServerRouter.#extractionPromise = undefined;
713
- }
714
- })();
718
+ ServerRouter.#extractionPromise ??= extractRoutesAndCreateRouteTree(url, manifest)
719
+ .then((routeTree) => new ServerRouter(routeTree))
720
+ .finally(() => {
721
+ ServerRouter.#extractionPromise = undefined;
722
+ });
715
723
  return ServerRouter.#extractionPromise;
716
724
  }
717
725
  /**
@@ -1016,12 +1024,6 @@ class AngularServerApp {
1016
1024
  useValue: responseInit,
1017
1025
  });
1018
1026
  }
1019
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
1020
- // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
1021
- // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
1022
- // See: https://github.com/angular/angular-cli/issues/25924
1023
- _resetCompiledComponents();
1024
- }
1025
1027
  const { manifest, hooks, assets } = this;
1026
1028
  let html = await assets.getIndexServerHtml();
1027
1029
  // Skip extra microtask if there are no pre hooks.
@@ -1058,6 +1060,12 @@ function getOrCreateAngularServerApp() {
1058
1060
  * typically when server configuration or application state needs to be refreshed.
1059
1061
  */
1060
1062
  function destroyAngularServerApp() {
1063
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
1064
+ // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
1065
+ // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
1066
+ // See: https://github.com/angular/angular-cli/issues/25924
1067
+ _resetCompiledComponents();
1068
+ }
1061
1069
  angularServerApp = undefined;
1062
1070
  }
1063
1071
 
@@ -1202,5 +1210,7 @@ function destroyAngularAppEngine() {
1202
1210
  angularAppEngine = undefined;
1203
1211
  }
1204
1212
 
1205
- export { destroyAngularAppEngine, getOrCreateAngularAppEngine, AngularAppEngine as ɵAngularAppEngine, InlineCriticalCssProcessor as ɵInlineCriticalCssProcessor, ServerRenderContext as ɵServerRenderContext, destroyAngularServerApp as ɵdestroyAngularServerApp, getOrCreateAngularServerApp as ɵgetOrCreateAngularServerApp, getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, setAngularAppManifest as ɵsetAngularAppManifest };
1213
+ // ɵgetRoutesFromAngularRouterConfig is only used by the Webpack based server builder.
1214
+
1215
+ export { destroyAngularAppEngine, getOrCreateAngularAppEngine, AngularAppEngine as ɵAngularAppEngine, InlineCriticalCssProcessor as ɵInlineCriticalCssProcessor, ServerRenderContext as ɵServerRenderContext, destroyAngularServerApp as ɵdestroyAngularServerApp, extractRoutesAndCreateRouteTree as ɵextractRoutesAndCreateRouteTree, getOrCreateAngularServerApp as ɵgetOrCreateAngularServerApp, getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig, setAngularAppEngineManifest as ɵsetAngularAppEngineManifest, setAngularAppManifest as ɵsetAngularAppManifest };
1206
1216
  //# sourceMappingURL=ssr.mjs.map