@angular/build 19.0.0-next.7 → 19.0.0-next.9

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.
Files changed (91) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +10 -9
  3. package/src/builders/application/execute-build.js +19 -2
  4. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  5. package/src/builders/application/execute-post-bundle.js +30 -11
  6. package/src/builders/application/i18n.d.ts +2 -2
  7. package/src/builders/application/i18n.js +4 -5
  8. package/src/builders/application/index.js +8 -5
  9. package/src/builders/application/options.d.ts +25 -1
  10. package/src/builders/application/options.js +31 -2
  11. package/src/builders/application/schema.d.ts +15 -0
  12. package/src/builders/application/schema.js +11 -1
  13. package/src/builders/application/schema.json +5 -0
  14. package/src/builders/application/setup-bundling.js +6 -3
  15. package/src/builders/dev-server/vite-server.d.ts +2 -1
  16. package/src/builders/dev-server/vite-server.js +71 -53
  17. package/src/builders/extract-i18n/application-extraction.js +3 -3
  18. package/src/tools/angular/angular-host.d.ts +2 -1
  19. package/src/tools/angular/angular-host.js +20 -1
  20. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  21. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  22. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  23. package/src/tools/angular/compilation/parallel-compilation.d.ts +1 -0
  24. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  25. package/src/tools/angular/compilation/parallel-worker.js +2 -1
  26. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  27. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  28. package/src/tools/babel/plugins/types.d.ts +20 -0
  29. package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -0
  30. package/src/tools/esbuild/angular/compiler-plugin.js +44 -4
  31. package/src/tools/esbuild/angular/component-stylesheets.d.ts +8 -3
  32. package/src/tools/esbuild/angular/component-stylesheets.js +46 -11
  33. package/src/tools/esbuild/application-code-bundle.d.ts +1 -0
  34. package/src/tools/esbuild/application-code-bundle.js +109 -2
  35. package/src/tools/esbuild/budget-stats.js +1 -1
  36. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  37. package/src/tools/esbuild/bundler-context.js +8 -4
  38. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  39. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  40. package/src/tools/esbuild/cache.d.ts +5 -0
  41. package/src/tools/esbuild/cache.js +7 -0
  42. package/src/tools/esbuild/compiler-plugin-options.js +3 -1
  43. package/src/tools/esbuild/i18n-inliner.js +2 -1
  44. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  45. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  46. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  47. package/src/tools/esbuild/javascript-transformer.js +5 -3
  48. package/src/tools/esbuild/utils.js +7 -3
  49. package/src/tools/vite/middlewares/assets-middleware.js +2 -5
  50. package/src/tools/vite/middlewares/html-fallback-middleware.js +22 -6
  51. package/src/tools/vite/middlewares/index.d.ts +1 -1
  52. package/src/tools/vite/middlewares/index.js +3 -2
  53. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -1
  54. package/src/tools/vite/middlewares/ssr-middleware.js +62 -15
  55. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  56. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -41
  57. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  58. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  59. package/src/tools/vite/plugins/index.d.ts +12 -0
  60. package/src/tools/vite/plugins/index.js +21 -0
  61. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +41 -0
  62. package/src/tools/vite/plugins/setup-middlewares-plugin.js +62 -0
  63. package/src/tools/vite/plugins/ssr-transform-plugin.d.ts +9 -0
  64. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  65. package/src/utils/environment-options.d.ts +2 -0
  66. package/src/utils/environment-options.js +5 -1
  67. package/src/utils/index-file/index-html-generator.js +5 -0
  68. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  69. package/src/utils/index-file/ngcm-attribute.js +37 -0
  70. package/src/utils/index-file/valid-self-closing-tags.js +28 -0
  71. package/src/utils/normalize-cache.js +1 -1
  72. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  73. package/src/utils/server-rendering/fetch-patch.js +2 -2
  74. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  75. package/src/utils/server-rendering/launch-server.js +63 -0
  76. package/src/utils/server-rendering/load-esm-from-memory.d.ts +7 -0
  77. package/src/utils/server-rendering/manifest.d.ts +8 -2
  78. package/src/utils/server-rendering/manifest.js +52 -12
  79. package/src/utils/server-rendering/models.d.ts +27 -0
  80. package/src/utils/server-rendering/models.js +22 -0
  81. package/src/utils/server-rendering/prerender.d.ts +6 -10
  82. package/src/utils/server-rendering/prerender.js +102 -63
  83. package/src/utils/server-rendering/render-worker.d.ts +4 -1
  84. package/src/utils/server-rendering/render-worker.js +13 -3
  85. package/src/utils/server-rendering/routes-extractor-worker.d.ts +6 -10
  86. package/src/utils/server-rendering/routes-extractor-worker.js +14 -5
  87. package/src/utils/server-rendering/utils.d.ts +11 -0
  88. package/src/utils/server-rendering/utils.js +17 -0
  89. package/src/tools/vite/angular-memory-plugin.d.ts +0 -22
  90. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  91. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -0,0 +1,14 @@
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 declare const DEFAULT_URL: import("url").URL;
9
+ /**
10
+ * Launches a server that handles local requests.
11
+ *
12
+ * @returns A promise that resolves to the URL of the running server.
13
+ */
14
+ export declare function launchServer(): Promise<URL>;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.DEFAULT_URL = void 0;
14
+ exports.launchServer = launchServer;
15
+ const node_assert_1 = __importDefault(require("node:assert"));
16
+ const node_http_1 = require("node:http");
17
+ const load_esm_1 = require("../load-esm");
18
+ const load_esm_from_memory_1 = require("./load-esm-from-memory");
19
+ const utils_1 = require("./utils");
20
+ exports.DEFAULT_URL = new URL('http://ng-localhost/');
21
+ /**
22
+ * Launches a server that handles local requests.
23
+ *
24
+ * @returns A promise that resolves to the URL of the running server.
25
+ */
26
+ async function launchServer() {
27
+ const { default: handler } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./server.mjs');
28
+ const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = await (0, load_esm_1.loadEsmModule)('@angular/ssr/node');
29
+ if (!(0, utils_1.isSsrNodeRequestHandler)(handler) && !(0, utils_1.isSsrRequestHandler)(handler)) {
30
+ return exports.DEFAULT_URL;
31
+ }
32
+ const server = (0, node_http_1.createServer)((req, res) => {
33
+ (async () => {
34
+ // handle request
35
+ if ((0, utils_1.isSsrNodeRequestHandler)(handler)) {
36
+ await handler(req, res, (e) => {
37
+ throw e;
38
+ });
39
+ }
40
+ else {
41
+ const webRes = await handler(createWebRequestFromNodeRequest(req));
42
+ if (webRes) {
43
+ await writeResponseToNodeResponse(webRes, res);
44
+ }
45
+ else {
46
+ res.statusCode = 501;
47
+ res.end('Not Implemented.');
48
+ }
49
+ }
50
+ })().catch((e) => {
51
+ res.statusCode = 500;
52
+ res.end('Internal Server Error.');
53
+ // eslint-disable-next-line no-console
54
+ console.error(e);
55
+ });
56
+ });
57
+ server.unref();
58
+ await new Promise((resolve) => server.listen(0, 'localhost', resolve));
59
+ const serverAddress = server.address();
60
+ (0, node_assert_1.default)(serverAddress, 'Server address should be defined.');
61
+ (0, node_assert_1.default)(typeof serverAddress !== 'string', 'Server address should not be a string.');
62
+ return new URL(`http://localhost:${serverAddress.port}/`);
63
+ }
@@ -15,5 +15,12 @@ interface MainServerBundleExports {
15
15
  ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree;
16
16
  ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp;
17
17
  }
18
+ /**
19
+ * Represents the exports available from the server bundle.
20
+ */
21
+ interface ServerBundleExports {
22
+ default: unknown;
23
+ }
18
24
  export declare function loadEsmModuleFromMemory(path: './main.server.mjs'): Promise<MainServerBundleExports>;
25
+ export declare function loadEsmModuleFromMemory(path: './server.mjs'): Promise<ServerBundleExports>;
19
26
  export {};
@@ -7,7 +7,9 @@
7
7
  */
8
8
  import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
9
9
  import type { BuildOutputFile } from '../../tools/esbuild/bundler-context';
10
+ import { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
10
11
  export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
12
+ export declare const SERVER_APP_ENGINE_MANIFEST_FILENAME = "angular-app-engine-manifest.mjs";
11
13
  /**
12
14
  * Generates the server manifest for the App Engine environment.
13
15
  *
@@ -19,9 +21,10 @@ export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
19
21
  * includes settings for inlining locales and determining the output structure.
20
22
  * @param baseHref - The base HREF for the application. This is used to set the base URL
21
23
  * for all relative URLs in the application.
24
+ * @param perenderedRoutes - A record mapping static paths to their associated data.
22
25
  * @returns A string representing the content of the SSR server manifest for App Engine.
23
26
  */
24
- export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined): string;
27
+ export declare function generateAngularServerAppEngineManifest(i18nOptions: NormalizedApplicationBuildOptions['i18nOptions'], baseHref: string | undefined, perenderedRoutes?: PrerenderedRoutesRecord | undefined): string;
25
28
  /**
26
29
  * Generates the server manifest for the standard Node.js environment.
27
30
  *
@@ -38,7 +41,10 @@ export declare function generateAngularServerAppEngineManifest(i18nOptions: Norm
38
41
  * in the server-side rendered pages.
39
42
  * @param routes - An optional array of route definitions for the application, used for
40
43
  * server-side rendering and routing.
44
+ * @param locale - An optional string representing the locale or language code to be used for
45
+ * the application, helping with localization and rendering content specific to the locale.
46
+ *
41
47
  * @returns A string representing the content of the SSR server manifest for the Node.js
42
48
  * environment.
43
49
  */
44
- export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined): string;
50
+ export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined): string;
@@ -7,12 +7,31 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.SERVER_APP_MANIFEST_FILENAME = void 0;
10
+ exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = exports.SERVER_APP_MANIFEST_FILENAME = void 0;
11
11
  exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
12
12
  exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
13
13
  const options_1 = require("../../builders/application/options");
14
14
  exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
15
+ exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
15
16
  const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
17
+ /**
18
+ * A mapping of unsafe characters to their escaped Unicode equivalents.
19
+ */
20
+ const UNSAFE_CHAR_MAP = {
21
+ '`': '\\`',
22
+ '$': '\\$',
23
+ '\\': '\\\\',
24
+ };
25
+ /**
26
+ * Escapes unsafe characters in a given string by replacing them with
27
+ * their Unicode escape sequences.
28
+ *
29
+ * @param str - The string to be escaped.
30
+ * @returns The escaped string where unsafe characters are replaced.
31
+ */
32
+ function escapeUnsafeChars(str) {
33
+ return str.replace(/[$`\\]/g, (c) => UNSAFE_CHAR_MAP[c]);
34
+ }
16
35
  /**
17
36
  * Generates the server manifest for the App Engine environment.
18
37
  *
@@ -24,26 +43,43 @@ const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
24
43
  * includes settings for inlining locales and determining the output structure.
25
44
  * @param baseHref - The base HREF for the application. This is used to set the base URL
26
45
  * for all relative URLs in the application.
46
+ * @param perenderedRoutes - A record mapping static paths to their associated data.
27
47
  * @returns A string representing the content of the SSR server manifest for App Engine.
28
48
  */
29
- function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
49
+ function generateAngularServerAppEngineManifest(i18nOptions, baseHref, perenderedRoutes = {}) {
30
50
  const entryPointsContent = [];
31
51
  if (i18nOptions.shouldInline) {
32
52
  for (const locale of i18nOptions.inlineLocales) {
33
53
  const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
34
- const localWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
35
- entryPointsContent.push(`['${localWithBaseHref}', () => import('${importPath}')]`);
54
+ let localeWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
55
+ // Remove leading and trailing slashes.
56
+ const start = localeWithBaseHref[0] === '/' ? 1 : 0;
57
+ const end = localeWithBaseHref[localeWithBaseHref.length - 1] === '/' ? -1 : undefined;
58
+ localeWithBaseHref = localeWithBaseHref.slice(start, end);
59
+ entryPointsContent.push(`['${localeWithBaseHref}', () => import('${importPath}')]`);
36
60
  }
37
61
  }
38
62
  else {
39
- entryPointsContent.push(`['/', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
63
+ entryPointsContent.push(`['', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
64
+ }
65
+ const staticHeaders = [];
66
+ for (const [path, { headers }] of Object.entries(perenderedRoutes)) {
67
+ if (!headers) {
68
+ continue;
69
+ }
70
+ const headersValues = [];
71
+ for (const [name, value] of Object.entries(headers)) {
72
+ headersValues.push(`['${name}', '${encodeURIComponent(value)}']`);
73
+ }
74
+ staticHeaders.push(`['${path}', [${headersValues.join(', ')}]]`);
40
75
  }
41
76
  const manifestContent = `
42
- {
43
- basePath: '${baseHref ?? '/'}',
44
- entryPoints: new Map([${entryPointsContent.join(', \n')}]),
45
- }
46
- `;
77
+ export default {
78
+ basePath: '${baseHref ?? '/'}',
79
+ entryPoints: new Map([${entryPointsContent.join(', \n')}]),
80
+ staticPathsHeaders: new Map([${staticHeaders.join(', \n')}]),
81
+ };
82
+ `;
47
83
  return manifestContent;
48
84
  }
49
85
  /**
@@ -62,16 +98,19 @@ function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
62
98
  * in the server-side rendered pages.
63
99
  * @param routes - An optional array of route definitions for the application, used for
64
100
  * server-side rendering and routing.
101
+ * @param locale - An optional string representing the locale or language code to be used for
102
+ * the application, helping with localization and rendering content specific to the locale.
103
+ *
65
104
  * @returns A string representing the content of the SSR server manifest for the Node.js
66
105
  * environment.
67
106
  */
68
- function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes) {
107
+ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale) {
69
108
  const serverAssetsContent = [];
70
109
  for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
71
110
  if (file.path === options_1.INDEX_HTML_SERVER ||
72
111
  file.path === options_1.INDEX_HTML_CSR ||
73
112
  (inlineCriticalCss && file.path.endsWith('.css'))) {
74
- serverAssetsContent.push(`['${file.path}', async () => ${JSON.stringify(file.text)}]`);
113
+ serverAssetsContent.push(`['${file.path}', async () => \`${escapeUnsafeChars(file.text)}\`]`);
75
114
  }
76
115
  }
77
116
  const manifestContent = `
@@ -80,6 +119,7 @@ export default {
80
119
  inlineCriticalCss: ${inlineCriticalCss},
81
120
  routes: ${JSON.stringify(routes, undefined, 2)},
82
121
  assets: new Map([${serverAssetsContent.join(', \n')}]),
122
+ locale: ${locale !== undefined ? `'${locale}'` : undefined},
83
123
  };
84
124
  `;
85
125
  return manifestContent;
@@ -0,0 +1,27 @@
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 type { RenderMode, ɵextractRoutesAndCreateRouteTree } from '@angular/ssr';
9
+ import { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
10
+ type Writeable<T extends readonly unknown[]> = T extends readonly (infer U)[] ? U[] : never;
11
+ export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData {
12
+ assetFiles: Record</** Destination */ string, /** Source */ string>;
13
+ }
14
+ export type SerializableRouteTreeNode = ReturnType<Awaited<ReturnType<typeof ɵextractRoutesAndCreateRouteTree>>['routeTree']['toObject']>;
15
+ export type WritableSerializableRouteTreeNode = Writeable<SerializableRouteTreeNode>;
16
+ export interface RoutersExtractorWorkerResult {
17
+ serializedRouteTree: SerializableRouteTreeNode;
18
+ errors: string[];
19
+ }
20
+ /**
21
+ * Local copy of `RenderMode` exported from `@angular/ssr`.
22
+ * This constant is needed to handle interop between CommonJS (CJS) and ES Modules (ESM) formats.
23
+ *
24
+ * It maps `RenderMode` enum values to their corresponding numeric identifiers.
25
+ */
26
+ export declare const RouteRenderMode: Record<keyof typeof RenderMode, RenderMode>;
27
+ export {};
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RouteRenderMode = void 0;
11
+ /**
12
+ * Local copy of `RenderMode` exported from `@angular/ssr`.
13
+ * This constant is needed to handle interop between CommonJS (CJS) and ES Modules (ESM) formats.
14
+ *
15
+ * It maps `RenderMode` enum values to their corresponding numeric identifiers.
16
+ */
17
+ exports.RouteRenderMode = {
18
+ AppShell: 0,
19
+ Server: 1,
20
+ Client: 2,
21
+ Prerender: 3,
22
+ };
@@ -5,16 +5,13 @@
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
+ import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
9
+ import { OutputMode } from '../../builders/application/schema';
8
10
  import { BuildOutputFile } from '../../tools/esbuild/bundler-context';
9
11
  import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result';
10
- import type { SerializableRouteTreeNode } from './routes-extractor-worker';
11
- interface PrerenderOptions {
12
- routesFile?: string;
13
- discoverRoutes?: boolean;
14
- }
15
- interface AppShellOptions {
16
- route?: string;
17
- }
12
+ import { SerializableRouteTreeNode } from './models';
13
+ type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions'];
14
+ type AppShellOptions = NormalizedApplicationBuildOptions['appShellOptions'];
18
15
  /**
19
16
  * Represents the output of a prerendering process.
20
17
  *
@@ -33,11 +30,10 @@ type PrerenderOutput = Record<string, {
33
30
  content: string;
34
31
  appShellRoute: boolean;
35
32
  }>;
36
- export declare function prerenderPages(workspaceRoot: string, baseHref: string, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, outputFiles: Readonly<BuildOutputFile[]>, assets: Readonly<BuildOutputAsset[]>, sourcemap?: boolean, maxThreads?: number, verbose?: boolean): Promise<{
33
+ export declare function prerenderPages(workspaceRoot: string, baseHref: string, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, outputFiles: Readonly<BuildOutputFile[]>, assets: Readonly<BuildOutputAsset[]>, outputMode: OutputMode | undefined, sourcemap?: boolean, maxThreads?: number): Promise<{
37
34
  output: PrerenderOutput;
38
35
  warnings: string[];
39
36
  errors: string[];
40
- prerenderedRoutes: Set<string>;
41
37
  serializableRouteTreeNode: SerializableRouteTreeNode;
42
38
  }>;
43
39
  export {};
@@ -11,16 +11,19 @@ exports.prerenderPages = prerenderPages;
11
11
  const promises_1 = require("node:fs/promises");
12
12
  const node_path_1 = require("node:path");
13
13
  const node_url_1 = require("node:url");
14
+ const schema_1 = require("../../builders/application/schema");
14
15
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
16
+ const error_1 = require("../error");
15
17
  const url_1 = require("../url");
16
18
  const worker_pool_1 = require("../worker-pool");
17
- async function prerenderPages(workspaceRoot, baseHref, appShellOptions = {}, prerenderOptions = {}, outputFiles, assets, sourcemap = false, maxThreads = 1, verbose = false) {
19
+ const models_1 = require("./models");
20
+ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerenderOptions, outputFiles, assets, outputMode, sourcemap = false, maxThreads = 1) {
18
21
  const outputFilesForWorker = {};
19
22
  const serverBundlesSourceMaps = new Map();
20
23
  const warnings = [];
21
24
  const errors = [];
22
25
  for (const { text, path, type } of outputFiles) {
23
- if (type !== bundler_context_1.BuildOutputFileType.Server) {
26
+ if (type !== bundler_context_1.BuildOutputFileType.ServerApplication && type !== bundler_context_1.BuildOutputFileType.ServerRoot) {
24
27
  continue;
25
28
  }
26
29
  // Contains the server runnable application code
@@ -48,39 +51,57 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions = {}, pre
48
51
  assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source;
49
52
  }
50
53
  // Get routes to prerender
51
- const { routes: allRoutes, warnings: routesWarnings, errors: routesErrors, serializableRouteTreeNode, } = await getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetsReversed, appShellOptions, prerenderOptions, sourcemap, verbose);
52
- if (routesErrors?.length) {
53
- errors.push(...routesErrors);
54
- }
55
- if (routesWarnings?.length) {
56
- warnings.push(...routesWarnings);
54
+ const { errors: extractionErrors, serializedRouteTree: serializableRouteTreeNode } = await getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetsReversed, appShellOptions, prerenderOptions, sourcemap, outputMode).catch((err) => {
55
+ return {
56
+ errors: [
57
+ `An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`,
58
+ ],
59
+ serializedRouteTree: [],
60
+ };
61
+ });
62
+ errors.push(...extractionErrors);
63
+ const serializableRouteTreeNodeForPrerender = [];
64
+ for (const metadata of serializableRouteTreeNode) {
65
+ if (outputMode !== schema_1.OutputMode.Static && metadata.redirectTo) {
66
+ // Skip redirects if output mode is not static.
67
+ continue;
68
+ }
69
+ if (metadata.route.includes('*')) {
70
+ // Skip catch all routes from prerender.
71
+ continue;
72
+ }
73
+ switch (metadata.renderMode) {
74
+ case undefined: /* Legacy building mode */
75
+ case models_1.RouteRenderMode.Prerender:
76
+ case models_1.RouteRenderMode.AppShell:
77
+ serializableRouteTreeNodeForPrerender.push(metadata);
78
+ break;
79
+ case models_1.RouteRenderMode.Server:
80
+ if (outputMode === schema_1.OutputMode.Static) {
81
+ errors.push(`Route '${metadata.route}' is configured with server render mode, but the build 'outputMode' is set to 'static'.`);
82
+ }
83
+ break;
84
+ }
57
85
  }
58
- if (allRoutes.size < 1 || errors.length > 0) {
86
+ if (!serializableRouteTreeNodeForPrerender.length || errors.length > 0) {
59
87
  return {
60
88
  errors,
61
89
  warnings,
62
90
  output: {},
63
91
  serializableRouteTreeNode,
64
- prerenderedRoutes: allRoutes,
65
92
  };
66
93
  }
67
94
  // Render routes
68
- const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions);
95
+ const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions, outputMode);
69
96
  errors.push(...renderingErrors);
70
97
  return {
71
98
  errors,
72
99
  warnings,
73
100
  output,
74
101
  serializableRouteTreeNode,
75
- prerenderedRoutes: allRoutes,
76
102
  };
77
103
  }
78
- class RoutesSet extends Set {
79
- add(value) {
80
- return super.add(addLeadingSlash(value));
81
- }
82
- }
83
- async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions) {
104
+ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions, outputMode) {
84
105
  const output = {};
85
106
  const errors = [];
86
107
  const workerExecArgv = [
@@ -93,27 +114,35 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
93
114
  }
94
115
  const renderWorker = new worker_pool_1.WorkerPool({
95
116
  filename: require.resolve('./render-worker'),
96
- maxThreads: Math.min(allRoutes.size, maxThreads),
117
+ maxThreads: Math.min(serializableRouteTreeNode.length, maxThreads),
97
118
  workerData: {
98
119
  workspaceRoot,
99
120
  outputFiles: outputFilesForWorker,
100
121
  assetFiles: assetFilesForWorker,
122
+ outputMode,
123
+ hasSsrEntry: !!outputFilesForWorker['/server.mjs'],
101
124
  },
102
125
  execArgv: workerExecArgv,
103
126
  });
104
127
  try {
105
128
  const renderingPromises = [];
106
- const appShellRoute = appShellOptions.route && addLeadingSlash(appShellOptions.route);
129
+ const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
107
130
  const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
108
- for (const route of allRoutes) {
131
+ for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
109
132
  // Remove base href from file output path.
110
133
  const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
111
- const render = renderWorker.run({ url: route });
134
+ const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
135
+ if (typeof redirectTo === 'string') {
136
+ output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false };
137
+ continue;
138
+ }
139
+ const isAppShellRoute = renderMode === models_1.RouteRenderMode.AppShell ||
140
+ // Legacy handling
141
+ (renderMode === undefined && appShellRoute === routeWithoutBaseHref);
142
+ const render = renderWorker.run({ url: route, isAppShellRoute });
112
143
  const renderResult = render
113
144
  .then((content) => {
114
145
  if (content !== null) {
115
- const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
116
- const isAppShellRoute = appShellRoute === routeWithoutBaseHref;
117
146
  output[outPath] = { content, appShellRoute: isAppShellRoute };
118
147
  }
119
148
  })
@@ -133,21 +162,24 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
133
162
  output,
134
163
  };
135
164
  }
136
- async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap, verbose) {
137
- const { routesFile, discoverRoutes } = prerenderOptions;
138
- const routes = new RoutesSet();
139
- const { route: appShellRoute } = appShellOptions;
140
- if (appShellRoute !== undefined) {
141
- routes.add((0, url_1.urlJoin)(baseHref, appShellRoute));
165
+ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap, outputMode) {
166
+ const { routesFile, discoverRoutes } = prerenderOptions ?? {};
167
+ const routes = [];
168
+ if (appShellOptions) {
169
+ routes.push({
170
+ route: (0, url_1.urlJoin)(baseHref, appShellOptions.route),
171
+ });
142
172
  }
143
173
  if (routesFile) {
144
174
  const routesFromFile = (await (0, promises_1.readFile)(routesFile, 'utf8')).split(/\r?\n/);
145
175
  for (const route of routesFromFile) {
146
- routes.add((0, url_1.urlJoin)(baseHref, route.trim()));
176
+ routes.push({
177
+ route: (0, url_1.urlJoin)(baseHref, route.trim()),
178
+ });
147
179
  }
148
180
  }
149
181
  if (!discoverRoutes) {
150
- return { routes, serializableRouteTreeNode: [] };
182
+ return { errors: [], serializedRouteTree: routes };
151
183
  }
152
184
  const workerExecArgv = [
153
185
  '--import',
@@ -164,42 +196,25 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
164
196
  workspaceRoot,
165
197
  outputFiles: outputFilesForWorker,
166
198
  assetFiles: assetFilesForWorker,
199
+ outputMode,
200
+ hasSsrEntry: !!outputFilesForWorker['/server.mjs'],
167
201
  },
168
202
  execArgv: workerExecArgv,
169
203
  });
170
- const errors = [];
171
- const { serializedRouteTree: serializableRouteTreeNode } = await renderWorker
172
- .run({})
173
- .catch((err) => {
174
- errors.push(`An error occurred while extracting routes.\n\n${err.stack}`);
175
- })
176
- .finally(() => {
177
- void renderWorker.destroy();
178
- });
179
- const skippedRedirects = [];
180
- const skippedOthers = [];
181
- for (const { route, redirectTo } of serializableRouteTreeNode) {
182
- if (redirectTo) {
183
- skippedRedirects.push(route);
184
- }
185
- else if (route.includes('*')) {
186
- skippedOthers.push(route);
187
- }
188
- else {
189
- routes.add(route);
190
- }
204
+ try {
205
+ const { serializedRouteTree, errors } = await renderWorker.run({});
206
+ return { errors, serializedRouteTree: [...routes, ...serializedRouteTree] };
191
207
  }
192
- let warnings;
193
- if (verbose) {
194
- if (skippedOthers.length) {
195
- (warnings ??= []).push('The following routes were skipped from prerendering because they contain routes with dynamic parameters:\n' +
196
- skippedOthers.join('\n'));
197
- }
198
- if (skippedRedirects.length) {
199
- (warnings ??= []).push('The following routes were skipped from prerendering because they contain redirects:\n', skippedRedirects.join('\n'));
200
- }
208
+ catch (err) {
209
+ (0, error_1.assertIsError)(err);
210
+ return {
211
+ errors: [`An error occurred while extracting routes.\n\n${err.stack}`],
212
+ serializedRouteTree: [],
213
+ };
214
+ }
215
+ finally {
216
+ void renderWorker.destroy();
201
217
  }
202
- return { routes, serializableRouteTreeNode, warnings };
203
218
  }
204
219
  function addLeadingSlash(value) {
205
220
  return value.charAt(0) === '/' ? value : '/' + value;
@@ -207,3 +222,27 @@ function addLeadingSlash(value) {
207
222
  function removeLeadingSlash(value) {
208
223
  return value.charAt(0) === '/' ? value.slice(1) : value;
209
224
  }
225
+ /**
226
+ * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
227
+ *
228
+ * This function creates a simple HTML page that performs a redirect using a meta tag.
229
+ * It includes a fallback link in case the meta-refresh doesn't work.
230
+ *
231
+ * @param url - The URL to which the page should redirect.
232
+ * @returns The HTML content of the static redirect page.
233
+ */
234
+ function generateRedirectStaticPage(url) {
235
+ return `
236
+ <!DOCTYPE html>
237
+ <html>
238
+ <head>
239
+ <meta charset="utf-8">
240
+ <title>Redirecting</title>
241
+ <meta http-equiv="refresh" content="0; url=${url}">
242
+ </head>
243
+ <body>
244
+ <pre>Redirecting to <a href="${url}">${url}</a></pre>
245
+ </body>
246
+ </html>
247
+ `.trim();
248
+ }
@@ -5,9 +5,12 @@
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
+ import type { OutputMode } from '../../builders/application/schema';
8
9
  import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
9
10
  export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
10
11
  assetFiles: Record</** Destination */ string, /** Source */ string>;
12
+ outputMode: OutputMode | undefined;
13
+ hasSsrEntry: boolean;
11
14
  }
12
15
  export interface RenderOptions {
13
16
  url: string;
@@ -16,5 +19,5 @@ export interface RenderOptions {
16
19
  * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
17
20
  */
18
21
  declare function renderPage({ url }: RenderOptions): Promise<string | null>;
19
- declare const _default: typeof renderPage;
22
+ declare const _default: Promise<typeof renderPage>;
20
23
  export default _default;
@@ -7,19 +7,29 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ const worker_threads_1 = require("worker_threads");
10
11
  const fetch_patch_1 = require("./fetch-patch");
12
+ const launch_server_1 = require("./launch-server");
11
13
  const load_esm_from_memory_1 = require("./load-esm-from-memory");
14
+ /**
15
+ * This is passed as workerData when setting up the worker via the `piscina` package.
16
+ */
17
+ const { outputMode, hasSsrEntry } = worker_threads_1.workerData;
18
+ let serverURL = launch_server_1.DEFAULT_URL;
12
19
  /**
13
20
  * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
14
21
  */
15
22
  async function renderPage({ url }) {
16
23
  const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
17
24
  const angularServerApp = getOrCreateAngularServerApp();
18
- const response = await angularServerApp.renderStatic(new URL(url, 'http://local-angular-prerender'), AbortSignal.timeout(30_000));
25
+ const response = await angularServerApp.renderStatic(new URL(url, serverURL), AbortSignal.timeout(30_000));
19
26
  return response ? response.text() : null;
20
27
  }
21
- function initialize() {
22
- (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
28
+ async function initialize() {
29
+ if (outputMode !== undefined && hasSsrEntry) {
30
+ serverURL = await (0, launch_server_1.launchServer)();
31
+ }
32
+ (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)(serverURL);
23
33
  return renderPage;
24
34
  }
25
35
  exports.default = initialize();