@angular/build 19.0.0-next.1 → 19.0.0-next.11

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 (129) hide show
  1. package/LICENSE +5 -5
  2. package/package.json +26 -22
  3. package/src/builders/application/build-action.js +9 -9
  4. package/src/builders/application/chunk-optimizer.js +1 -4
  5. package/src/builders/application/execute-build.js +19 -2
  6. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  7. package/src/builders/application/execute-post-bundle.js +58 -20
  8. package/src/builders/application/i18n.d.ts +2 -2
  9. package/src/builders/application/i18n.js +6 -16
  10. package/src/builders/application/index.js +8 -5
  11. package/src/builders/application/options.d.ts +38 -1
  12. package/src/builders/application/options.js +62 -4
  13. package/src/builders/application/results.d.ts +5 -3
  14. package/src/builders/application/schema.d.ts +72 -0
  15. package/src/builders/application/schema.js +29 -1
  16. package/src/builders/application/schema.json +38 -0
  17. package/src/builders/application/setup-bundling.js +12 -9
  18. package/src/builders/dev-server/internal.d.ts +0 -1
  19. package/src/builders/dev-server/internal.js +1 -3
  20. package/src/builders/dev-server/vite-server.d.ts +8 -2
  21. package/src/builders/dev-server/vite-server.js +132 -58
  22. package/src/builders/extract-i18n/application-extraction.js +3 -3
  23. package/src/tools/angular/angular-host.d.ts +2 -1
  24. package/src/tools/angular/angular-host.js +17 -1
  25. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  26. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  27. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  28. package/src/tools/angular/compilation/parallel-compilation.d.ts +2 -1
  29. package/src/tools/angular/compilation/parallel-compilation.js +4 -12
  30. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  31. package/src/tools/angular/compilation/parallel-worker.js +5 -2
  32. package/src/tools/babel/plugins/add-code-coverage.d.ts +14 -0
  33. package/src/tools/babel/plugins/add-code-coverage.js +44 -0
  34. package/src/tools/babel/plugins/types.d.ts +20 -0
  35. package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -0
  36. package/src/tools/esbuild/angular/compiler-plugin.js +69 -10
  37. package/src/tools/esbuild/angular/component-stylesheets.d.ts +10 -4
  38. package/src/tools/esbuild/angular/component-stylesheets.js +49 -12
  39. package/src/tools/esbuild/angular/file-reference-tracker.d.ts +1 -1
  40. package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
  41. package/src/tools/esbuild/angular/jit-plugin-callbacks.js +2 -2
  42. package/src/tools/esbuild/application-code-bundle.d.ts +2 -6
  43. package/src/tools/esbuild/application-code-bundle.js +233 -71
  44. package/src/tools/esbuild/budget-stats.js +1 -1
  45. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  46. package/src/tools/esbuild/bundler-context.js +24 -19
  47. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  48. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  49. package/src/tools/esbuild/cache.d.ts +6 -1
  50. package/src/tools/esbuild/cache.js +7 -0
  51. package/src/tools/esbuild/compiler-plugin-options.js +6 -1
  52. package/src/tools/esbuild/global-scripts.js +1 -1
  53. package/src/tools/esbuild/global-styles.js +3 -0
  54. package/src/tools/esbuild/i18n-inliner.js +4 -4
  55. package/src/tools/esbuild/javascript-transformer-worker.d.ts +1 -0
  56. package/src/tools/esbuild/javascript-transformer-worker.js +5 -1
  57. package/src/tools/esbuild/javascript-transformer.d.ts +2 -2
  58. package/src/tools/esbuild/javascript-transformer.js +7 -12
  59. package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
  60. package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
  61. package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
  62. package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
  63. package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
  64. package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
  65. package/src/tools/esbuild/utils.d.ts +9 -0
  66. package/src/tools/esbuild/utils.js +33 -4
  67. package/src/tools/sass/sass-service.js +11 -13
  68. package/src/tools/sass/worker.d.ts +13 -32
  69. package/src/tools/sass/worker.js +20 -0
  70. package/src/tools/vite/middlewares/assets-middleware.d.ts +1 -1
  71. package/src/tools/vite/middlewares/assets-middleware.js +43 -4
  72. package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
  73. package/src/tools/vite/middlewares/component-middleware.js +33 -0
  74. package/src/tools/vite/middlewares/headers-middleware.d.ts +19 -0
  75. package/src/tools/vite/middlewares/headers-middleware.js +34 -0
  76. package/src/tools/vite/middlewares/html-fallback-middleware.d.ts +1 -1
  77. package/src/tools/vite/middlewares/html-fallback-middleware.js +23 -7
  78. package/src/tools/vite/middlewares/index-html-middleware.js +1 -2
  79. package/src/tools/vite/middlewares/index.d.ts +3 -1
  80. package/src/tools/vite/middlewares/index.js +7 -2
  81. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -4
  82. package/src/tools/vite/middlewares/ssr-middleware.js +75 -43
  83. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  84. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -40
  85. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  86. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  87. package/src/tools/vite/plugins/index.d.ts +12 -0
  88. package/src/tools/vite/plugins/index.js +21 -0
  89. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +42 -0
  90. package/src/tools/vite/plugins/setup-middlewares-plugin.js +63 -0
  91. package/src/{utils/server-rendering/main-bundle-exports.js → tools/vite/plugins/ssr-transform-plugin.d.ts} +2 -2
  92. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  93. package/src/tools/vite/utils.d.ts +0 -3
  94. package/src/tools/vite/utils.js +0 -12
  95. package/src/typings.d.ts +26 -0
  96. package/src/utils/environment-options.d.ts +2 -0
  97. package/src/utils/environment-options.js +5 -1
  98. package/src/utils/index-file/index-html-generator.js +5 -0
  99. package/src/utils/index-file/inline-critical-css.js +43 -33
  100. package/src/utils/index-file/ngcm-attribute.d.ts +15 -0
  101. package/src/utils/index-file/ngcm-attribute.js +37 -0
  102. package/src/utils/index-file/valid-self-closing-tags.js +28 -0
  103. package/src/utils/normalize-cache.js +1 -1
  104. package/src/utils/server-rendering/fetch-patch.d.ts +1 -1
  105. package/src/utils/server-rendering/fetch-patch.js +5 -6
  106. package/src/utils/server-rendering/launch-server.d.ts +14 -0
  107. package/src/utils/server-rendering/launch-server.js +63 -0
  108. package/src/utils/server-rendering/load-esm-from-memory.d.ts +18 -2
  109. package/src/utils/server-rendering/manifest.d.ts +50 -0
  110. package/src/utils/server-rendering/manifest.js +126 -0
  111. package/src/utils/server-rendering/models.d.ts +27 -0
  112. package/src/utils/server-rendering/models.js +22 -0
  113. package/src/utils/server-rendering/prerender.d.ts +26 -10
  114. package/src/utils/server-rendering/prerender.js +122 -75
  115. package/src/utils/server-rendering/render-worker.d.ts +9 -8
  116. package/src/utils/server-rendering/render-worker.js +19 -14
  117. package/src/utils/server-rendering/routes-extractor-worker.d.ts +6 -10
  118. package/src/utils/server-rendering/routes-extractor-worker.js +16 -33
  119. package/src/utils/server-rendering/utils.d.ts +11 -0
  120. package/src/utils/server-rendering/utils.js +17 -0
  121. package/src/utils/supported-browsers.js +1 -0
  122. package/src/utils/worker-pool.d.ts +12 -0
  123. package/src/utils/worker-pool.js +43 -0
  124. package/src/tools/vite/angular-memory-plugin.d.ts +0 -21
  125. package/src/utils/server-rendering/main-bundle-exports.d.ts +0 -27
  126. package/src/utils/server-rendering/render-page.d.ts +0 -26
  127. package/src/utils/server-rendering/render-page.js +0 -114
  128. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  129. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -0,0 +1,126 @@
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.SERVER_APP_ENGINE_MANIFEST_FILENAME = exports.SERVER_APP_MANIFEST_FILENAME = void 0;
11
+ exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
12
+ exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
13
+ const options_1 = require("../../builders/application/options");
14
+ exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
15
+ exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
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
+ }
35
+ /**
36
+ * Generates the server manifest for the App Engine environment.
37
+ *
38
+ * This manifest is used to configure the server-side rendering (SSR) setup for the
39
+ * Angular application when deployed to Google App Engine. It includes the entry points
40
+ * for different locales and the base HREF for the application.
41
+ *
42
+ * @param i18nOptions - The internationalization options for the application build. This
43
+ * includes settings for inlining locales and determining the output structure.
44
+ * @param baseHref - The base HREF for the application. This is used to set the base URL
45
+ * for all relative URLs in the application.
46
+ * @param perenderedRoutes - A record mapping static paths to their associated data.
47
+ * @returns A string representing the content of the SSR server manifest for App Engine.
48
+ */
49
+ function generateAngularServerAppEngineManifest(i18nOptions, baseHref, perenderedRoutes = {}) {
50
+ const entryPointsContent = [];
51
+ if (i18nOptions.shouldInline) {
52
+ for (const locale of i18nOptions.inlineLocales) {
53
+ const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
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}')]`);
60
+ }
61
+ }
62
+ else {
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(', ')}]]`);
75
+ }
76
+ const manifestContent = `
77
+ export default {
78
+ basePath: '${baseHref ?? '/'}',
79
+ entryPoints: new Map([${entryPointsContent.join(', \n')}]),
80
+ staticPathsHeaders: new Map([${staticHeaders.join(', \n')}]),
81
+ };
82
+ `;
83
+ return manifestContent;
84
+ }
85
+ /**
86
+ * Generates the server manifest for the standard Node.js environment.
87
+ *
88
+ * This manifest is used to configure the server-side rendering (SSR) setup for the
89
+ * Angular application when running in a standard Node.js environment. It includes
90
+ * information about the bootstrap module, whether to inline critical CSS, and any
91
+ * additional HTML and CSS output files.
92
+ *
93
+ * @param additionalHtmlOutputFiles - A map of additional HTML output files generated
94
+ * during the build process, keyed by their file paths.
95
+ * @param outputFiles - An array of all output files from the build process, including
96
+ * JavaScript and CSS files.
97
+ * @param inlineCriticalCss - A boolean indicating whether critical CSS should be inlined
98
+ * in the server-side rendered pages.
99
+ * @param routes - An optional array of route definitions for the application, used for
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
+ *
104
+ * @returns A string representing the content of the SSR server manifest for the Node.js
105
+ * environment.
106
+ */
107
+ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale) {
108
+ const serverAssetsContent = [];
109
+ for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
110
+ if (file.path === options_1.INDEX_HTML_SERVER ||
111
+ file.path === options_1.INDEX_HTML_CSR ||
112
+ (inlineCriticalCss && file.path.endsWith('.css'))) {
113
+ serverAssetsContent.push(`['${file.path}', async () => \`${escapeUnsafeChars(file.text)}\`]`);
114
+ }
115
+ }
116
+ const manifestContent = `
117
+ export default {
118
+ bootstrap: () => import('./main.server.mjs').then(m => m.default),
119
+ inlineCriticalCss: ${inlineCriticalCss},
120
+ routes: ${JSON.stringify(routes, undefined, 2)},
121
+ assets: new Map([${serverAssetsContent.join(', \n')}]),
122
+ locale: ${locale !== undefined ? `'${locale}'` : undefined},
123
+ };
124
+ `;
125
+ return manifestContent;
126
+ }
@@ -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,19 +5,35 @@
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
- interface PrerenderOptions {
11
- routesFile?: string;
12
- discoverRoutes?: boolean;
13
- }
14
- interface AppShellOptions {
15
- route?: string;
16
- }
17
- export declare function prerenderPages(workspaceRoot: string, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, outputFiles: Readonly<BuildOutputFile[]>, assets: Readonly<BuildOutputAsset[]>, document: string, sourcemap?: boolean, inlineCriticalCss?: boolean, maxThreads?: number, verbose?: boolean): Promise<{
18
- output: Record<string, string>;
12
+ import { SerializableRouteTreeNode } from './models';
13
+ type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions'];
14
+ type AppShellOptions = NormalizedApplicationBuildOptions['appShellOptions'];
15
+ /**
16
+ * Represents the output of a prerendering process.
17
+ *
18
+ * The key is the file path, and the value is an object containing the following properties:
19
+ *
20
+ * - `content`: The HTML content or output generated for the corresponding file path.
21
+ * - `appShellRoute`: A boolean flag indicating whether the content is an app shell.
22
+ *
23
+ * @example
24
+ * {
25
+ * '/index.html': { content: '<html>...</html>', appShell: false },
26
+ * '/shell/index.html': { content: '<html>...</html>', appShellRoute: true }
27
+ * }
28
+ */
29
+ type PrerenderOutput = Record<string, {
30
+ content: string;
31
+ appShellRoute: boolean;
32
+ }>;
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<{
34
+ output: PrerenderOutput;
19
35
  warnings: string[];
20
36
  errors: string[];
21
- prerenderedRoutes: Set<string>;
37
+ serializableRouteTreeNode: SerializableRouteTreeNode;
22
38
  }>;
23
39
  export {};
@@ -6,29 +6,31 @@
6
6
  * Use of this source code is governed by an MIT-style license that can be
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
- var __importDefault = (this && this.__importDefault) || function (mod) {
10
- return (mod && mod.__esModule) ? mod : { "default": mod };
11
- };
12
9
  Object.defineProperty(exports, "__esModule", { value: true });
13
10
  exports.prerenderPages = prerenderPages;
14
11
  const promises_1 = require("node:fs/promises");
15
12
  const node_path_1 = require("node:path");
16
13
  const node_url_1 = require("node:url");
17
- const piscina_1 = __importDefault(require("piscina"));
14
+ const schema_1 = require("../../builders/application/schema");
18
15
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
19
- async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOptions = {}, outputFiles, assets, document, sourcemap = false, inlineCriticalCss = false, maxThreads = 1, verbose = false) {
16
+ const error_1 = require("../error");
17
+ const url_1 = require("../url");
18
+ const worker_pool_1 = require("../worker-pool");
19
+ const models_1 = require("./models");
20
+ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerenderOptions, outputFiles, assets, outputMode, sourcemap = false, maxThreads = 1) {
20
21
  const outputFilesForWorker = {};
21
22
  const serverBundlesSourceMaps = new Map();
22
23
  const warnings = [];
23
24
  const errors = [];
24
25
  for (const { text, path, type } of outputFiles) {
25
- const fileExt = (0, node_path_1.extname)(path);
26
- if (type === bundler_context_1.BuildOutputFileType.Server && fileExt === '.map') {
26
+ if (type !== bundler_context_1.BuildOutputFileType.ServerApplication && type !== bundler_context_1.BuildOutputFileType.ServerRoot) {
27
+ continue;
28
+ }
29
+ // Contains the server runnable application code
30
+ if ((0, node_path_1.extname)(path) === '.map') {
27
31
  serverBundlesSourceMaps.set(path.slice(0, -4), text);
28
32
  }
29
- else if (type === bundler_context_1.BuildOutputFileType.Server || // Contains the server runnable application code
30
- (type === bundler_context_1.BuildOutputFileType.Browser && fileExt === '.css') // Global styles for critical CSS inlining.
31
- ) {
33
+ else {
32
34
  outputFilesForWorker[path] = text;
33
35
  }
34
36
  }
@@ -49,40 +51,58 @@ async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOpti
49
51
  assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source;
50
52
  }
51
53
  // Get routes to prerender
52
- const { routes: allRoutes, warnings: routesWarnings, errors: routesErrors, } = await getAllRoutes(workspaceRoot, outputFilesForWorker, assetsReversed, document, appShellOptions, prerenderOptions, sourcemap, verbose);
53
- if (routesErrors?.length) {
54
- errors.push(...routesErrors);
55
- }
56
- if (routesWarnings?.length) {
57
- 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
+ }
58
85
  }
59
- if (allRoutes.size < 1 || errors.length > 0) {
86
+ if (!serializableRouteTreeNodeForPrerender.length || errors.length > 0) {
60
87
  return {
61
88
  errors,
62
89
  warnings,
63
90
  output: {},
64
- prerenderedRoutes: allRoutes,
91
+ serializableRouteTreeNode,
65
92
  };
66
93
  }
67
94
  // Render routes
68
- const { warnings: renderingWarnings, errors: renderingErrors, output, } = await renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, inlineCriticalCss, document, appShellOptions);
95
+ const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions, outputMode);
69
96
  errors.push(...renderingErrors);
70
- warnings.push(...renderingWarnings);
71
97
  return {
72
98
  errors,
73
99
  warnings,
74
100
  output,
75
- prerenderedRoutes: allRoutes,
101
+ serializableRouteTreeNode,
76
102
  };
77
103
  }
78
- class RoutesSet extends Set {
79
- add(value) {
80
- return super.add(addLeadingSlash(value));
81
- }
82
- }
83
- async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, inlineCriticalCss, document, appShellOptions) {
104
+ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions, outputMode) {
84
105
  const output = {};
85
- const warnings = [];
86
106
  const errors = [];
87
107
  const workerExecArgv = [
88
108
  '--import',
@@ -92,43 +112,42 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
92
112
  if (sourcemap) {
93
113
  workerExecArgv.push('--enable-source-maps');
94
114
  }
95
- const renderWorker = new piscina_1.default({
115
+ const renderWorker = new worker_pool_1.WorkerPool({
96
116
  filename: require.resolve('./render-worker'),
97
- maxThreads: Math.min(allRoutes.size, maxThreads),
117
+ maxThreads: Math.min(serializableRouteTreeNode.length, maxThreads),
98
118
  workerData: {
99
119
  workspaceRoot,
100
120
  outputFiles: outputFilesForWorker,
101
121
  assetFiles: assetFilesForWorker,
102
- inlineCriticalCss,
103
- document,
122
+ outputMode,
123
+ hasSsrEntry: !!outputFilesForWorker['server.mjs'],
104
124
  },
105
125
  execArgv: workerExecArgv,
106
- recordTiming: false,
107
126
  });
108
127
  try {
109
128
  const renderingPromises = [];
110
- const appShellRoute = appShellOptions.route && addLeadingSlash(appShellOptions.route);
111
- for (const route of allRoutes) {
112
- const isAppShellRoute = appShellRoute === route;
113
- const serverContext = isAppShellRoute ? 'app-shell' : 'ssg';
114
- const render = renderWorker.run({ route, serverContext });
129
+ const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
130
+ const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
131
+ for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
132
+ // Remove base href from file output path.
133
+ const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
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 });
115
143
  const renderResult = render
116
- .then(({ content, warnings, errors }) => {
117
- if (content !== undefined) {
118
- const outPath = isAppShellRoute
119
- ? 'index.html'
120
- : node_path_1.posix.join(removeLeadingSlash(route), 'index.html');
121
- output[outPath] = content;
122
- }
123
- if (warnings) {
124
- warnings.push(...warnings);
125
- }
126
- if (errors) {
127
- errors.push(...errors);
144
+ .then((content) => {
145
+ if (content !== null) {
146
+ output[outPath] = { content, appShellRoute: isAppShellRoute };
128
147
  }
129
148
  })
130
149
  .catch((err) => {
131
- errors.push(`An error occurred while prerendering route '${route}'.\n\n${err.stack}`);
150
+ errors.push(`An error occurred while prerendering route '${route}'.\n\n${err.stack ?? err.message ?? err.code ?? err}`);
132
151
  void renderWorker.destroy();
133
152
  });
134
153
  renderingPromises.push(renderResult);
@@ -140,25 +159,27 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp
140
159
  }
141
160
  return {
142
161
  errors,
143
- warnings,
144
162
  output,
145
163
  };
146
164
  }
147
- async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWorker, document, appShellOptions, prerenderOptions, sourcemap, verbose) {
148
- const { routesFile, discoverRoutes } = prerenderOptions;
149
- const routes = new RoutesSet();
150
- const { route: appShellRoute } = appShellOptions;
151
- if (appShellRoute !== undefined) {
152
- routes.add(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
+ });
153
172
  }
154
173
  if (routesFile) {
155
174
  const routesFromFile = (await (0, promises_1.readFile)(routesFile, 'utf8')).split(/\r?\n/);
156
175
  for (const route of routesFromFile) {
157
- routes.add(route.trim());
176
+ routes.push({
177
+ route: (0, url_1.urlJoin)(baseHref, route.trim()),
178
+ });
158
179
  }
159
180
  }
160
181
  if (!discoverRoutes) {
161
- return { routes };
182
+ return { errors: [], serializedRouteTree: routes };
162
183
  }
163
184
  const workerExecArgv = [
164
185
  '--import',
@@ -168,32 +189,34 @@ async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWo
168
189
  if (sourcemap) {
169
190
  workerExecArgv.push('--enable-source-maps');
170
191
  }
171
- const renderWorker = new piscina_1.default({
192
+ const renderWorker = new worker_pool_1.WorkerPool({
172
193
  filename: require.resolve('./routes-extractor-worker'),
173
194
  maxThreads: 1,
174
195
  workerData: {
175
196
  workspaceRoot,
176
197
  outputFiles: outputFilesForWorker,
177
198
  assetFiles: assetFilesForWorker,
178
- document,
179
- verbose,
199
+ outputMode,
200
+ hasSsrEntry: !!outputFilesForWorker['server.mjs'],
180
201
  },
181
202
  execArgv: workerExecArgv,
182
- recordTiming: false,
183
203
  });
184
- const errors = [];
185
- const { routes: extractedRoutes, warnings } = await renderWorker
186
- .run({})
187
- .catch((err) => {
188
- errors.push(`An error occurred while extracting routes.\n\n${err.stack}`);
189
- })
190
- .finally(() => {
204
+ try {
205
+ const { serializedRouteTree, errors } = await renderWorker.run({});
206
+ return { errors, serializedRouteTree: [...routes, ...serializedRouteTree] };
207
+ }
208
+ catch (err) {
209
+ (0, error_1.assertIsError)(err);
210
+ return {
211
+ errors: [
212
+ `An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err.code ?? err}`,
213
+ ],
214
+ serializedRouteTree: [],
215
+ };
216
+ }
217
+ finally {
191
218
  void renderWorker.destroy();
192
- });
193
- for (const route of extractedRoutes) {
194
- routes.add(route);
195
219
  }
196
- return { routes, warnings, errors };
197
220
  }
198
221
  function addLeadingSlash(value) {
199
222
  return value.charAt(0) === '/' ? value : '/' + value;
@@ -201,3 +224,27 @@ function addLeadingSlash(value) {
201
224
  function removeLeadingSlash(value) {
202
225
  return value.charAt(0) === '/' ? value.slice(1) : value;
203
226
  }
227
+ /**
228
+ * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
229
+ *
230
+ * This function creates a simple HTML page that performs a redirect using a meta tag.
231
+ * It includes a fallback link in case the meta-refresh doesn't work.
232
+ *
233
+ * @param url - The URL to which the page should redirect.
234
+ * @returns The HTML content of the static redirect page.
235
+ */
236
+ function generateRedirectStaticPage(url) {
237
+ return `
238
+ <!DOCTYPE html>
239
+ <html>
240
+ <head>
241
+ <meta charset="utf-8">
242
+ <title>Redirecting</title>
243
+ <meta http-equiv="refresh" content="0; url=${url}">
244
+ </head>
245
+ <body>
246
+ <pre>Redirecting to <a href="${url}">${url}</a></pre>
247
+ </body>
248
+ </html>
249
+ `.trim();
250
+ }
@@ -5,18 +5,19 @@
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
- import { RenderResult, ServerContext } from './render-page';
10
10
  export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData {
11
- document: string;
12
- inlineCriticalCss?: boolean;
13
11
  assetFiles: Record</** Destination */ string, /** Source */ string>;
12
+ outputMode: OutputMode | undefined;
13
+ hasSsrEntry: boolean;
14
14
  }
15
15
  export interface RenderOptions {
16
- route: string;
17
- serverContext: ServerContext;
16
+ url: string;
18
17
  }
19
- /** Renders an application based on a provided options. */
20
- declare function render(options: RenderOptions): Promise<RenderResult>;
21
- declare const _default: typeof render;
18
+ /**
19
+ * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
20
+ */
21
+ declare function renderPage({ url }: RenderOptions): Promise<string | null>;
22
+ declare const _default: Promise<typeof renderPage>;
22
23
  export default _default;
@@ -7,24 +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 node_worker_threads_1 = require("node:worker_threads");
10
+ const worker_threads_1 = require("worker_threads");
11
11
  const fetch_patch_1 = require("./fetch-patch");
12
- const render_page_1 = require("./render-page");
12
+ const launch_server_1 = require("./launch-server");
13
+ const load_esm_from_memory_1 = require("./load-esm-from-memory");
13
14
  /**
14
15
  * This is passed as workerData when setting up the worker via the `piscina` package.
15
16
  */
16
- const { outputFiles, document, inlineCriticalCss } = node_worker_threads_1.workerData;
17
- /** Renders an application based on a provided options. */
18
- function render(options) {
19
- return (0, render_page_1.renderPage)({
20
- ...options,
21
- outputFiles,
22
- document,
23
- inlineCriticalCss,
24
- });
17
+ const { outputMode, hasSsrEntry } = worker_threads_1.workerData;
18
+ let serverURL = launch_server_1.DEFAULT_URL;
19
+ /**
20
+ * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
21
+ */
22
+ async function renderPage({ url }) {
23
+ const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
24
+ const angularServerApp = getOrCreateAngularServerApp();
25
+ const response = await angularServerApp.renderStatic(new URL(url, serverURL), AbortSignal.timeout(30_000));
26
+ return response ? response.text() : null;
25
27
  }
26
- function initialize() {
27
- (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
28
- return render;
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);
33
+ return renderPage;
29
34
  }
30
35
  exports.default = initialize();
@@ -5,17 +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 type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
9
- export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData {
10
- document: string;
11
- verbose: boolean;
12
- assetFiles: Record</** Destination */ string, /** Source */ string>;
13
- }
14
- export interface RoutersExtractorWorkerResult {
15
- routes: string[];
16
- warnings?: string[];
8
+ import { OutputMode } from '../../builders/application/schema';
9
+ import { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
10
+ import { RoutersExtractorWorkerResult } from './models';
11
+ export interface ExtractRoutesWorkerData extends ESMInMemoryFileLoaderWorkerData {
12
+ outputMode: OutputMode | undefined;
17
13
  }
18
14
  /** Renders an application based on a provided options. */
19
15
  declare function extractRoutes(): Promise<RoutersExtractorWorkerResult>;
20
- declare const _default: typeof extractRoutes;
16
+ declare const _default: Promise<typeof extractRoutes>;
21
17
  export default _default;