@angular/build 19.1.6 → 19.1.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 (30) hide show
  1. package/package.json +4 -4
  2. package/src/builders/application/execute-build.js +1 -5
  3. package/src/builders/application/i18n.js +6 -3
  4. package/src/builders/application/options.d.ts +5 -1
  5. package/src/builders/application/options.js +9 -1
  6. package/src/builders/dev-server/schema.d.ts +4 -4
  7. package/src/builders/dev-server/schema.json +2 -2
  8. package/src/builders/dev-server/vite-server.js +19 -7
  9. package/src/tools/esbuild/angular/compiler-plugin.js +1 -1
  10. package/src/tools/esbuild/i18n-inliner.d.ts +9 -0
  11. package/src/tools/esbuild/i18n-inliner.js +110 -21
  12. package/src/tools/esbuild/index-html-generator.js +1 -8
  13. package/src/tools/esbuild/utils.js +4 -1
  14. package/src/tools/vite/middlewares/html-fallback-middleware.js +2 -1
  15. package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -1
  16. package/src/tools/vite/plugins/angular-memory-plugin.js +9 -3
  17. package/src/utils/error.js +2 -2
  18. package/src/utils/index-file/auto-csp.js +1 -1
  19. package/src/utils/index-file/index-html-generator.js +3 -2
  20. package/src/utils/index-file/inline-critical-css.d.ts +1 -0
  21. package/src/utils/index-file/inline-critical-css.js +11 -7
  22. package/src/utils/load-translations.js +3 -3
  23. package/src/utils/normalize-asset-patterns.js +3 -3
  24. package/src/utils/normalize-cache.js +1 -1
  25. package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +1 -2
  26. package/src/utils/server-rendering/manifest.js +7 -2
  27. package/src/utils/server-rendering/prerender.js +3 -3
  28. package/src/utils/server-rendering/render-worker.js +2 -2
  29. package/src/utils/server-rendering/routes-extractor-worker.js +2 -2
  30. package/src/utils/service-worker.js +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/build",
3
- "version": "19.1.6",
3
+ "version": "19.1.8",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,8 +23,8 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/core": "19.1.6",
27
- "@angular-devkit/architect": "0.1901.6",
26
+ "@angular-devkit/core": "19.1.8",
27
+ "@angular-devkit/architect": "0.1901.8",
28
28
  "@babel/core": "7.26.0",
29
29
  "@babel/helper-annotate-as-pure": "7.25.9",
30
30
  "@babel/helper-split-export-declaration": "7.24.7",
@@ -58,7 +58,7 @@
58
58
  "@angular/localize": "^19.0.0",
59
59
  "@angular/platform-server": "^19.0.0",
60
60
  "@angular/service-worker": "^19.0.0",
61
- "@angular/ssr": "^19.1.6",
61
+ "@angular/ssr": "^19.1.8",
62
62
  "less": "^4.2.0",
63
63
  "ng-packagr": "^19.0.0",
64
64
  "postcss": "^8.4.0",
@@ -148,7 +148,6 @@ async function executeBuild(options, context, rebuildState) {
148
148
  }
149
149
  const { metafile, initialFiles, outputFiles } = bundlingResult;
150
150
  executionResult.outputFiles.push(...outputFiles);
151
- const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo);
152
151
  // Analyze files for bundle budget failures if present
153
152
  let budgetFailures;
154
153
  if (options.budgets) {
@@ -191,10 +190,6 @@ async function executeBuild(options, context, rebuildState) {
191
190
  if (serverEntryPoint) {
192
191
  executionResult.addOutputFile(manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME, (0, manifest_1.generateAngularServerAppEngineManifest)(i18nOptions, baseHref), bundler_context_1.BuildOutputFileType.ServerRoot);
193
192
  }
194
- // Override auto-CSP settings if we are serving through Vite middleware.
195
- if (context.builder.builderName === 'dev-server' && options.security) {
196
- options.security.autoCsp = false;
197
- }
198
193
  // Perform i18n translation inlining if enabled
199
194
  if (i18nOptions.shouldInline) {
200
195
  const result = await (0, i18n_1.inlineI18n)(metafile, options, executionResult, initialFiles);
@@ -218,6 +213,7 @@ async function executeBuild(options, context, rebuildState) {
218
213
  executionResult.addOutputFile('stats.json', JSON.stringify(metafile, null, 2), bundler_context_1.BuildOutputFileType.Root);
219
214
  }
220
215
  if (!jsonLogs) {
216
+ const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo);
221
217
  executionResult.addLog((0, utils_1.logBuildStats)(metafile, outputFiles, initialFiles, budgetFailures, colors, changedFiles, estimatedTransferSizes, !!ssrOptions, verbose));
222
218
  }
223
219
  return executionResult;
@@ -26,12 +26,13 @@ const options_1 = require("./options");
26
26
  * @param initialFiles A map containing initial file information for the executed build.
27
27
  */
28
28
  async function inlineI18n(metafile, options, executionResult, initialFiles) {
29
- const { i18nOptions, optimizationOptions, baseHref } = options;
29
+ const { i18nOptions, optimizationOptions, baseHref, cacheOptions } = options;
30
30
  // Create the multi-threaded inliner with common options and the files generated from the build.
31
31
  const inliner = new i18n_inliner_1.I18nInliner({
32
32
  missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning',
33
33
  outputFiles: executionResult.outputFiles,
34
34
  shouldOptimize: optimizationOptions.scripts,
35
+ persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined,
35
36
  }, environment_options_1.maxWorkers);
36
37
  const inlineResult = {
37
38
  errors: [],
@@ -41,6 +42,8 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
41
42
  // For each active locale, use the inliner to process the output files of the build.
42
43
  const updatedOutputFiles = [];
43
44
  const updatedAssetFiles = [];
45
+ // Root and SSR entry files are not modified.
46
+ const unModifiedOutputFiles = executionResult.outputFiles.filter(({ type }) => type === bundler_context_1.BuildOutputFileType.Root || type === bundler_context_1.BuildOutputFileType.ServerRoot);
44
47
  try {
45
48
  for (const locale of i18nOptions.inlineLocales) {
46
49
  // A locale specific set of files is returned from the inliner.
@@ -51,7 +54,7 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
51
54
  const { errors, warnings, additionalAssets, additionalOutputFiles, prerenderedRoutes: generatedRoutes, } = await (0, execute_post_bundle_1.executePostBundleSteps)(metafile, {
52
55
  ...options,
53
56
  baseHref: (0, options_1.getLocaleBaseHref)(baseHref, i18nOptions, locale) ?? baseHref,
54
- }, localeOutputFiles, executionResult.assetFiles, initialFiles, locale);
57
+ }, [...unModifiedOutputFiles, ...localeOutputFiles], executionResult.assetFiles, initialFiles, locale);
55
58
  localeOutputFiles.push(...additionalOutputFiles);
56
59
  inlineResult.errors.push(...errors);
57
60
  inlineResult.warnings.push(...warnings);
@@ -81,7 +84,7 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
81
84
  // Update the result with all localized files.
82
85
  executionResult.outputFiles = [
83
86
  // Root and SSR entry files are not modified.
84
- ...executionResult.outputFiles.filter(({ type }) => type === bundler_context_1.BuildOutputFileType.Root || type === bundler_context_1.BuildOutputFileType.ServerRoot),
87
+ ...unModifiedOutputFiles,
85
88
  // Updated files for each locale.
86
89
  ...updatedOutputFiles,
87
90
  ];
@@ -196,7 +196,11 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
196
196
  partialSSRBuild: boolean;
197
197
  externalRuntimeStyles: boolean | undefined;
198
198
  instrumentForCoverage: ((filename: string) => boolean) | undefined;
199
- security: import("./schema").Security | undefined;
199
+ security: {
200
+ autoCsp: {
201
+ unsafeEval: boolean;
202
+ } | undefined;
203
+ };
200
204
  templateUpdates: boolean;
201
205
  incrementalResults: boolean;
202
206
  }>;
@@ -236,8 +236,16 @@ async function normalizeOptions(context, projectName, options, extensions) {
236
236
  throw new Error('The "index" option cannot be set to false when enabling "ssr", "prerender" or "app-shell".');
237
237
  }
238
238
  }
239
+ const autoCsp = options.security?.autoCsp;
240
+ const security = {
241
+ autoCsp: autoCsp
242
+ ? {
243
+ unsafeEval: autoCsp === true ? false : !!autoCsp.unsafeEval,
244
+ }
245
+ : undefined,
246
+ };
239
247
  // Initial options to keep
240
- const { allowedCommonJsDependencies, aot = true, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, security, } = options;
248
+ const { allowedCommonJsDependencies, aot = true, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, } = options;
241
249
  // Return all the normalized options
242
250
  return {
243
251
  advancedOptimizations: !!aot && optimizationOptions.scripts,
@@ -3,8 +3,8 @@
3
3
  */
4
4
  export type Schema = {
5
5
  /**
6
- * The hosts that can access the development server. This option sets the Vite option of the
7
- * same name. For further details:
6
+ * The hosts that the development server will respond to. This option sets the Vite option
7
+ * of the same name. For further details:
8
8
  * https://vite.dev/config/server-options.html#server-allowedhosts
9
9
  */
10
10
  allowedHosts?: AllowedHosts;
@@ -86,8 +86,8 @@ export type Schema = {
86
86
  watch?: boolean;
87
87
  };
88
88
  /**
89
- * The hosts that can access the development server. This option sets the Vite option of the
90
- * same name. For further details:
89
+ * The hosts that the development server will respond to. This option sets the Vite option
90
+ * of the same name. For further details:
91
91
  * https://vite.dev/config/server-options.html#server-allowedhosts
92
92
  */
93
93
  export type AllowedHosts = string[] | boolean;
@@ -37,12 +37,12 @@
37
37
  "description": "SSL certificate to use for serving HTTPS."
38
38
  },
39
39
  "allowedHosts": {
40
- "description": "The hosts that can access the development server. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts",
40
+ "description": "The hosts that the development server will respond to. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts",
41
41
  "default": [],
42
42
  "oneOf": [
43
43
  {
44
44
  "type": "array",
45
- "description": "List of hosts that are allowed to access the development server.",
45
+ "description": "A list of hosts that the development server will respond to.",
46
46
  "items": {
47
47
  "type": "string"
48
48
  }
@@ -82,6 +82,10 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
82
82
  browserOptions.prerender = undefined;
83
83
  browserOptions.ssr ||= true;
84
84
  }
85
+ // Disable auto CSP.
86
+ browserOptions.security = {
87
+ autoCsp: false,
88
+ };
85
89
  // Set all packages as external to support Vite's prebundle caching
86
90
  browserOptions.externalPackages = serverOptions.prebundle;
87
91
  // Disable generating a full manifest with routes.
@@ -530,6 +534,7 @@ function updateResultRecord(outputPath, file, normalizePath, htmlIndexPath, gene
530
534
  }
531
535
  }
532
536
  }
537
+ // eslint-disable-next-line max-lines-per-function
533
538
  async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, componentStyles, templateUpdates, prebundleLoaderExtensions, define, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
534
539
  const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
535
540
  // dynamically import Vite for ESM compatibility
@@ -546,6 +551,14 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
546
551
  ssrFiles = ['./main.server.mjs', './server.mjs'];
547
552
  break;
548
553
  }
554
+ /**
555
+ * Required when using `externalDependencies` to prevent Vite load errors.
556
+ *
557
+ * @note Can be removed if Vite introduces native support for externals.
558
+ * @note Vite misresolves browser modules in SSR when accessing URLs with multiple segments
559
+ * (e.g., 'foo/bar'), as they are not correctly re-based from the base href.
560
+ */
561
+ const preTransformRequests = externalMetadata.explicitBrowser.length === 0 && ssrMode === plugins_1.ServerSsrMode.NoSsr;
549
562
  const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite');
550
563
  const configuration = {
551
564
  configFile: false,
@@ -575,14 +588,10 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
575
588
  preserveSymlinks,
576
589
  },
577
590
  dev: {
578
- // This is needed when `externalDependencies` is used to prevent Vite load errors.
579
- // NOTE: If Vite adds direct support for externals, this can be removed.
580
- // NOTE: Vite breaks the resolution of browser modules in SSR
581
- // when accessing a url with two or more segments (e.g., 'foo/bar'),
582
- // as they are not re-based from the base href.
583
- preTransformRequests: externalMetadata.explicitBrowser.length === 0 && ssrMode === plugins_1.ServerSsrMode.NoSsr,
591
+ preTransformRequests,
584
592
  },
585
593
  server: {
594
+ preTransformRequests,
586
595
  warmup: {
587
596
  ssrFiles,
588
597
  },
@@ -603,6 +612,9 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
603
612
  ? (proxy ?? {})
604
613
  : proxy,
605
614
  cors: {
615
+ // This will add the header `Access-Control-Allow-Origin: http://example.com`,
616
+ // where `http://example.com` is the requesting origin.
617
+ origin: true,
606
618
  // Allow preflight requests to be proxied.
607
619
  preflightContinue: true,
608
620
  },
@@ -660,7 +672,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
660
672
  outputFiles,
661
673
  templateUpdates,
662
674
  external: externalMetadata.explicitBrowser,
663
- skipViteClient: serverOptions.liveReload === false && serverOptions.hmr === false,
675
+ disableViteTransport: !serverOptions.liveReload,
664
676
  }),
665
677
  ],
666
678
  // Browser only optimizeDeps. (This does not run for SSR dependencies).
@@ -402,7 +402,7 @@ function createCompilerPlugin(pluginOptions, compilationOrFactory, stylesheetBun
402
402
  const replacement = pluginOptions.fileReplacements?.[path.normalize(args.path)];
403
403
  if (replacement) {
404
404
  return {
405
- contents: await Promise.resolve().then(() => __importStar(require('fs/promises'))).then(({ readFile }) => readFile(path.normalize(replacement))),
405
+ contents: await Promise.resolve().then(() => __importStar(require('node:fs/promises'))).then(({ readFile }) => readFile(path.normalize(replacement))),
406
406
  loader: 'json',
407
407
  watchFiles: [replacement],
408
408
  };
@@ -13,6 +13,7 @@ export interface I18nInlinerOptions {
13
13
  missingTranslation: 'error' | 'warning' | 'ignore';
14
14
  outputFiles: BuildOutputFile[];
15
15
  shouldOptimize?: boolean;
16
+ persistentCachePath?: string;
16
17
  }
17
18
  /**
18
19
  * A class that performs i18n translation inlining of JavaScript code.
@@ -22,6 +23,7 @@ export interface I18nInlinerOptions {
22
23
  */
23
24
  export declare class I18nInliner {
24
25
  #private;
26
+ private readonly options;
25
27
  constructor(options: I18nInlinerOptions, maxThreads?: number);
26
28
  /**
27
29
  * Performs inlining of translations for the provided locale and translations. The files that
@@ -41,4 +43,11 @@ export declare class I18nInliner {
41
43
  * @returns A void promise that resolves when closing is complete.
42
44
  */
43
45
  close(): Promise<void>;
46
+ /**
47
+ * Initializes the cache for storing translated bundles.
48
+ * If the cache is already initialized, it does nothing.
49
+ *
50
+ * @returns A promise that resolves once the cache initialization process is complete.
51
+ */
52
+ private initCache;
44
53
  }
@@ -6,12 +6,47 @@
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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
9
42
  var __importDefault = (this && this.__importDefault) || function (mod) {
10
43
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
44
  };
12
45
  Object.defineProperty(exports, "__esModule", { value: true });
13
46
  exports.I18nInliner = void 0;
14
47
  const node_assert_1 = __importDefault(require("node:assert"));
48
+ const node_crypto_1 = require("node:crypto");
49
+ const node_path_1 = require("node:path");
15
50
  const worker_pool_1 = require("../../utils/worker-pool");
16
51
  const bundler_context_1 = require("./bundler-context");
17
52
  const utils_1 = require("./utils");
@@ -27,37 +62,37 @@ const LOCALIZE_KEYWORD = '$localize';
27
62
  * localize function (`$localize`).
28
63
  */
29
64
  class I18nInliner {
65
+ options;
66
+ #cacheInitFailed = false;
30
67
  #workerPool;
68
+ #cache;
31
69
  #localizeFiles;
32
70
  #unmodifiedFiles;
33
- #fileToType = new Map();
34
71
  constructor(options, maxThreads) {
72
+ this.options = options;
35
73
  this.#unmodifiedFiles = [];
74
+ const { outputFiles, shouldOptimize, missingTranslation } = options;
36
75
  const files = new Map();
37
76
  const pendingMaps = [];
38
- for (const file of options.outputFiles) {
77
+ for (const file of outputFiles) {
39
78
  if (file.type === bundler_context_1.BuildOutputFileType.Root || file.type === bundler_context_1.BuildOutputFileType.ServerRoot) {
40
79
  // Skip also the server entry-point.
41
80
  // Skip stats and similar files.
42
81
  continue;
43
82
  }
44
- this.#fileToType.set(file.path, file.type);
45
- if (file.path.endsWith('.js') || file.path.endsWith('.mjs')) {
83
+ const fileExtension = (0, node_path_1.extname)(file.path);
84
+ if (fileExtension === '.js' || fileExtension === '.mjs') {
46
85
  // Check if localizations are present
47
86
  const contentBuffer = Buffer.isBuffer(file.contents)
48
87
  ? file.contents
49
88
  : Buffer.from(file.contents.buffer, file.contents.byteOffset, file.contents.byteLength);
50
89
  const hasLocalize = contentBuffer.includes(LOCALIZE_KEYWORD);
51
90
  if (hasLocalize) {
52
- // A Blob is an immutable data structure that allows sharing the data between workers
53
- // without copying until the data is actually used within a Worker. This is useful here
54
- // since each file may not actually be processed in each Worker and the Blob avoids
55
- // unneeded repeat copying of potentially large JavaScript files.
56
- files.set(file.path, new Blob([file.contents]));
91
+ files.set(file.path, file);
57
92
  continue;
58
93
  }
59
94
  }
60
- else if (file.path.endsWith('.js.map')) {
95
+ else if (fileExtension === '.map') {
61
96
  // The related JS file may not have been checked yet. To ensure that map files are not
62
97
  // missed, store any pending map files and check them after all output files.
63
98
  pendingMaps.push(file);
@@ -68,7 +103,7 @@ class I18nInliner {
68
103
  // Check if any pending map files should be processed by checking if the parent JS file is present
69
104
  for (const file of pendingMaps) {
70
105
  if (files.has(file.path.slice(0, -4))) {
71
- files.set(file.path, new Blob([file.contents]));
106
+ files.set(file.path, file);
72
107
  }
73
108
  else {
74
109
  this.#unmodifiedFiles.push(file);
@@ -80,9 +115,13 @@ class I18nInliner {
80
115
  maxThreads,
81
116
  // Extract options to ensure only the named options are serialized and sent to the worker
82
117
  workerData: {
83
- missingTranslation: options.missingTranslation,
84
- shouldOptimize: options.shouldOptimize,
85
- files,
118
+ missingTranslation,
119
+ shouldOptimize,
120
+ // A Blob is an immutable data structure that allows sharing the data between workers
121
+ // without copying until the data is actually used within a Worker. This is useful here
122
+ // since each file may not actually be processed in each Worker and the Blob avoids
123
+ // unneeded repeat copying of potentially large JavaScript files.
124
+ files: new Map(Array.from(files, ([name, file]) => [name, new Blob([file.contents])])),
86
125
  },
87
126
  });
88
127
  }
@@ -95,18 +134,41 @@ class I18nInliner {
95
134
  * @returns A promise that resolves to an array of OutputFiles representing a translated result.
96
135
  */
97
136
  async inlineForLocale(locale, translation) {
137
+ await this.initCache();
138
+ const { shouldOptimize, missingTranslation } = this.options;
98
139
  // Request inlining for each file that contains localize calls
99
140
  const requests = [];
100
- for (const filename of this.#localizeFiles.keys()) {
141
+ let fileCacheKeyBase;
142
+ for (const [filename, file] of this.#localizeFiles) {
143
+ let cacheKey;
101
144
  if (filename.endsWith('.map')) {
102
145
  continue;
103
146
  }
104
- const fileRequest = this.#workerPool.run({
105
- filename,
106
- locale,
107
- translation,
147
+ let cacheResultPromise = Promise.resolve(null);
148
+ if (this.#cache) {
149
+ fileCacheKeyBase ??= Buffer.from(JSON.stringify({ locale, translation, missingTranslation, shouldOptimize }), 'utf-8');
150
+ // NOTE: If additional options are added, this may need to be updated.
151
+ // TODO: Consider xxhash or similar instead of SHA256
152
+ cacheKey = (0, node_crypto_1.createHash)('sha256')
153
+ .update(file.hash)
154
+ .update(filename)
155
+ .update(fileCacheKeyBase)
156
+ .digest('hex');
157
+ // Failure to get the value should not fail the transform
158
+ cacheResultPromise = this.#cache.get(cacheKey).catch(() => null);
159
+ }
160
+ const fileResult = cacheResultPromise.then(async (cachedResult) => {
161
+ if (cachedResult) {
162
+ return cachedResult;
163
+ }
164
+ const result = await this.#workerPool.run({ filename, locale, translation });
165
+ if (this.#cache && cacheKey) {
166
+ // Failure to set the value should not fail the transform
167
+ await this.#cache.set(cacheKey, result).catch(() => { });
168
+ }
169
+ return result;
108
170
  });
109
- requests.push(fileRequest);
171
+ requests.push(fileResult);
110
172
  }
111
173
  // Wait for all file requests to complete
112
174
  const rawResults = await Promise.all(requests);
@@ -115,7 +177,7 @@ class I18nInliner {
115
177
  const warnings = [];
116
178
  const outputFiles = [
117
179
  ...rawResults.flatMap(({ file, code, map, messages }) => {
118
- const type = this.#fileToType.get(file);
180
+ const type = this.#localizeFiles.get(file)?.type;
119
181
  (0, node_assert_1.default)(type !== undefined, 'localized file should always have a type' + file);
120
182
  const resultFiles = [(0, utils_1.createOutputFile)(file, code, type)];
121
183
  if (map) {
@@ -146,5 +208,32 @@ class I18nInliner {
146
208
  close() {
147
209
  return this.#workerPool.destroy();
148
210
  }
211
+ /**
212
+ * Initializes the cache for storing translated bundles.
213
+ * If the cache is already initialized, it does nothing.
214
+ *
215
+ * @returns A promise that resolves once the cache initialization process is complete.
216
+ */
217
+ async initCache() {
218
+ if (this.#cache || this.#cacheInitFailed) {
219
+ return;
220
+ }
221
+ const { persistentCachePath } = this.options;
222
+ // Webcontainers currently do not support this persistent cache store.
223
+ if (!persistentCachePath || process.versions.webcontainer) {
224
+ return;
225
+ }
226
+ // Initialize a persistent cache for i18n transformations.
227
+ try {
228
+ const { LmbdCacheStore } = await Promise.resolve().then(() => __importStar(require('./lmdb-cache-store')));
229
+ this.#cache = new LmbdCacheStore((0, node_path_1.join)(persistentCachePath, 'angular-i18n.db'));
230
+ }
231
+ catch {
232
+ this.#cacheInitFailed = true;
233
+ // eslint-disable-next-line no-console
234
+ console.warn('Unable to initialize JavaScript cache storage.\n' +
235
+ 'This will not affect the build output content but may result in slower builds.');
236
+ }
237
+ }
149
238
  }
150
239
  exports.I18nInliner = I18nInliner;
@@ -59,13 +59,6 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
59
59
  }
60
60
  throw new Error(`Output file does not exist: ${relativefilePath}`);
61
61
  };
62
- // Read the Auto CSP options.
63
- const autoCsp = buildOptions.security?.autoCsp;
64
- const autoCspOptions = autoCsp === true
65
- ? { unsafeEval: false }
66
- : autoCsp
67
- ? { unsafeEval: !!autoCsp.unsafeEval }
68
- : undefined;
69
62
  // Create an index HTML generator that reads from the in-memory output files
70
63
  const indexHtmlGenerator = new index_html_generator_1.IndexHtmlGenerator({
71
64
  indexPath: indexHtmlOptions.input,
@@ -78,7 +71,7 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
78
71
  generateDedicatedSSRContent: !!(buildOptions.ssrOptions ||
79
72
  buildOptions.prerenderOptions ||
80
73
  buildOptions.appShellOptions),
81
- autoCsp: autoCspOptions,
74
+ autoCsp: buildOptions.security.autoCsp,
82
75
  });
83
76
  indexHtmlGenerator.readAsset = readAsset;
84
77
  return indexHtmlGenerator.process({
@@ -35,6 +35,9 @@ const manifest_1 = require("../../utils/server-rendering/manifest");
35
35
  const stats_table_1 = require("../../utils/stats-table");
36
36
  const bundler_context_1 = require("./bundler-context");
37
37
  function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
38
+ // Remove the i18n subpath in case the build is using i18n.
39
+ // en-US/main.js -> main.js
40
+ const normalizedChangedFiles = new Set([...(changedFiles ?? [])].map((f) => (0, node_path_1.basename)(f)));
38
41
  const browserStats = [];
39
42
  const serverStats = [];
40
43
  let unchangedCount = 0;
@@ -45,7 +48,7 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
45
48
  continue;
46
49
  }
47
50
  // Show only changed files if a changed list is provided
48
- if (changedFiles && !changedFiles.has(file)) {
51
+ if (normalizedChangedFiles.size && !normalizedChangedFiles.has(file)) {
49
52
  ++unchangedCount;
50
53
  continue;
51
54
  }
@@ -20,7 +20,8 @@ function angularHtmlFallbackMiddleware(req, _res, next) {
20
20
  }
21
21
  if (req.url) {
22
22
  const mimeType = (0, utils_1.lookupMimeTypeFromRequest)(req.url);
23
- if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml') {
23
+ if ((mimeType === 'text/html' || mimeType === 'application/xhtml+xml') &&
24
+ !/^\/index\.(?:csr\.)?html/.test(req.url)) {
24
25
  // eslint-disable-next-line no-console
25
26
  console.warn(`Request for HTML file "${req.url}" was received but no asset found. Asset may be missing from build.`);
26
27
  }
@@ -12,7 +12,7 @@ interface AngularMemoryPluginOptions {
12
12
  outputFiles: AngularMemoryOutputFiles;
13
13
  templateUpdates?: ReadonlyMap<string, string>;
14
14
  external?: string[];
15
- skipViteClient?: boolean;
15
+ disableViteTransport?: boolean;
16
16
  }
17
17
  export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Promise<Plugin>;
18
18
  export {};
@@ -73,7 +73,7 @@ async function createAngularMemoryPlugin(options) {
73
73
  const codeContents = outputFiles.get(relativeFile)?.contents;
74
74
  if (codeContents === undefined) {
75
75
  if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
76
- return options.skipViteClient ? '' : loadViteClientCode(file);
76
+ return loadViteClientCode(file, options.disableViteTransport);
77
77
  }
78
78
  return undefined;
79
79
  }
@@ -96,9 +96,9 @@ async function createAngularMemoryPlugin(options) {
96
96
  * @param file The absolute path to the Vite client code.
97
97
  * @returns
98
98
  */
99
- async function loadViteClientCode(file) {
99
+ async function loadViteClientCode(file, disableViteTransport = false) {
100
100
  const originalContents = await (0, promises_1.readFile)(file, 'utf-8');
101
- const updatedContents = originalContents.replace(`"You can also disable this overlay by setting ",
101
+ let updatedContents = originalContents.replace(`"You can also disable this overlay by setting ",
102
102
  h("code", { part: "config-option-name" }, "server.hmr.overlay"),
103
103
  " to ",
104
104
  h("code", { part: "config-option-value" }, "false"),
@@ -106,5 +106,11 @@ async function loadViteClientCode(file) {
106
106
  h("code", { part: "config-file-name" }, hmrConfigName),
107
107
  "."`, '');
108
108
  (0, node_assert_1.default)(originalContents !== updatedContents, 'Failed to update Vite client error overlay text.');
109
+ if (disableViteTransport) {
110
+ const previousUpdatedContents = updatedContents;
111
+ updatedContents = updatedContents.replace('transport.connect(handleMessage)', '');
112
+ (0, node_assert_1.default)(previousUpdatedContents !== updatedContents, 'Failed to update Vite client WebSocket disable.');
113
+ updatedContents = updatedContents.replace('console.debug("[vite] connecting...")', '');
114
+ }
109
115
  return updatedContents;
110
116
  }
@@ -11,10 +11,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.assertIsError = assertIsError;
14
- const assert_1 = __importDefault(require("assert"));
14
+ const node_assert_1 = __importDefault(require("node:assert"));
15
15
  function assertIsError(value) {
16
16
  const isError = value instanceof Error ||
17
17
  // The following is needing to identify errors coming from RxJs.
18
18
  (typeof value === 'object' && value && 'name' in value && 'message' in value);
19
- (0, assert_1.default)(isError, 'catch clause variable is not an Error instance');
19
+ (0, node_assert_1.default)(isError, 'catch clause variable is not an Error instance');
20
20
  }
@@ -112,7 +112,7 @@ async function autoCsp(html, unsafeEval = false) {
112
112
  rewriter.emitRaw(`<script>${loaderScript}</script>`);
113
113
  scriptContent = [];
114
114
  }
115
- rewriter.on('startTag', (tag, html) => {
115
+ rewriter.on('startTag', (tag) => {
116
116
  if (tag.tagName === 'script') {
117
117
  openedScriptTag = tag;
118
118
  const src = getScriptAttributeValue(tag, 'src');
@@ -32,7 +32,7 @@ class IndexHtmlGenerator {
32
32
  this.plugins = [augmentIndexHtmlPlugin(this), ...extraCommonPlugins, postTransformPlugin(this)];
33
33
  // CSR plugins
34
34
  if (options?.optimization?.styles?.inlineCritical) {
35
- this.csrPlugins.push(inlineCriticalCssPlugin(this));
35
+ this.csrPlugins.push(inlineCriticalCssPlugin(this, !!options.autoCsp));
36
36
  }
37
37
  this.csrPlugins.push(addNoncePlugin());
38
38
  // SSR plugins
@@ -127,11 +127,12 @@ function inlineFontsPlugin({ options }) {
127
127
  });
128
128
  return async (html) => inlineFontsProcessor.process(html);
129
129
  }
130
- function inlineCriticalCssPlugin(generator) {
130
+ function inlineCriticalCssPlugin(generator, autoCsp) {
131
131
  const inlineCriticalCssProcessor = new inline_critical_css_1.InlineCriticalCssProcessor({
132
132
  minify: generator.options.optimization?.styles.minify,
133
133
  deployUrl: generator.options.deployUrl,
134
134
  readAsset: (filePath) => generator.readAsset(filePath),
135
+ autoCsp,
135
136
  });
136
137
  return async (html, options) => inlineCriticalCssProcessor.process(html, { outputPath: options.outputPath });
137
138
  }
@@ -12,6 +12,7 @@ export interface InlineCriticalCssProcessorOptions {
12
12
  minify?: boolean;
13
13
  deployUrl?: string;
14
14
  readAsset?: (path: string) => Promise<string>;
15
+ autoCsp?: boolean;
15
16
  }
16
17
  export declare class InlineCriticalCssProcessor {
17
18
  protected readonly options: InlineCriticalCssProcessorOptions;
@@ -113,7 +113,7 @@ class BeastiesExtended extends BeastiesBase {
113
113
  }
114
114
  const returnValue = await super.embedLinkedStylesheet(link, document);
115
115
  const cspNonce = this.findCspNonce(document);
116
- if (cspNonce) {
116
+ if (cspNonce || this.optionsExtended.autoCsp) {
117
117
  const beastiesMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
118
118
  if (beastiesMedia) {
119
119
  // If there's a Beasties-generated `onload` handler and the file has an Angular CSP nonce,
@@ -128,11 +128,13 @@ class BeastiesExtended extends BeastiesBase {
128
128
  // a way of doing that at the moment so we fall back to doing it any time a `link` tag is
129
129
  // inserted. We mitigate it by only iterating the direct children of the `<head>` which
130
130
  // should be pretty shallow.
131
- document.head.children.forEach((child) => {
132
- if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
133
- child.setAttribute('nonce', cspNonce);
134
- }
135
- });
131
+ if (cspNonce) {
132
+ document.head.children.forEach((child) => {
133
+ if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
134
+ child.setAttribute('nonce', cspNonce);
135
+ }
136
+ });
137
+ }
136
138
  }
137
139
  return returnValue;
138
140
  }
@@ -159,8 +161,10 @@ class BeastiesExtended extends BeastiesBase {
159
161
  return;
160
162
  }
161
163
  const script = document.createElement('script');
162
- script.setAttribute('nonce', nonce);
163
164
  script.textContent = LINK_LOAD_SCRIPT_CONTENT;
165
+ if (nonce) {
166
+ script.setAttribute('nonce', nonce);
167
+ }
164
168
  // Prepend the script to the head since it needs to
165
169
  // run as early as possible, before the `link` tags.
166
170
  document.head.insertBefore(script, link);
@@ -41,8 +41,8 @@ var __importStar = (this && this.__importStar) || (function () {
41
41
  })();
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.createTranslationLoader = createTranslationLoader;
44
- const crypto_1 = require("crypto");
45
- const fs = __importStar(require("fs"));
44
+ const node_crypto_1 = require("node:crypto");
45
+ const fs = __importStar(require("node:fs"));
46
46
  const load_esm_1 = require("./load-esm");
47
47
  async function createTranslationLoader() {
48
48
  const { parsers, diagnostics } = await importParsers();
@@ -55,7 +55,7 @@ async function createTranslationLoader() {
55
55
  // Types don't overlap here so we need to use any.
56
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
57
  const { locale, translations } = parser.parse(path, content, analysis.hint);
58
- const integrity = 'sha256-' + (0, crypto_1.createHash)('sha256').update(content).digest('base64');
58
+ const integrity = 'sha256-' + (0, node_crypto_1.createHash)('sha256').update(content).digest('base64');
59
59
  return { format, locale, translations, diagnostics, integrity };
60
60
  }
61
61
  else {
@@ -45,9 +45,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.MissingAssetSourceRootException = void 0;
47
47
  exports.normalizeAssetPatterns = normalizeAssetPatterns;
48
- const fs_1 = require("fs");
49
48
  const node_assert_1 = __importDefault(require("node:assert"));
50
- const path = __importStar(require("path"));
49
+ const node_fs_1 = require("node:fs");
50
+ const path = __importStar(require("node:path"));
51
51
  class MissingAssetSourceRootException extends Error {
52
52
  constructor(path) {
53
53
  super(`The ${path} asset path must start with the project source root.`);
@@ -73,7 +73,7 @@ function normalizeAssetPatterns(assetPatterns, workspaceRoot, projectRoot, proje
73
73
  let glob, input;
74
74
  let isDirectory = false;
75
75
  try {
76
- isDirectory = (0, fs_1.statSync)(resolvedAssetPath).isDirectory();
76
+ isDirectory = (0, node_fs_1.statSync)(resolvedAssetPath).isDirectory();
77
77
  }
78
78
  catch {
79
79
  isDirectory = true;
@@ -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.6';
13
+ const VERSION = '19.1.8';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -17,7 +17,6 @@ const node_assert_1 = __importDefault(require("node:assert"));
17
17
  const node_crypto_1 = require("node:crypto");
18
18
  const node_path_1 = require("node:path");
19
19
  const node_url_1 = require("node:url");
20
- const url_1 = require("url");
21
20
  const javascript_transformer_1 = require("../../../tools/esbuild/javascript-transformer");
22
21
  /**
23
22
  * @note For some unknown reason, setting `globalThis.ngServerMode = true` does not work when using ESM loader hooks.
@@ -110,7 +109,7 @@ async function load(url, context, nextLoad) {
110
109
  // Only module files potentially require transformation. Angular libraries that would
111
110
  // need linking are ESM only.
112
111
  if (format === 'module' && isFileProtocol(url)) {
113
- const filePath = (0, url_1.fileURLToPath)(url);
112
+ const filePath = (0, node_url_1.fileURLToPath)(url);
114
113
  let source = await javascriptTransformer.transformFile(filePath);
115
114
  if (filePath.includes('@angular/')) {
116
115
  // Prepend 'var ngServerMode=true;' to the source.
@@ -11,6 +11,7 @@ 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 node_vm_1 = require("node:vm");
14
15
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
15
16
  const utils_1 = require("../../tools/esbuild/utils");
16
17
  const environment_options_1 = require("../environment-options");
@@ -115,9 +116,13 @@ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles
115
116
  const extension = (0, node_path_1.extname)(file.path);
116
117
  if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
117
118
  const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
118
- serverAssetsChunks.push((0, utils_1.createOutputFile)(jsChunkFilePath, `export default \`${escapeUnsafeChars(file.text)}\`;`, bundler_context_1.BuildOutputFileType.ServerApplication));
119
+ const escapedContent = escapeUnsafeChars(file.text);
120
+ serverAssetsChunks.push((0, utils_1.createOutputFile)(jsChunkFilePath, `export default \`${escapedContent}\`;`, bundler_context_1.BuildOutputFileType.ServerApplication));
121
+ // This is needed because JavaScript engines script parser convert `\r\n` to `\n` in template literals,
122
+ // which can result in an incorrect byte length.
123
+ const size = (0, node_vm_1.runInThisContext)(`new TextEncoder().encode(\`${escapedContent}\`).byteLength`);
119
124
  serverAssets[file.path] =
120
- `{size: ${file.size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
125
+ `{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
121
126
  }
122
127
  }
123
128
  // When routes have been extracted, mappings are no longer needed, as preloads will be included in the metadata.
@@ -128,11 +128,11 @@ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxTh
128
128
  try {
129
129
  const renderingPromises = [];
130
130
  const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute);
131
- const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);
131
+ const baseHrefPathnameWithLeadingSlash = new URL(baseHref, 'http://localhost').pathname;
132
132
  for (const { route, redirectTo } of serializableRouteTreeNode) {
133
133
  // Remove the base href from the file output path.
134
- const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefWithLeadingSlash)
135
- ? addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length))
134
+ const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefPathnameWithLeadingSlash)
135
+ ? addLeadingSlash(route.slice(baseHrefPathnameWithLeadingSlash.length))
136
136
  : route;
137
137
  const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
138
138
  if (typeof redirectTo === 'string') {
@@ -7,14 +7,14 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- const worker_threads_1 = require("worker_threads");
10
+ const node_worker_threads_1 = require("node:worker_threads");
11
11
  const fetch_patch_1 = require("./fetch-patch");
12
12
  const launch_server_1 = require("./launch-server");
13
13
  const load_esm_from_memory_1 = require("./load-esm-from-memory");
14
14
  /**
15
15
  * This is passed as workerData when setting up the worker via the `piscina` package.
16
16
  */
17
- const { outputMode, hasSsrEntry } = worker_threads_1.workerData;
17
+ const { outputMode, hasSsrEntry } = node_worker_threads_1.workerData;
18
18
  let serverURL = launch_server_1.DEFAULT_URL;
19
19
  /**
20
20
  * Renders each route in routes and writes them to <outputPath>/<route>/index.html.
@@ -7,7 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- const worker_threads_1 = require("worker_threads");
10
+ const node_worker_threads_1 = require("node:worker_threads");
11
11
  const schema_1 = require("../../builders/application/schema");
12
12
  const fetch_patch_1 = require("./fetch-patch");
13
13
  const launch_server_1 = require("./launch-server");
@@ -15,7 +15,7 @@ const load_esm_from_memory_1 = require("./load-esm-from-memory");
15
15
  /**
16
16
  * This is passed as workerData when setting up the worker via the `piscina` package.
17
17
  */
18
- const { outputMode, hasSsrEntry } = worker_threads_1.workerData;
18
+ const { outputMode, hasSsrEntry } = node_worker_threads_1.workerData;
19
19
  /** Renders an application based on a provided options. */
20
20
  async function extractRoutes() {
21
21
  const serverURL = outputMode !== undefined && hasSsrEntry ? await (0, launch_server_1.launchServer)() : launch_server_1.DEFAULT_URL;
@@ -43,9 +43,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.augmentAppWithServiceWorker = augmentAppWithServiceWorker;
44
44
  exports.augmentAppWithServiceWorkerEsbuild = augmentAppWithServiceWorkerEsbuild;
45
45
  exports.augmentAppWithServiceWorkerCore = augmentAppWithServiceWorkerCore;
46
- const crypto = __importStar(require("crypto"));
46
+ const crypto = __importStar(require("node:crypto"));
47
47
  const node_fs_1 = require("node:fs");
48
- const path = __importStar(require("path"));
48
+ const path = __importStar(require("node:path"));
49
49
  const bundler_context_1 = require("../tools/esbuild/bundler-context");
50
50
  const error_1 = require("./error");
51
51
  const load_esm_1 = require("./load-esm");