@angular/build 19.0.2 → 19.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/build",
3
- "version": "19.0.2",
3
+ "version": "19.0.3",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,7 +23,7 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/architect": "0.1900.2",
26
+ "@angular-devkit/architect": "0.1900.3",
27
27
  "@babel/core": "7.26.0",
28
28
  "@babel/helper-annotate-as-pure": "7.25.9",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
@@ -57,7 +57,7 @@
57
57
  "@angular/localize": "^19.0.0",
58
58
  "@angular/platform-server": "^19.0.0",
59
59
  "@angular/service-worker": "^19.0.0",
60
- "@angular/ssr": "^19.0.2",
60
+ "@angular/ssr": "^19.0.3",
61
61
  "less": "^4.2.0",
62
62
  "postcss": "^8.4.0",
63
63
  "tailwindcss": "^2.0.0 || ^3.0.0",
@@ -37,7 +37,7 @@ async function executePostBundleSteps(options, outputFiles, assetFiles, initialF
37
37
  const allErrors = [];
38
38
  const allWarnings = [];
39
39
  const prerenderedRoutes = {};
40
- const { baseHref = '/', serviceWorker, indexHtmlOptions, optimizationOptions, sourcemapOptions, outputMode, serverEntryPoint, prerenderOptions, appShellOptions, workspaceRoot, partialSSRBuild, } = options;
40
+ const { baseHref = '/', serviceWorker, i18nOptions, indexHtmlOptions, optimizationOptions, sourcemapOptions, outputMode, serverEntryPoint, prerenderOptions, appShellOptions, workspaceRoot, partialSSRBuild, } = options;
41
41
  // Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR).
42
42
  // NOTE: Critical CSS inlining is deliberately omitted here, as it will be handled during server rendering.
43
43
  // Additionally, when using prerendering or AppShell, the index HTML file may be regenerated.
@@ -56,7 +56,7 @@ async function executePostBundleSteps(options, outputFiles, assetFiles, initialF
56
56
  }
57
57
  // Create server manifest
58
58
  if (serverEntryPoint) {
59
- const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, undefined, locale);
59
+ const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, undefined, locale, baseHref);
60
60
  additionalOutputFiles.push(...serverAssetsChunks, (0, utils_1.createOutputFile)(manifest_1.SERVER_APP_MANIFEST_FILENAME, manifestContent, bundler_context_1.BuildOutputFileType.ServerApplication));
61
61
  }
62
62
  // Pre-render (SSG) and App-shell
@@ -95,7 +95,7 @@ async function executePostBundleSteps(options, outputFiles, assetFiles, initialF
95
95
  // Regenerate the manifest to append route tree. This is only needed if SSR is enabled.
96
96
  const manifest = additionalOutputFiles.find((f) => f.path === manifest_1.SERVER_APP_MANIFEST_FILENAME);
97
97
  (0, node_assert_1.default)(manifest, `${manifest_1.SERVER_APP_MANIFEST_FILENAME} was not found in output files.`);
98
- const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, serializableRouteTreeNodeForManifest, locale);
98
+ const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, serializableRouteTreeNodeForManifest, locale, baseHref);
99
99
  for (const chunk of serverAssetsChunks) {
100
100
  const idx = additionalOutputFiles.findIndex(({ path }) => path === chunk.path);
101
101
  if (idx === -1) {
@@ -194,24 +194,25 @@ async function normalizeOptions(context, projectName, options, extensions) {
194
194
  let indexOutput;
195
195
  // The output file will be created within the configured output path
196
196
  if (typeof options.index === 'string') {
197
- /**
198
- * If SSR is activated, create a distinct entry file for the `index.html`.
199
- * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file
200
- * if it exists (handling SSG).
201
- *
202
- * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server.
203
- *
204
- * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`.
205
- */
206
- const indexBaseName = node_path_1.default.basename(options.index);
207
- indexOutput =
208
- (ssrOptions || prerenderOptions) && indexBaseName === 'index.html'
209
- ? exports.INDEX_HTML_CSR
210
- : indexBaseName;
197
+ indexOutput = options.index;
211
198
  }
212
199
  else {
213
200
  indexOutput = options.index.output || 'index.html';
214
201
  }
202
+ /**
203
+ * If SSR is activated, create a distinct entry file for the `index.html`.
204
+ * This is necessary because numerous server/cloud providers automatically serve the `index.html` as a static file
205
+ * if it exists (handling SSG).
206
+ *
207
+ * For instance, accessing `foo.com/` would lead to `foo.com/index.html` being served instead of hitting the server.
208
+ *
209
+ * This approach can also be applied to service workers, where the `index.csr.html` is served instead of the prerendered `index.html`.
210
+ */
211
+ const indexBaseName = node_path_1.default.basename(indexOutput);
212
+ indexOutput =
213
+ (ssrOptions || prerenderOptions) && indexBaseName === 'index.html'
214
+ ? exports.INDEX_HTML_CSR
215
+ : indexBaseName;
215
216
  indexHtmlOptions = {
216
217
  input: node_path_1.default.join(workspaceRoot, typeof options.index === 'string' ? options.index : options.index.input),
217
218
  output: indexOutput,
@@ -386,6 +386,22 @@ function createCompilerPlugin(pluginOptions, stylesheetBundler) {
386
386
  };
387
387
  }, true);
388
388
  }));
389
+ // Add a load handler if there are file replacement option entries for JSON files
390
+ if (pluginOptions.fileReplacements &&
391
+ Object.keys(pluginOptions.fileReplacements).some((value) => value.endsWith('.json'))) {
392
+ build.onLoad({ filter: /\.json$/ }, (0, load_result_cache_1.createCachedLoad)(pluginOptions.loadResultCache, async (args) => {
393
+ const replacement = pluginOptions.fileReplacements?.[path.normalize(args.path)];
394
+ if (replacement) {
395
+ return {
396
+ contents: await Promise.resolve().then(() => __importStar(require('fs/promises'))).then(({ readFile }) => readFile(path.normalize(replacement))),
397
+ loader: 'json',
398
+ watchFiles: [replacement],
399
+ };
400
+ }
401
+ // If no replacement defined, let esbuild handle it directly
402
+ return null;
403
+ }));
404
+ }
389
405
  // Setup bundling of component templates and stylesheets when in JIT mode
390
406
  if (pluginOptions.jit) {
391
407
  (0, jit_plugin_callbacks_1.setupJitPluginCallbacks)(build, stylesheetBundler, additionalResults, pluginOptions.loadResultCache);
@@ -49,7 +49,7 @@ const virtual_module_plugin_1 = require("./virtual-module-plugin");
49
49
  * @returns An esbuild BuildOptions object.
50
50
  */
51
51
  function createGlobalScriptsBundleOptions(options, target, initial) {
52
- const { globalScripts, optimizationOptions, outputNames, preserveSymlinks, sourcemapOptions, jsonLogs, workspaceRoot, } = options;
52
+ const { globalScripts, optimizationOptions, outputNames, preserveSymlinks, sourcemapOptions, jsonLogs, workspaceRoot, define, } = options;
53
53
  const namespace = 'angular:script/global';
54
54
  const entryPoints = {};
55
55
  let found = false;
@@ -83,6 +83,7 @@ function createGlobalScriptsBundleOptions(options, target, initial) {
83
83
  platform: 'neutral',
84
84
  target,
85
85
  preserveSymlinks,
86
+ define,
86
87
  plugins: [
87
88
  (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
88
89
  (0, virtual_module_plugin_1.createVirtualModulePlugin)({
@@ -21,7 +21,7 @@ const valid_self_closing_tags_1 = require("./valid-self-closing-tags");
21
21
  */
22
22
  // eslint-disable-next-line max-lines-per-function
23
23
  async function augmentIndexHtml(params) {
24
- const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html, imageDomains, } = params;
24
+ const { loadOutputFile, files, entrypoints, sri, deployUrl, lang, baseHref, html, imageDomains } = params;
25
25
  const warnings = [];
26
26
  const errors = [];
27
27
  let { crossOrigin = 'none' } = params;
@@ -57,7 +57,7 @@ async function augmentIndexHtml(params) {
57
57
  }
58
58
  let scriptTags = [];
59
59
  for (const [src, isModule] of scripts) {
60
- const attrs = [`src="${deployUrl}${src}"`];
60
+ const attrs = [`src="${generateUrl(src, deployUrl)}"`];
61
61
  // This is also need for non entry-points as they may contain problematic code.
62
62
  if (isModule) {
63
63
  attrs.push('type="module"');
@@ -77,7 +77,7 @@ async function augmentIndexHtml(params) {
77
77
  let headerLinkTags = [];
78
78
  let bodyLinkTags = [];
79
79
  for (const src of stylesheets) {
80
- const attrs = [`rel="stylesheet"`, `href="${deployUrl}${src}"`];
80
+ const attrs = [`rel="stylesheet"`, `href="${generateUrl(src, deployUrl)}"`];
81
81
  if (crossOrigin !== 'none') {
82
82
  attrs.push(`crossorigin="${crossOrigin}"`);
83
83
  }
@@ -89,7 +89,7 @@ async function augmentIndexHtml(params) {
89
89
  }
90
90
  if (params.hints?.length) {
91
91
  for (const hint of params.hints) {
92
- const attrs = [`rel="${hint.mode}"`, `href="${deployUrl}${hint.url}"`];
92
+ const attrs = [`rel="${hint.mode}"`, `href="${generateUrl(hint.url, deployUrl)}"`];
93
93
  if (hint.mode !== 'modulepreload' && crossOrigin !== 'none') {
94
94
  // Value is considered anonymous by the browser when not present or empty
95
95
  attrs.push(crossOrigin === 'anonymous' ? 'crossorigin' : `crossorigin="${crossOrigin}"`);
@@ -215,6 +215,16 @@ function generateSriAttributes(content) {
215
215
  const hash = (0, node_crypto_1.createHash)(algo).update(content, 'utf8').digest('base64');
216
216
  return `integrity="${algo}-${hash}"`;
217
217
  }
218
+ function generateUrl(value, deployUrl) {
219
+ if (!deployUrl) {
220
+ return value;
221
+ }
222
+ // Skip if root-relative, absolute or protocol relative url
223
+ if (/^((?:\w+:)?\/\/|data:|chrome:|\/)/.test(value)) {
224
+ return value;
225
+ }
226
+ return `${deployUrl}${value}`;
227
+ }
218
228
  function updateAttribute(tag, name, value) {
219
229
  const index = tag.attrs.findIndex((a) => a.name === name);
220
230
  const newValue = { name, value };
@@ -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.0.2';
13
+ const VERSION = '19.0.3';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -40,12 +40,14 @@ export declare function generateAngularServerAppEngineManifest(i18nOptions: Norm
40
40
  * server-side rendering and routing.
41
41
  * @param locale - An optional string representing the locale or language code to be used for
42
42
  * the application, helping with localization and rendering content specific to the locale.
43
+ * @param baseHref - The base HREF for the application. This is used to set the base URL
44
+ * for all relative URLs in the application.
43
45
  *
44
46
  * @returns An object containing:
45
47
  * - `manifestContent`: A string of the SSR manifest content.
46
48
  * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
47
49
  */
48
- export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined): {
50
+ export declare function generateAngularServerAppManifest(additionalHtmlOutputFiles: Map<string, BuildOutputFile>, outputFiles: BuildOutputFile[], inlineCriticalCss: boolean, routes: readonly unknown[] | undefined, locale: string | undefined, baseHref: string): {
49
51
  manifestContent: string;
50
52
  serverAssetsChunks: BuildOutputFile[];
51
53
  };
@@ -48,7 +48,7 @@ function escapeUnsafeChars(str) {
48
48
  * for all relative URLs in the application.
49
49
  */
50
50
  function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
51
- const entryPointsContent = [];
51
+ const entryPoints = {};
52
52
  if (i18nOptions.shouldInline) {
53
53
  for (const locale of i18nOptions.inlineLocales) {
54
54
  const importPath = './' + (i18nOptions.flatOutput ? '' : locale + '/') + MAIN_SERVER_OUTPUT_FILENAME;
@@ -57,18 +57,22 @@ function generateAngularServerAppEngineManifest(i18nOptions, baseHref) {
57
57
  const start = localeWithBaseHref[0] === '/' ? 1 : 0;
58
58
  const end = localeWithBaseHref[localeWithBaseHref.length - 1] === '/' ? -1 : undefined;
59
59
  localeWithBaseHref = localeWithBaseHref.slice(start, end);
60
- entryPointsContent.push(`['${localeWithBaseHref}', () => import('${importPath}')]`);
60
+ entryPoints[localeWithBaseHref] = `() => import('${importPath}')`;
61
61
  }
62
62
  }
63
63
  else {
64
- entryPointsContent.push(`['', () => import('./${MAIN_SERVER_OUTPUT_FILENAME}')]`);
64
+ entryPoints[''] = `() => import('./${MAIN_SERVER_OUTPUT_FILENAME}')`;
65
65
  }
66
66
  const manifestContent = `
67
67
  export default {
68
68
  basePath: '${baseHref ?? '/'}',
69
- entryPoints: new Map([${entryPointsContent.join(', \n')}]),
69
+ entryPoints: {
70
+ ${Object.entries(entryPoints)
71
+ .map(([key, value]) => `'${key}': ${value}`)
72
+ .join(',\n ')}
73
+ },
70
74
  };
71
- `;
75
+ `;
72
76
  return manifestContent;
73
77
  }
74
78
  /**
@@ -89,29 +93,37 @@ export default {
89
93
  * server-side rendering and routing.
90
94
  * @param locale - An optional string representing the locale or language code to be used for
91
95
  * the application, helping with localization and rendering content specific to the locale.
96
+ * @param baseHref - The base HREF for the application. This is used to set the base URL
97
+ * for all relative URLs in the application.
92
98
  *
93
99
  * @returns An object containing:
94
100
  * - `manifestContent`: A string of the SSR manifest content.
95
101
  * - `serverAssetsChunks`: An array of build output files containing the generated assets for the server.
96
102
  */
97
- function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale) {
103
+ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles, inlineCriticalCss, routes, locale, baseHref) {
98
104
  const serverAssetsChunks = [];
99
- const serverAssetsContent = [];
105
+ const serverAssets = {};
100
106
  for (const file of [...additionalHtmlOutputFiles.values(), ...outputFiles]) {
101
107
  const extension = (0, node_path_1.extname)(file.path);
102
108
  if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
103
109
  const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
104
110
  serverAssetsChunks.push((0, utils_1.createOutputFile)(jsChunkFilePath, `export default \`${escapeUnsafeChars(file.text)}\`;`, bundler_context_1.BuildOutputFileType.ServerApplication));
105
- serverAssetsContent.push(`['${file.path}', {size: ${file.size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}]`);
111
+ serverAssets[file.path] =
112
+ `{size: ${file.size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
106
113
  }
107
114
  }
108
115
  const manifestContent = `
109
116
  export default {
110
117
  bootstrap: () => import('./main.server.mjs').then(m => m.default),
111
118
  inlineCriticalCss: ${inlineCriticalCss},
119
+ baseHref: '${baseHref}',
120
+ locale: ${JSON.stringify(locale)},
112
121
  routes: ${JSON.stringify(routes, undefined, 2)},
113
- assets: new Map([\n${serverAssetsContent.join(', \n')}\n]),
114
- locale: ${locale !== undefined ? `'${locale}'` : undefined},
122
+ assets: {
123
+ ${Object.entries(serverAssets)
124
+ .map(([key, value]) => `'${key}': ${value}`)
125
+ .join(',\n ')}
126
+ },
115
127
  };
116
128
  `;
117
129
  return { manifestContent, serverAssetsChunks };
@@ -90,7 +90,7 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerende
90
90
  };
91
91
  }
92
92
  // Render routes
93
- const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, appShellOptions, outputMode, appShellRoute ?? appShellOptions?.route);
93
+ const { errors: renderingErrors, output } = await renderPages(baseHref, sourcemap, serializableRouteTreeNodeForPrerender, maxThreads, workspaceRoot, outputFilesForWorker, assetsReversed, outputMode, appShellRoute ?? appShellOptions?.route);
94
94
  errors.push(...renderingErrors);
95
95
  return {
96
96
  errors,
@@ -99,7 +99,7 @@ async function prerenderPages(workspaceRoot, baseHref, appShellOptions, prerende
99
99
  serializableRouteTreeNode,
100
100
  };
101
101
  }
102
- async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, appShellOptions, outputMode, appShellRoute) {
102
+ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxThreads, workspaceRoot, outputFilesForWorker, assetFilesForWorker, outputMode, appShellRoute) {
103
103
  const output = {};
104
104
  const errors = [];
105
105
  const workerExecArgv = [utils_1.IMPORT_EXEC_ARGV];
@@ -125,7 +125,7 @@ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxTh
125
125
  for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
126
126
  // Remove the base href from the file output path.
127
127
  const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefWithLeadingSlash)
128
- ? addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1))
128
+ ? addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length))
129
129
  : route;
130
130
  const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
131
131
  if (typeof redirectTo === 'string') {
@@ -21,7 +21,12 @@ async function extractRoutes() {
21
21
  const serverURL = outputMode !== undefined && hasSsrEntry ? await (0, launch_server_1.launchServer)() : launch_server_1.DEFAULT_URL;
22
22
  (0, fetch_patch_1.patchFetchToLoadInMemoryAssets)(serverURL);
23
23
  const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = await (0, load_esm_from_memory_1.loadEsmModuleFromMemory)('./main.server.mjs');
24
- const { routeTree, appShellRoute, errors } = await extractRoutesAndCreateRouteTree(serverURL, undefined /** manifest */, outputMode !== undefined /** invokeGetPrerenderParams */, outputMode === schema_1.OutputMode.Server /** includePrerenderFallbackRoutes */);
24
+ const { routeTree, appShellRoute, errors } = await extractRoutesAndCreateRouteTree({
25
+ url: serverURL,
26
+ invokeGetPrerenderParams: outputMode !== undefined,
27
+ includePrerenderFallbackRoutes: outputMode === schema_1.OutputMode.Server,
28
+ signal: AbortSignal.timeout(30_000),
29
+ });
25
30
  return {
26
31
  errors,
27
32
  appShellRoute,