@angular/build 19.1.0-next.0 → 19.1.0-next.2

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 (42) hide show
  1. package/package.json +13 -11
  2. package/src/builders/application/execute-build.js +37 -4
  3. package/src/builders/application/execute-post-bundle.d.ts +3 -1
  4. package/src/builders/application/execute-post-bundle.js +6 -4
  5. package/src/builders/application/i18n.d.ts +3 -1
  6. package/src/builders/application/i18n.js +15 -13
  7. package/src/builders/application/options.js +7 -5
  8. package/src/builders/dev-server/vite-server.d.ts +2 -2
  9. package/src/builders/dev-server/vite-server.js +31 -47
  10. package/src/builders/extract-i18n/options.js +1 -1
  11. package/src/tools/angular/angular-host.d.ts +1 -1
  12. package/src/tools/angular/angular-host.js +4 -4
  13. package/src/tools/angular/compilation/aot-compilation.d.ts +2 -0
  14. package/src/tools/angular/compilation/aot-compilation.js +30 -9
  15. package/src/tools/angular/compilation/factory.d.ts +2 -1
  16. package/src/tools/angular/compilation/factory.js +5 -4
  17. package/src/tools/angular/compilation/jit-compilation.d.ts +2 -0
  18. package/src/tools/angular/compilation/jit-compilation.js +12 -2
  19. package/src/tools/angular/compilation/parallel-compilation.d.ts +3 -2
  20. package/src/tools/angular/compilation/parallel-compilation.js +4 -1
  21. package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
  22. package/src/tools/angular/compilation/parallel-worker.js +3 -1
  23. package/src/tools/angular/transformers/lazy-routes-transformer.d.ts +39 -0
  24. package/src/tools/angular/transformers/lazy-routes-transformer.js +163 -0
  25. package/src/tools/esbuild/angular/compiler-plugin.d.ts +1 -0
  26. package/src/tools/esbuild/angular/compiler-plugin.js +1 -1
  27. package/src/tools/esbuild/application-code-bundle.js +26 -20
  28. package/src/tools/esbuild/compiler-plugin-options.js +1 -0
  29. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -0
  30. package/src/tools/vite/plugins/angular-memory-plugin.js +25 -2
  31. package/src/tools/vite/utils.d.ts +14 -0
  32. package/src/tools/vite/utils.js +37 -0
  33. package/src/utils/environment-options.js +1 -1
  34. package/src/utils/i18n-options.d.ts +4 -1
  35. package/src/utils/i18n-options.js +50 -7
  36. package/src/utils/index-file/auto-csp.js +5 -5
  37. package/src/utils/normalize-cache.js +1 -1
  38. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +9 -1
  39. package/src/utils/server-rendering/manifest.d.ts +5 -1
  40. package/src/utils/server-rendering/manifest.js +60 -11
  41. package/src/utils/server-rendering/prerender.js +1 -1
  42. package/tsconfig-build.json +6 -0
@@ -9,8 +9,10 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.pathnameWithoutBasePath = pathnameWithoutBasePath;
11
11
  exports.lookupMimeTypeFromRequest = lookupMimeTypeFromRequest;
12
+ exports.getDepOptimizationConfig = getDepOptimizationConfig;
12
13
  const mrmime_1 = require("mrmime");
13
14
  const node_path_1 = require("node:path");
15
+ const utils_1 = require("../esbuild/utils");
14
16
  function pathnameWithoutBasePath(url, basePath) {
15
17
  const parsedUrl = new URL(url, 'http://localhost');
16
18
  const pathname = decodeURIComponent(parsedUrl.pathname);
@@ -26,3 +28,38 @@ function lookupMimeTypeFromRequest(url) {
26
28
  }
27
29
  return extension && (0, mrmime_1.lookup)(extension);
28
30
  }
31
+ function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless, prebundleTransformer, ssr, loader, thirdPartySourcemaps, }) {
32
+ const plugins = [
33
+ {
34
+ name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${thirdPartySourcemaps ? '-vendor-sourcemap' : ''}`,
35
+ setup(build) {
36
+ build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
37
+ return {
38
+ contents: await prebundleTransformer.transformFile(args.path),
39
+ loader: 'js',
40
+ };
41
+ });
42
+ },
43
+ },
44
+ ];
45
+ return {
46
+ // Exclude any explicitly defined dependencies (currently build defined externals)
47
+ exclude,
48
+ // NB: to disable the deps optimizer, set optimizeDeps.noDiscovery to true and optimizeDeps.include as undefined.
49
+ // Include all implict dependencies from the external packages internal option
50
+ include: disabled ? undefined : include,
51
+ noDiscovery: disabled,
52
+ // Add an esbuild plugin to run the Angular linker on dependencies
53
+ esbuildOptions: {
54
+ // Set esbuild supported targets.
55
+ target,
56
+ supported: (0, utils_1.getFeatureSupport)(target, zoneless),
57
+ plugins,
58
+ loader,
59
+ define: {
60
+ 'ngServerMode': `${ssr}`,
61
+ },
62
+ resolveExtensions: ['.mjs', '.js', '.cjs'],
63
+ },
64
+ };
65
+ }
@@ -85,6 +85,6 @@ exports.shouldOptimizeChunks = isPresent(optimizeChunksVariable) && isEnabled(op
85
85
  const hmrComponentStylesVariable = process.env['NG_HMR_CSTYLES'];
86
86
  exports.useComponentStyleHmr = !isPresent(hmrComponentStylesVariable) || !isDisabled(hmrComponentStylesVariable);
87
87
  const hmrComponentTemplateVariable = process.env['NG_HMR_TEMPLATES'];
88
- exports.useComponentTemplateHmr = isPresent(hmrComponentTemplateVariable) && isEnabled(hmrComponentTemplateVariable);
88
+ exports.useComponentTemplateHmr = !isPresent(hmrComponentTemplateVariable) || !isDisabled(hmrComponentTemplateVariable);
89
89
  const partialSsrBuildVariable = process.env['NG_BUILD_PARTIAL_SSR'];
90
90
  exports.usePartialSsrBuild = isPresent(partialSsrBuildVariable) && isEnabled(partialSsrBuildVariable);
@@ -15,6 +15,7 @@ export interface LocaleDescription {
15
15
  translation?: Record<string, unknown>;
16
16
  dataPath?: string;
17
17
  baseHref?: string;
18
+ subPath: string;
18
19
  }
19
20
  export interface I18nOptions {
20
21
  inlineLocales: Set<string>;
@@ -26,7 +27,9 @@ export interface I18nOptions {
26
27
  }
27
28
  export declare function createI18nOptions(projectMetadata: {
28
29
  i18n?: unknown;
29
- }, inline?: boolean | string[]): I18nOptions;
30
+ }, inline?: boolean | string[], logger?: {
31
+ warn(message: string): void;
32
+ }): I18nOptions;
30
33
  export declare function loadTranslations(locale: string, desc: LocaleDescription, workspaceRoot: string, loader: TranslationLoader, logger: {
31
34
  warn: (message: string) => void;
32
35
  error: (message: string) => void;
@@ -31,15 +31,21 @@ function normalizeTranslationFileOption(option, locale, expectObjectInError) {
31
31
  }
32
32
  function ensureObject(value, name) {
33
33
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
34
- throw new Error(`Project ${name} field is malformed. Expected an object.`);
34
+ throw new Error(`Project field '${name}' is malformed. Expected an object.`);
35
35
  }
36
36
  }
37
37
  function ensureString(value, name) {
38
38
  if (typeof value !== 'string') {
39
- throw new Error(`Project ${name} field is malformed. Expected a string.`);
39
+ throw new Error(`Project field '${name}' is malformed. Expected a string.`);
40
40
  }
41
41
  }
42
- function createI18nOptions(projectMetadata, inline) {
42
+ function ensureValidsubPath(value, name) {
43
+ ensureString(value, name);
44
+ if (!/^[\w-]*$/.test(value)) {
45
+ throw new Error(`Project field '${name}' is invalid. It can only contain letters, numbers, hyphens, and underscores.`);
46
+ }
47
+ }
48
+ function createI18nOptions(projectMetadata, inline, logger) {
43
49
  const { i18n: metadata = {} } = projectMetadata;
44
50
  ensureObject(metadata, 'i18n');
45
51
  const i18n = {
@@ -53,19 +59,31 @@ function createI18nOptions(projectMetadata, inline) {
53
59
  };
54
60
  let rawSourceLocale;
55
61
  let rawSourceLocaleBaseHref;
62
+ let rawsubPath;
56
63
  if (typeof metadata.sourceLocale === 'string') {
57
64
  rawSourceLocale = metadata.sourceLocale;
58
65
  }
59
66
  else if (metadata.sourceLocale !== undefined) {
60
- ensureObject(metadata.sourceLocale, 'i18n sourceLocale');
67
+ ensureObject(metadata.sourceLocale, 'i18n.sourceLocale');
61
68
  if (metadata.sourceLocale.code !== undefined) {
62
- ensureString(metadata.sourceLocale.code, 'i18n sourceLocale code');
69
+ ensureString(metadata.sourceLocale.code, 'i18n.sourceLocale.code');
63
70
  rawSourceLocale = metadata.sourceLocale.code;
64
71
  }
65
72
  if (metadata.sourceLocale.baseHref !== undefined) {
66
- ensureString(metadata.sourceLocale.baseHref, 'i18n sourceLocale baseHref');
73
+ ensureString(metadata.sourceLocale.baseHref, 'i18n.sourceLocale.baseHref');
74
+ logger?.warn(`The 'baseHref' field under 'i18n.sourceLocale' is deprecated and will be removed in future versions. ` +
75
+ `Please use 'subPath' instead.\nNote: 'subPath' defines the URL segment for the locale, acting ` +
76
+ `as both the HTML base HREF and the directory name for output.\nBy default, ` +
77
+ `if not specified, 'subPath' uses the locale code.`);
67
78
  rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref;
68
79
  }
80
+ if (metadata.sourceLocale.subPath !== undefined) {
81
+ ensureValidsubPath(metadata.sourceLocale.subPath, 'i18n.sourceLocale.subPath');
82
+ rawsubPath = metadata.sourceLocale.subPath;
83
+ }
84
+ if (rawsubPath !== undefined && rawSourceLocaleBaseHref !== undefined) {
85
+ throw new Error(`'i18n.sourceLocale.subPath' and 'i18n.sourceLocale.baseHref' cannot be used together.`);
86
+ }
69
87
  }
70
88
  if (rawSourceLocale !== undefined) {
71
89
  i18n.sourceLocale = rawSourceLocale;
@@ -74,18 +92,31 @@ function createI18nOptions(projectMetadata, inline) {
74
92
  i18n.locales[i18n.sourceLocale] = {
75
93
  files: [],
76
94
  baseHref: rawSourceLocaleBaseHref,
95
+ subPath: rawsubPath ?? i18n.sourceLocale,
77
96
  };
78
97
  if (metadata.locales !== undefined) {
79
98
  ensureObject(metadata.locales, 'i18n locales');
80
99
  for (const [locale, options] of Object.entries(metadata.locales)) {
81
100
  let translationFiles;
82
101
  let baseHref;
102
+ let subPath;
83
103
  if (options && typeof options === 'object' && 'translation' in options) {
84
104
  translationFiles = normalizeTranslationFileOption(options.translation, locale, false);
85
105
  if ('baseHref' in options) {
86
- ensureString(options.baseHref, `i18n locales ${locale} baseHref`);
106
+ ensureString(options.baseHref, `i18n.locales.${locale}.baseHref`);
107
+ logger?.warn(`The 'baseHref' field under 'i18n.locales.${locale}' is deprecated and will be removed in future versions. ` +
108
+ `Please use 'subPath' instead.\nNote: 'subPath' defines the URL segment for the locale, acting ` +
109
+ `as both the HTML base HREF and the directory name for output.\nBy default, ` +
110
+ `if not specified, 'subPath' uses the locale code.`);
87
111
  baseHref = options.baseHref;
88
112
  }
113
+ if ('subPath' in options) {
114
+ ensureString(options.subPath, `i18n.locales.${locale}.subPath`);
115
+ subPath = options.subPath;
116
+ }
117
+ if (subPath !== undefined && baseHref !== undefined) {
118
+ throw new Error(`'i18n.locales.${locale}.subPath' and 'i18n.locales.${locale}.baseHref' cannot be used together.`);
119
+ }
89
120
  }
90
121
  else {
91
122
  translationFiles = normalizeTranslationFileOption(options, locale, true);
@@ -96,9 +127,21 @@ function createI18nOptions(projectMetadata, inline) {
96
127
  i18n.locales[locale] = {
97
128
  files: translationFiles.map((file) => ({ path: file })),
98
129
  baseHref,
130
+ subPath: subPath ?? locale,
99
131
  };
100
132
  }
101
133
  }
134
+ // Check that subPaths are unique.
135
+ const localesData = Object.entries(i18n.locales);
136
+ for (let i = 0; i < localesData.length; i++) {
137
+ const [localeA, { subPath: subPathA }] = localesData[i];
138
+ for (let j = i + 1; j < localesData.length; j++) {
139
+ const [localeB, { subPath: subPathB }] = localesData[j];
140
+ if (subPathA === subPathB) {
141
+ throw new Error(`Invalid i18n configuration: Locales '${localeA}' and '${localeB}' cannot have the same subPath: '${subPathB}'.`);
142
+ }
143
+ }
144
+ }
102
145
  if (inline === true) {
103
146
  i18n.inlineLocales.add(i18n.sourceLocale);
104
147
  Object.keys(i18n.locales).forEach((locale) => i18n.inlineLocales.add(locale));
@@ -107,7 +107,7 @@ async function autoCsp(html, unsafeEval = false) {
107
107
  * loader script to the collection of hashes to add to the <meta> tag CSP.
108
108
  */
109
109
  function emitLoaderScript() {
110
- const loaderScript = createLoaderScript(scriptContent);
110
+ const loaderScript = createLoaderScript(scriptContent, /* enableTrustedTypes = */ false);
111
111
  hashes.push(hashTextContent(loaderScript));
112
112
  rewriter.emitRaw(`<script>${loaderScript}</script>`);
113
113
  scriptContent = [];
@@ -161,7 +161,7 @@ async function autoCsp(html, unsafeEval = false) {
161
161
  return;
162
162
  }
163
163
  }
164
- if (tag.tagName === 'body' || tag.tagName === 'html') {
164
+ if (tag.tagName === 'head' || tag.tagName === 'body' || tag.tagName === 'html') {
165
165
  // Write the loader script if a string of <script>s were the last opening tag of the document.
166
166
  if (scriptContent.length > 0) {
167
167
  emitLoaderScript();
@@ -259,7 +259,7 @@ function createLoaderScript(srcList, enableTrustedTypes = false) {
259
259
  // URI encoding means value can't escape string, JS, or HTML context.
260
260
  const srcAttr = encodeURI(s.src).replaceAll("'", "\\'");
261
261
  // Can only be 'module' or a JS MIME type or an empty string.
262
- const typeAttr = s.type ? "'" + s.type + "'" : undefined;
262
+ const typeAttr = s.type ? "'" + s.type + "'" : "''";
263
263
  const asyncAttr = s.async ? 'true' : 'false';
264
264
  const deferAttr = s.defer ? 'true' : 'false';
265
265
  return `['${srcAttr}', ${typeAttr}, ${asyncAttr}, ${deferAttr}]`;
@@ -278,7 +278,7 @@ function createLoaderScript(srcList, enableTrustedTypes = false) {
278
278
  s.type = scriptUrl[1];
279
279
  s.async = !!scriptUrl[2];
280
280
  s.defer = !!scriptUrl[3];
281
- document.body.appendChild(s);
281
+ document.lastElementChild.appendChild(s);
282
282
  });\n`
283
283
  : `
284
284
  var scripts = [${srcListFormatted}];
@@ -288,6 +288,6 @@ function createLoaderScript(srcList, enableTrustedTypes = false) {
288
288
  s.type = scriptUrl[1];
289
289
  s.async = !!scriptUrl[2];
290
290
  s.defer = !!scriptUrl[3];
291
- document.body.appendChild(s);
291
+ document.lastElementChild.appendChild(s);
292
292
  });\n`;
293
293
  }
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.normalizeCacheOptions = normalizeCacheOptions;
11
11
  const node_path_1 = require("node:path");
12
12
  /** Version placeholder is replaced during the build process with actual package version */
13
- const VERSION = '19.1.0-next.0';
13
+ const VERSION = '19.1.0-next.2';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -19,6 +19,10 @@ const node_path_1 = require("node:path");
19
19
  const node_url_1 = require("node:url");
20
20
  const url_1 = require("url");
21
21
  const javascript_transformer_1 = require("../../../tools/esbuild/javascript-transformer");
22
+ /**
23
+ * @note For some unknown reason, setting `globalThis.ngServerMode = true` does not work when using ESM loader hooks.
24
+ */
25
+ const NG_SERVER_MODE_INIT_BYTES = new TextEncoder().encode('var ngServerMode=true;');
22
26
  /**
23
27
  * Node.js ESM loader to redirect imports to in memory files.
24
28
  * @see: https://nodejs.org/api/esm.html#loaders for more information about loaders.
@@ -107,7 +111,11 @@ async function load(url, context, nextLoad) {
107
111
  // need linking are ESM only.
108
112
  if (format === 'module' && isFileProtocol(url)) {
109
113
  const filePath = (0, url_1.fileURLToPath)(url);
110
- const source = await javascriptTransformer.transformFile(filePath);
114
+ let source = await javascriptTransformer.transformFile(filePath);
115
+ if (filePath.includes('@angular/')) {
116
+ // Prepend 'var ngServerMode=true;' to the source.
117
+ source = Buffer.concat([NG_SERVER_MODE_INIT_BYTES, source]);
118
+ }
111
119
  return {
112
120
  format,
113
121
  shortCircuit: true,
@@ -5,6 +5,7 @@
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 { Metafile } from 'esbuild';
8
9
  import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
9
10
  import { type BuildOutputFile } from '../../tools/esbuild/bundler-context';
10
11
  export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
@@ -42,12 +43,15 @@ export declare function generateAngularServerAppEngineManifest(i18nOptions: Norm
42
43
  * the application, helping with localization and rendering content specific to the locale.
43
44
  * @param baseHref - The base HREF for the application. This is used to set the base URL
44
45
  * for all relative URLs in the application.
46
+ * @param initialFiles - A list of initial files that preload tags have already been added for.
47
+ * @param metafile - An esbuild metafile object.
48
+ * @param publicPath - The configured public path.
45
49
  *
46
50
  * @returns An object containing:
47
51
  * - `manifestContent`: A string of the SSR manifest content.
48
52
  * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
49
53
  */
50
- export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined, baseHref: string): {
54
+ export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined, baseHref: string, initialFiles: Set<string>, metafile: Metafile, publicPath: string | undefined): {
51
55
  manifestContent: string;
52
56
  serverAssetsChunks: BuildOutputFile[];
53
57
  };
@@ -11,9 +11,9 @@ exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = exports.SERVER_APP_MANIFEST_FILENA
11
11
  exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
12
12
  exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
13
13
  const node_path_1 = require("node:path");
14
- const options_1 = require("../../builders/application/options");
15
14
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
16
15
  const utils_1 = require("../../tools/esbuild/utils");
16
+ const environment_options_1 = require("../environment-options");
17
17
  exports.SERVER_APP_MANIFEST_FILENAME = 'angular-app-manifest.mjs';
18
18
  exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = 'angular-app-engine-manifest.mjs';
19
19
  const MAIN_SERVER_OUTPUT_FILENAME = 'main.server.mjs';
@@ -49,23 +49,28 @@ function escapeUnsafeChars(str) {
49
49
  */
50
50
  function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
51
51
  const entryPoints = {};
52
- if (i18nOptions.shouldInline) {
52
+ const supportedLocales = {};
53
+ if (i18nOptions.shouldInline && !i18nOptions.flatOutput) {
53
54
  for (const locale of i18nOptions.inlineLocales) {
54
- const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
55
- let localeWithBaseHref = (0, options_1.getLocaleBaseHref)('', i18nOptions, locale) || '/';
56
- // Remove leading and trailing slashes.
57
- const start = localeWithBaseHref[0] === '/' ? 1 : 0;
58
- const end = localeWithBaseHref[localeWithBaseHref.length - 1] === '/' ? -1 : undefined;
59
- localeWithBaseHref = localeWithBaseHref.slice(start, end);
60
- entryPoints[localeWithBaseHref] = `() => import('${importPath}')`;
55
+ const { subPath } = i18nOptions.locales[locale];
56
+ const importPath = `${subPath ? `${subPath}/` : ''}${MAIN_SERVER_OUTPUT_FILENAME}`;
57
+ entryPoints[subPath] = `() => import('./${importPath}')`;
58
+ supportedLocales[locale] = subPath;
61
59
  }
62
60
  }
63
61
  else {
64
62
  entryPoints[''] = `() => import('./${MAIN_SERVER_OUTPUT_FILENAME}')`;
63
+ supportedLocales[i18nOptions.sourceLocale] = '';
64
+ }
65
+ // Remove trailing slash but retain leading slash.
66
+ let basePath = baseHref || '/';
67
+ if (basePath.length > 1 && basePath[basePath.length - 1] === '/') {
68
+ basePath = basePath.slice(0, -1);
65
69
  }
66
70
  const manifestContent = `
67
71
  export default {
68
- basePath: '${baseHref ?? '/'}',
72
+ basePath: '${basePath}',
73
+ supportedLocales: ${JSON.stringify(supportedLocales, undefined, 2)},
69
74
  entryPoints: {
70
75
  ${Object.entries(entryPoints)
71
76
  .map(([key, value]) => `'${key}': ${value}`)
@@ -95,12 +100,15 @@ export default {
95
100
  * the application, helping with localization and rendering content specific to the locale.
96
101
  * @param baseHref - The base HREF for the application. This is used to set the base URL
97
102
  * for all relative URLs in the application.
103
+ * @param initialFiles - A list of initial files that preload tags have already been added for.
104
+ * @param metafile - An esbuild metafile object.
105
+ * @param publicPath - The configured public path.
98
106
  *
99
107
  * @returns An object containing:
100
108
  * - `manifestContent`: A string of the SSR manifest content.
101
109
  * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
102
110
  */
103
- function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale, baseHref) {
111
+ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale, baseHref, initialFiles, metafile, publicPath) {
104
112
  const serverAssetsChunks = [];
105
113
  const serverAssets = {};
106
114
  for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
@@ -112,6 +120,11 @@ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles
112
120
  `{size: ${file.size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
113
121
  }
114
122
  }
123
+ // When routes have been extracted, mappings are no longer needed, as preloads will be included in the metadata.
124
+ // When shouldOptimizeChunks is enabled the metadata is no longer correct and thus we cannot generate the mappings.
125
+ const entryPointToBrowserMapping = routes?.length || environment_options_1.shouldOptimizeChunks
126
+ ? undefined
127
+ : generateLazyLoadedFilesMappings(metafile, initialFiles, publicPath);
115
128
  const manifestContent = `
116
129
  export default {
117
130
  bootstrap: () => import('./main.server.mjs').then(m => m.default),
@@ -119,6 +132,7 @@ export default {
119
132
  baseHref: '${baseHref}',
120
133
  locale: ${JSON.stringify(locale)},
121
134
  routes: ${JSON.stringify(routes, undefined, 2)},
135
+ entryPointToBrowserMapping: ${JSON.stringify(entryPointToBrowserMapping, undefined, 2)},
122
136
  assets: {
123
137
  ${Object.entries(serverAssets)
124
138
  .map(([key, value]) => `'${key}': ${value}`)
@@ -128,3 +142,38 @@ export default {
128
142
  `;
129
143
  return { manifestContent, serverAssetsChunks };
130
144
  }
145
+ /**
146
+ * Maps entry points to their corresponding browser bundles for lazy loading.
147
+ *
148
+ * This function processes a metafile's outputs to generate a mapping between browser-side entry points
149
+ * and the associated JavaScript files that should be loaded in the browser. It includes the entry-point's
150
+ * own path and any valid imports while excluding initial files or external resources.
151
+ */
152
+ function generateLazyLoadedFilesMappings(metafile, initialFiles, publicPath = '') {
153
+ const entryPointToBundles = {};
154
+ for (const [fileName, { entryPoint, exports, imports }] of Object.entries(metafile.outputs)) {
155
+ // Skip files that don't have an entryPoint, no exports, or are not .js
156
+ if (!entryPoint || exports?.length < 1 || !fileName.endsWith('.js')) {
157
+ continue;
158
+ }
159
+ const importedPaths = [
160
+ {
161
+ path: `${publicPath}${fileName}`,
162
+ dynamicImport: false,
163
+ },
164
+ ];
165
+ for (const { kind, external, path } of imports) {
166
+ if (external ||
167
+ initialFiles.has(path) ||
168
+ (kind !== 'dynamic-import' && kind !== 'import-statement')) {
169
+ continue;
170
+ }
171
+ importedPaths.push({
172
+ path: `${publicPath}${path}`,
173
+ dynamicImport: kind === 'dynamic-import',
174
+ });
175
+ }
176
+ entryPointToBundles[entryPoint] = importedPaths;
177
+ }
178
+ return entryPointToBundles;
179
+ }
@@ -122,7 +122,7 @@ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxTh
122
122
  const renderingPromises = [];
123
123
  const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute);
124
124
  const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
125
- for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
125
+ for (const { route, redirectTo } of serializableRouteTreeNode) {
126
126
  // Remove the base href from the file output path.
127
127
  const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefWithLeadingSlash)
128
128
  ? addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length))
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["node"]
5
+ }
6
+ }