@angular/build 19.0.0-next.6 → 19.0.0-next.8

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 (83) hide show
  1. package/package.json +11 -11
  2. package/src/builders/application/execute-build.js +19 -2
  3. package/src/builders/application/execute-post-bundle.d.ts +2 -2
  4. package/src/builders/application/execute-post-bundle.js +30 -11
  5. package/src/builders/application/i18n.d.ts +2 -2
  6. package/src/builders/application/i18n.js +4 -5
  7. package/src/builders/application/index.js +8 -5
  8. package/src/builders/application/options.d.ts +18 -1
  9. package/src/builders/application/options.js +30 -2
  10. package/src/builders/application/schema.d.ts +15 -0
  11. package/src/builders/application/schema.js +11 -1
  12. package/src/builders/application/schema.json +5 -0
  13. package/src/builders/application/setup-bundling.js +6 -3
  14. package/src/builders/dev-server/vite-server.d.ts +2 -1
  15. package/src/builders/dev-server/vite-server.js +64 -47
  16. package/src/builders/extract-i18n/application-extraction.js +3 -3
  17. package/src/tools/angular/angular-host.d.ts +2 -1
  18. package/src/tools/angular/angular-host.js +20 -1
  19. package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
  20. package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
  21. package/src/tools/angular/compilation/aot-compilation.js +9 -1
  22. package/src/tools/angular/compilation/parallel-compilation.d.ts +1 -0
  23. package/src/tools/angular/compilation/parallel-compilation.js +2 -10
  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/esbuild/angular/compiler-plugin.d.ts +1 -0
  27. package/src/tools/esbuild/angular/compiler-plugin.js +42 -3
  28. package/src/tools/esbuild/angular/component-stylesheets.d.ts +8 -3
  29. package/src/tools/esbuild/angular/component-stylesheets.js +46 -11
  30. package/src/tools/esbuild/application-code-bundle.d.ts +1 -0
  31. package/src/tools/esbuild/application-code-bundle.js +109 -2
  32. package/src/tools/esbuild/budget-stats.js +1 -1
  33. package/src/tools/esbuild/bundler-context.d.ts +4 -3
  34. package/src/tools/esbuild/bundler-context.js +8 -4
  35. package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
  36. package/src/tools/esbuild/bundler-execution-result.js +7 -3
  37. package/src/tools/esbuild/cache.d.ts +5 -0
  38. package/src/tools/esbuild/cache.js +7 -0
  39. package/src/tools/esbuild/compiler-plugin-options.js +2 -1
  40. package/src/tools/esbuild/i18n-inliner.js +4 -4
  41. package/src/tools/esbuild/javascript-transformer.js +2 -9
  42. package/src/tools/esbuild/utils.js +7 -3
  43. package/src/tools/sass/sass-service.js +2 -9
  44. package/src/tools/vite/middlewares/assets-middleware.js +8 -8
  45. package/src/tools/vite/middlewares/headers-middleware.d.ts +19 -0
  46. package/src/tools/vite/middlewares/headers-middleware.js +34 -0
  47. package/src/tools/vite/middlewares/html-fallback-middleware.d.ts +1 -1
  48. package/src/tools/vite/middlewares/html-fallback-middleware.js +23 -7
  49. package/src/tools/vite/middlewares/index-html-middleware.js +1 -2
  50. package/src/tools/vite/middlewares/index.d.ts +2 -1
  51. package/src/tools/vite/middlewares/index.js +5 -2
  52. package/src/tools/vite/middlewares/ssr-middleware.d.ts +2 -1
  53. package/src/tools/vite/middlewares/ssr-middleware.js +61 -17
  54. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +16 -0
  55. package/src/tools/vite/{angular-memory-plugin.js → plugins/angular-memory-plugin.js} +19 -40
  56. package/src/tools/vite/{i18n-locale-plugin.d.ts → plugins/i18n-locale-plugin.d.ts} +0 -4
  57. package/src/tools/vite/{i18n-locale-plugin.js → plugins/i18n-locale-plugin.js} +2 -3
  58. package/src/tools/vite/plugins/index.d.ts +12 -0
  59. package/src/tools/vite/plugins/index.js +21 -0
  60. package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +41 -0
  61. package/src/tools/vite/plugins/setup-middlewares-plugin.js +62 -0
  62. package/src/tools/vite/plugins/ssr-transform-plugin.d.ts +9 -0
  63. package/src/tools/vite/plugins/ssr-transform-plugin.js +38 -0
  64. package/src/tools/vite/utils.d.ts +0 -3
  65. package/src/tools/vite/utils.js +0 -12
  66. package/src/typings.d.ts +7 -0
  67. package/src/utils/environment-options.d.ts +2 -0
  68. package/src/utils/environment-options.js +5 -1
  69. package/src/utils/index-file/valid-self-closing-tags.js +1 -0
  70. package/src/utils/normalize-cache.js +1 -1
  71. package/src/utils/server-rendering/manifest.d.ts +8 -2
  72. package/src/utils/server-rendering/manifest.js +61 -12
  73. package/src/utils/server-rendering/models.d.ts +27 -0
  74. package/src/utils/server-rendering/models.js +22 -0
  75. package/src/utils/server-rendering/prerender.d.ts +6 -10
  76. package/src/utils/server-rendering/prerender.js +103 -71
  77. package/src/utils/server-rendering/routes-extractor-worker.d.ts +5 -6
  78. package/src/utils/server-rendering/routes-extractor-worker.js +7 -5
  79. package/src/utils/worker-pool.d.ts +12 -0
  80. package/src/utils/worker-pool.js +43 -0
  81. package/src/tools/vite/angular-memory-plugin.d.ts +0 -22
  82. /package/src/tools/vite/{id-prefix-plugin.d.ts → plugins/id-prefix-plugin.d.ts} +0 -0
  83. /package/src/tools/vite/{id-prefix-plugin.js → plugins/id-prefix-plugin.js} +0 -0
@@ -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,40 @@
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
+ '<': '\\u003C',
22
+ '>': '\\u003E',
23
+ '/': '\\u002F',
24
+ '\\': '\\\\',
25
+ '\b': '\\b',
26
+ '\f': '\\f',
27
+ '\n': '\\n',
28
+ '\r': '\\r',
29
+ '\t': '\\t',
30
+ '\0': '\\0',
31
+ '\u2028': '\\u2028',
32
+ '\u2029': '\\u2029',
33
+ };
34
+ /**
35
+ * Escapes unsafe characters in a given string by replacing them with
36
+ * their Unicode escape sequences.
37
+ *
38
+ * @param str - The string to be escaped.
39
+ * @returns The escaped string where unsafe characters are replaced.
40
+ */
41
+ function escapeUnsafeChars(str) {
42
+ return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029]/g, (c) => UNSAFE_CHAR_MAP[c]);
43
+ }
16
44
  /**
17
45
  * Generates the server manifest for the App Engine environment.
18
46
  *
@@ -24,26 +52,43 @@ const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
24
52
  * includes settings for inlining locales and determining the output structure.
25
53
  * @param baseHref - The base HREF for the application. This is used to set the base URL
26
54
  * for all relative URLs in the application.
55
+ * @param perenderedRoutes - A record mapping static paths to their associated data.
27
56
  * @returns A string representing the content of the SSR server manifest for App Engine.
28
57
  */
29
- function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
58
+ function generateAngularServerAppEngineManifest(i18nOptions, baseHref, perenderedRoutes = {}) {
30
59
  const entryPointsContent = [];
31
60
  if (i18nOptions.shouldInline) {
32
61
  for (const locale of i18nOptions.inlineLocales) {
33
62
  const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
34
- const localWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
35
- entryPointsContent.push(`['${localWithBaseHref}', () => import('${importPath}')]`);
63
+ let localeWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
64
+ // Remove leading and trailing slashes.
65
+ const start = localeWithBaseHref[0] === '/' ? 1 : 0;
66
+ const end = localeWithBaseHref[localeWithBaseHref.length - 1] === '/' ? -1 : undefined;
67
+ localeWithBaseHref = localeWithBaseHref.slice(start, end);
68
+ entryPointsContent.push(`['${localeWithBaseHref}', () => import('${importPath}')]`);
36
69
  }
37
70
  }
38
71
  else {
39
- entryPointsContent.push(`['/', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
72
+ entryPointsContent.push(`['', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
73
+ }
74
+ const staticHeaders = [];
75
+ for (const [path, { headers }] of Object.entries(perenderedRoutes)) {
76
+ if (!headers) {
77
+ continue;
78
+ }
79
+ const headersValues = [];
80
+ for (const [name, value] of Object.entries(headers)) {
81
+ headersValues.push(`['${name}', '${encodeURIComponent(value)}']`);
82
+ }
83
+ staticHeaders.push(`['${path}', [${headersValues.join(', ')}]]`);
40
84
  }
41
85
  const manifestContent = `
42
- {
43
- basePath: '${baseHref ?? '/'}',
44
- entryPoints: new Map([${entryPointsContent.join(', \n')}]),
45
- }
46
- `;
86
+ export default {
87
+ basePath: '${baseHref ?? '/'}',
88
+ entryPoints: new Map([${entryPointsContent.join(', \n')}]),
89
+ staticPathsHeaders: new Map([${staticHeaders.join(', \n')}]),
90
+ };
91
+ `;
47
92
  return manifestContent;
48
93
  }
49
94
  /**
@@ -62,16 +107,19 @@ function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
62
107
  * in the server-side rendered pages.
63
108
  * @param routes - An optional array of route definitions for the application, used for
64
109
  * server-side rendering and routing.
110
+ * @param locale - An optional string representing the locale or language code to be used for
111
+ * the application, helping with localization and rendering content specific to the locale.
112
+ *
65
113
  * @returns A string representing the content of the SSR server manifest for the Node.js
66
114
  * environment.
67
115
  */
68
- function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes) {
116
+ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale) {
69
117
  const serverAssetsContent = [];
70
118
  for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
71
119
  if (file.path === options_1.INDEX_HTML_SERVER ||
72
120
  file.path === options_1.INDEX_HTML_CSR ||
73
121
  (inlineCriticalCss && file.path.endsWith('.css'))) {
74
- serverAssetsContent.push(`['${file.path}', async () => ${JSON.stringify(file.text)}]`);
122
+ serverAssetsContent.push(`['${file.path}', async () => ${escapeUnsafeChars(JSON.stringify(file.text))}]`);
75
123
  }
76
124
  }
77
125
  const manifestContent = `
@@ -80,6 +128,7 @@ export default {
80
128
  inlineCriticalCss: ${inlineCriticalCss},
81
129
  routes: ${JSON.stringify(routes, undefined, 2)},
82
130
  assets: new Map([${serverAssetsContent.join(', \n')}]),
131
+ locale: ${locale !== undefined ? `'${locale}'` : undefined},
83
132
  };
84
133
  `;
85
134
  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 { RoutersExtractorWorkerResult as 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 {};
@@ -6,24 +6,24 @@
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");
16
+ const error_1 = require("../error");
19
17
  const url_1 = require("../url");
20
- async function prerenderPages(workspaceRoot, baseHref, appShellOptions = {}, prerenderOptions = {}, outputFiles, assets, sourcemap = false, maxThreads = 1, verbose = false) {
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) {
21
21
  const outputFilesForWorker = {};
22
22
  const serverBundlesSourceMaps = new Map();
23
23
  const warnings = [];
24
24
  const errors = [];
25
25
  for (const { text, path, type } of outputFiles) {
26
- if (type !== bundler_context_1.BuildOutputFileType.Server) {
26
+ if (type !== bundler_context_1.BuildOutputFileType.ServerApplication && type !== bundler_context_1.BuildOutputFileType.ServerRoot) {
27
27
  continue;
28
28
  }
29
29
  // Contains the server runnable application code
@@ -51,39 +51,57 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions = {}, pre
51
51
  assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source;
52
52
  }
53
53
  // Get routes to prerender
54
- const { routes: allRoutes, warnings: routesWarnings, errors: routesErrors, serializableRouteTreeNode, } = await getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetsReversed, appShellOptions, prerenderOptions, sourcemap, verbose);
55
- if (routesErrors?.length) {
56
- errors.push(...routesErrors);
57
- }
58
- if (routesWarnings?.length) {
59
- 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
+ }
60
85
  }
61
- if (allRoutes.size < 1 || errors.length > 0) {
86
+ if (!serializableRouteTreeNodeForPrerender.length || errors.length > 0) {
62
87
  return {
63
88
  errors,
64
89
  warnings,
65
90
  output: {},
66
91
  serializableRouteTreeNode,
67
- prerenderedRoutes: allRoutes,
68
92
  };
69
93
  }
70
94
  // Render routes
71
- 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);
72
96
  errors.push(...renderingErrors);
73
97
  return {
74
98
  errors,
75
99
  warnings,
76
100
  output,
77
101
  serializableRouteTreeNode,
78
- prerenderedRoutes: allRoutes,
79
102
  };
80
103
  }
81
- class RoutesSet extends Set {
82
- add(value) {
83
- return super.add(addLeadingSlash(value));
84
- }
85
- }
86
- async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions) {
104
+ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions) {
87
105
  const output = {};
88
106
  const errors = [];
89
107
  const workerExecArgv = [
@@ -94,30 +112,35 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
94
112
  if (sourcemap) {
95
113
  workerExecArgv.push('--enable-source-maps');
96
114
  }
97
- const renderWorker = new piscina_1.default({
115
+ const renderWorker = new worker_pool_1.WorkerPool({
98
116
  filename: require.resolve('./render-worker'),
99
- maxThreads: Math.min(allRoutes.size, maxThreads),
117
+ maxThreads: Math.min(serializableRouteTreeNode.length, maxThreads),
100
118
  workerData: {
101
119
  workspaceRoot,
102
120
  outputFiles: outputFilesForWorker,
103
121
  assetFiles: assetFilesForWorker,
104
122
  },
105
123
  execArgv: workerExecArgv,
106
- recordTiming: false,
107
124
  });
108
125
  try {
109
126
  const renderingPromises = [];
110
- const appShellRoute = appShellOptions.route && addLeadingSlash(appShellOptions.route);
127
+ const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route);
111
128
  const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
112
- for (const route of allRoutes) {
129
+ for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
113
130
  // Remove base href from file output path.
114
131
  const routeWithoutBaseHref = addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1));
115
- const render = renderWorker.run({ url: route });
132
+ const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
133
+ if (typeof redirectTo === 'string') {
134
+ output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false };
135
+ continue;
136
+ }
137
+ const isAppShellRoute = renderMode === models_1.RouteRenderMode.AppShell ||
138
+ // Legacy handling
139
+ (renderMode === undefined && appShellRoute === routeWithoutBaseHref);
140
+ const render = renderWorker.run({ url: route, isAppShellRoute });
116
141
  const renderResult = render
117
142
  .then((content) => {
118
143
  if (content !== null) {
119
- const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
120
- const isAppShellRoute = appShellRoute === routeWithoutBaseHref;
121
144
  output[outPath] = { content, appShellRoute: isAppShellRoute };
122
145
  }
123
146
  })
@@ -137,21 +160,24 @@ async function renderPages(baseHref, sourcemap, allRoutes, maxThreads, workspace
137
160
  output,
138
161
  };
139
162
  }
140
- async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap, verbose) {
141
- const { routesFile, discoverRoutes } = prerenderOptions;
142
- const routes = new RoutesSet();
143
- const { route: appShellRoute } = appShellOptions;
144
- if (appShellRoute !== undefined) {
145
- routes.add((0, url_1.urlJoin)(baseHref, appShellRoute));
163
+ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, assetFilesForWorker, appShellOptions, prerenderOptions, sourcemap, outputMode) {
164
+ const { routesFile, discoverRoutes } = prerenderOptions ?? {};
165
+ const routes = [];
166
+ if (appShellOptions) {
167
+ routes.push({
168
+ route: (0, url_1.urlJoin)(baseHref, appShellOptions.route),
169
+ });
146
170
  }
147
171
  if (routesFile) {
148
172
  const routesFromFile = (await (0, promises_1.readFile)(routesFile, 'utf8')).split(/\r?\n/);
149
173
  for (const route of routesFromFile) {
150
- routes.add((0, url_1.urlJoin)(baseHref, route.trim()));
174
+ routes.push({
175
+ route: (0, url_1.urlJoin)(baseHref, route.trim()),
176
+ });
151
177
  }
152
178
  }
153
179
  if (!discoverRoutes) {
154
- return { routes, serializableRouteTreeNode: [] };
180
+ return { errors: [], serializedRouteTree: routes };
155
181
  }
156
182
  const workerExecArgv = [
157
183
  '--import',
@@ -161,7 +187,7 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
161
187
  if (sourcemap) {
162
188
  workerExecArgv.push('--enable-source-maps');
163
189
  }
164
- const renderWorker = new piscina_1.default({
190
+ const renderWorker = new worker_pool_1.WorkerPool({
165
191
  filename: require.resolve('./routes-extractor-worker'),
166
192
  maxThreads: 1,
167
193
  workerData: {
@@ -170,41 +196,23 @@ async function getAllRoutes(workspaceRoot, baseHref, outputFilesForWorker, asset
170
196
  assetFiles: assetFilesForWorker,
171
197
  },
172
198
  execArgv: workerExecArgv,
173
- recordTiming: false,
174
199
  });
175
- const errors = [];
176
- const serializableRouteTreeNode = await renderWorker
177
- .run({})
178
- .catch((err) => {
179
- errors.push(`An error occurred while extracting routes.\n\n${err.stack}`);
180
- })
181
- .finally(() => {
182
- void renderWorker.destroy();
183
- });
184
- const skippedRedirects = [];
185
- const skippedOthers = [];
186
- for (const { route, redirectTo } of serializableRouteTreeNode) {
187
- if (redirectTo) {
188
- skippedRedirects.push(route);
189
- }
190
- else if (route.includes('*')) {
191
- skippedOthers.push(route);
192
- }
193
- else {
194
- routes.add(route);
195
- }
200
+ try {
201
+ const { serializedRouteTree, errors } = await renderWorker.run({
202
+ outputMode,
203
+ });
204
+ return { errors, serializedRouteTree: [...routes, ...serializedRouteTree] };
196
205
  }
197
- let warnings;
198
- if (verbose) {
199
- if (skippedOthers.length) {
200
- (warnings ??= []).push('The following routes were skipped from prerendering because they contain routes with dynamic parameters:\n' +
201
- skippedOthers.join('\n'));
202
- }
203
- if (skippedRedirects.length) {
204
- (warnings ??= []).push('The following routes were skipped from prerendering because they contain redirects:\n', skippedRedirects.join('\n'));
205
- }
206
+ catch (err) {
207
+ (0, error_1.assertIsError)(err);
208
+ return {
209
+ errors: [`An error occurred while extracting routes.\n\n${err.stack}`],
210
+ serializedRouteTree: [],
211
+ };
212
+ }
213
+ finally {
214
+ void renderWorker.destroy();
206
215
  }
207
- return { routes, serializableRouteTreeNode, warnings };
208
216
  }
209
217
  function addLeadingSlash(value) {
210
218
  return value.charAt(0) === '/' ? value : '/' + value;
@@ -212,3 +220,27 @@ function addLeadingSlash(value) {
212
220
  function removeLeadingSlash(value) {
213
221
  return value.charAt(0) === '/' ? value.slice(1) : value;
214
222
  }
223
+ /**
224
+ * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
225
+ *
226
+ * This function creates a simple HTML page that performs a redirect using a meta tag.
227
+ * It includes a fallback link in case the meta-refresh doesn't work.
228
+ *
229
+ * @param url - The URL to which the page should redirect.
230
+ * @returns The HTML content of the static redirect page.
231
+ */
232
+ function generateRedirectStaticPage(url) {
233
+ return `
234
+ <!DOCTYPE html>
235
+ <html>
236
+ <head>
237
+ <meta charset="utf-8">
238
+ <title>Redirecting</title>
239
+ <meta http-equiv="refresh" content="0; url=${url}">
240
+ </head>
241
+ <body>
242
+ <pre>Redirecting to <a href="${url}">${url}</a></pre>
243
+ </body>
244
+ </html>
245
+ `.trim();
246
+ }
@@ -5,13 +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 { ɵextractRoutesAndCreateRouteTree } from '@angular/ssr';
9
- import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks';
10
- export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData {
11
- assetFiles: Record</** Destination */ string, /** Source */ string>;
8
+ import { OutputMode } from '../../builders/application/schema';
9
+ import { RoutersExtractorWorkerResult } from './models';
10
+ export interface ExtractRoutesOptions {
11
+ outputMode?: OutputMode;
12
12
  }
13
- export type RoutersExtractorWorkerResult = ReturnType<Awaited<ReturnType<typeof ɵextractRoutesAndCreateRouteTree>>['toObject']>;
14
13
  /** Renders an application based on a provided options. */
15
- declare function extractRoutes(): Promise<RoutersExtractorWorkerResult>;
14
+ declare function extractRoutes({ outputMode, }: ExtractRoutesOptions): Promise<RoutersExtractorWorkerResult>;
16
15
  declare const _default: typeof extractRoutes;
17
16
  export default _default;
@@ -7,15 +7,17 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ const schema_1 = require("../../builders/application/schema");
10
11
  const fetch_patch_1 = require("./fetch-patch");
11
12
  const load_esm_from_memory_1 = require("./load-esm-from-memory");
12
13
  /** Renders an application based on a provided options. */
13
- async function extractRoutes() {
14
+ async function extractRoutes({ outputMode, }) {
14
15
  const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
15
- const routeTree = await extractRoutesAndCreateRouteTree(new URL('http://local-angular-prerender/'),
16
- /** manifest */ undefined,
17
- /** invokeGetPrerenderParams */ true);
18
- return routeTree.toObject();
16
+ const { routeTree, errors } = await extractRoutesAndCreateRouteTree(new URL('http://local-angular-prerender/'), undefined /** manifest */, true /** invokeGetPrerenderParams */, outputMode === schema_1.OutputMode.Server /** includePrerenderFallbackRoutes */);
17
+ return {
18
+ errors,
19
+ serializedRouteTree: routeTree.toObject(),
20
+ };
19
21
  }
20
22
  function initialize() {
21
23
  (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)();
@@ -0,0 +1,12 @@
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 { Piscina } from 'piscina';
9
+ export type WorkerPoolOptions = ConstructorParameters<typeof Piscina>[0];
10
+ export declare class WorkerPool extends Piscina {
11
+ constructor(options: WorkerPoolOptions);
12
+ }
@@ -0,0 +1,43 @@
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.WorkerPool = void 0;
11
+ const node_module_1 = require("node:module");
12
+ const piscina_1 = require("piscina");
13
+ class WorkerPool extends piscina_1.Piscina {
14
+ constructor(options) {
15
+ const piscinaOptions = {
16
+ minThreads: 1,
17
+ idleTimeout: 1000,
18
+ // Web containers do not support transferable objects with receiveOnMessagePort which
19
+ // is used when the Atomics based wait loop is enable.
20
+ useAtomics: !process.versions.webcontainer,
21
+ recordTiming: false,
22
+ ...options,
23
+ };
24
+ // Enable compile code caching if enabled for the main process (only exists on Node.js v22.8+).
25
+ // Skip if running inside Bazel via a RUNFILES environment variable check. The cache does not work
26
+ // well with Bazel's hermeticity requirements.
27
+ const compileCacheDirectory = process.env['RUNFILES'] ? undefined : (0, node_module_1.getCompileCacheDir)?.();
28
+ if (compileCacheDirectory) {
29
+ if (typeof piscinaOptions.env === 'object') {
30
+ piscinaOptions.env['NODE_COMPILE_CACHE'] = compileCacheDirectory;
31
+ }
32
+ else {
33
+ // Default behavior of `env` option is to copy current process values
34
+ piscinaOptions.env = {
35
+ ...process.env,
36
+ 'NODE_COMPILE_CACHE': compileCacheDirectory,
37
+ };
38
+ }
39
+ }
40
+ super(piscinaOptions);
41
+ }
42
+ }
43
+ exports.WorkerPool = WorkerPool;
@@ -1,22 +0,0 @@
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 { Connect, Plugin } from 'vite';
9
- import { AngularMemoryOutputFiles } from './utils';
10
- export interface AngularMemoryPluginOptions {
11
- workspaceRoot: string;
12
- virtualProjectRoot: string;
13
- outputFiles: AngularMemoryOutputFiles;
14
- assets: Map<string, string>;
15
- ssr: boolean;
16
- external?: string[];
17
- extensionMiddleware?: Connect.NextHandleFunction[];
18
- indexHtmlTransformer?: (content: string) => Promise<string>;
19
- normalizePath: (path: string) => string;
20
- usedComponentStyles: Map<string, string[]>;
21
- }
22
- export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Plugin;