@angular/build 20.0.0-next.9 → 20.0.0-rc.1

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": "20.0.0-next.9",
3
+ "version": "20.0.0-rc.1",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,34 +23,34 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/architect": "0.2000.0-next.9",
27
- "@babel/core": "7.26.10",
28
- "@babel/helper-annotate-as-pure": "7.25.9",
26
+ "@angular-devkit/architect": "0.2000.0-rc.1",
27
+ "@babel/core": "7.27.1",
28
+ "@babel/helper-annotate-as-pure": "7.27.1",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
30
- "@inquirer/confirm": "5.1.9",
30
+ "@inquirer/confirm": "5.1.10",
31
31
  "@vitejs/plugin-basic-ssl": "2.0.0",
32
32
  "beasties": "0.3.3",
33
33
  "browserslist": "^4.23.0",
34
- "esbuild": "0.25.3",
34
+ "esbuild": "0.25.4",
35
35
  "https-proxy-agent": "7.0.6",
36
36
  "istanbul-lib-instrument": "6.0.3",
37
37
  "jsonc-parser": "3.3.1",
38
- "listr2": "8.3.2",
38
+ "listr2": "8.3.3",
39
39
  "magic-string": "0.30.17",
40
40
  "mrmime": "2.0.1",
41
41
  "parse5-html-rewriting-stream": "7.1.0",
42
42
  "picomatch": "4.0.2",
43
- "piscina": "4.9.2",
44
- "rollup": "4.40.1",
45
- "sass": "1.87.0",
46
- "semver": "7.7.1",
43
+ "piscina": "5.0.0",
44
+ "rollup": "4.40.2",
45
+ "sass": "1.88.0",
46
+ "semver": "7.7.2",
47
47
  "source-map-support": "0.5.21",
48
48
  "tinyglobby": "0.2.13",
49
- "vite": "6.3.4",
49
+ "vite": "6.3.5",
50
50
  "watchpack": "2.4.2"
51
51
  },
52
52
  "optionalDependencies": {
53
- "lmdb": "3.2.6"
53
+ "lmdb": "3.3.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@angular/core": "^20.0.0 || ^20.0.0-next.0",
@@ -60,7 +60,7 @@
60
60
  "@angular/platform-browser": "^20.0.0 || ^20.0.0-next.0",
61
61
  "@angular/platform-server": "^20.0.0 || ^20.0.0-next.0",
62
62
  "@angular/service-worker": "^20.0.0 || ^20.0.0-next.0",
63
- "@angular/ssr": "^20.0.0-next.9",
63
+ "@angular/ssr": "^20.0.0-rc.1",
64
64
  "karma": "^6.4.0",
65
65
  "less": "^4.2.0",
66
66
  "ng-packagr": "^20.0.0 || ^20.0.0-next.0",
@@ -112,9 +112,9 @@
112
112
  "type": "git",
113
113
  "url": "https://github.com/angular/angular-cli.git"
114
114
  },
115
- "packageManager": "pnpm@9.15.6",
115
+ "packageManager": "pnpm@9.15.9",
116
116
  "engines": {
117
- "node": "^20.11.1 || >=22.11.0",
117
+ "node": "^20.11.1 || ^22.11.0 || >=24.0.0",
118
118
  "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
119
119
  "yarn": ">= 1.13.0"
120
120
  },
@@ -229,8 +229,9 @@ async function executeBuild(options, context, rebuildState) {
229
229
  const result = await (0, execute_post_bundle_1.executePostBundleSteps)(metafile, options, executionResult.outputFiles, executionResult.assetFiles, initialFiles,
230
230
  // Set lang attribute to the defined source locale if present
231
231
  i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined);
232
- executionResult.addErrors(result.errors);
233
- executionResult.addWarnings(result.warnings);
232
+ // Deduplicate and add errors and warnings
233
+ executionResult.addErrors([...new Set(result.errors)]);
234
+ executionResult.addWarnings([...new Set(result.warnings)]);
234
235
  executionResult.addPrerenderedRoutes(result.prerenderedRoutes);
235
236
  executionResult.outputFiles.push(...result.additionalOutputFiles);
236
237
  executionResult.assetFiles.push(...result.additionalAssets);
@@ -92,6 +92,21 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
92
92
  if (!i18nOptions.flatOutput) {
93
93
  executionResult.assetFiles = updatedAssetFiles;
94
94
  }
95
+ // Inline any template updates if present
96
+ if (executionResult.templateUpdates?.size) {
97
+ // The development server only allows a single locale but issue a warning if used programmatically (experimental)
98
+ // with multiple locales and template HMR.
99
+ if (i18nOptions.inlineLocales.size > 1) {
100
+ inlineResult.warnings.push(`Component HMR updates can only be inlined with a single locale. The first locale will be used.`);
101
+ }
102
+ const firstLocale = [...i18nOptions.inlineLocales][0];
103
+ for (const [id, content] of executionResult.templateUpdates) {
104
+ const templateUpdateResult = await inliner.inlineTemplateUpdate(firstLocale, i18nOptions.locales[firstLocale].translation, content, id);
105
+ executionResult.templateUpdates.set(id, templateUpdateResult.code);
106
+ inlineResult.errors.push(...templateUpdateResult.errors);
107
+ inlineResult.warnings.push(...templateUpdateResult.warnings);
108
+ }
109
+ }
95
110
  return inlineResult;
96
111
  }
97
112
  /**
@@ -69,7 +69,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
69
69
  if (options.forceI18nFlatOutput) {
70
70
  i18nOptions.flatOutput = true;
71
71
  }
72
- const entryPoints = normalizeEntryPoints(workspaceRoot, options.browser, options.entryPoints);
72
+ const entryPoints = normalizeEntryPoints(workspaceRoot, projectSourceRoot, options.browser, options.entryPoints);
73
73
  const tsconfig = node_path_1.default.join(workspaceRoot, options.tsConfig);
74
74
  const optimizationOptions = (0, utils_1.normalizeOptimization)(options.optimization);
75
75
  const sourcemapOptions = (0, utils_1.normalizeSourceMaps)(options.sourceMap ?? false);
@@ -355,23 +355,22 @@ async function getTailwindConfig(searchDirectories, workspaceRoot, context) {
355
355
  * @param entryPoints Set of entry points to use if provided.
356
356
  * @returns An object mapping entry point names to their file paths.
357
357
  */
358
- function normalizeEntryPoints(workspaceRoot, browser, entryPoints = new Set()) {
358
+ function normalizeEntryPoints(workspaceRoot, projectSourceRoot, browser, entryPoints) {
359
359
  if (browser === '') {
360
360
  throw new Error('`browser` option cannot be an empty string.');
361
361
  }
362
362
  // `browser` and `entryPoints` are mutually exclusive.
363
- if (browser && entryPoints.size > 0) {
363
+ if (browser && entryPoints) {
364
364
  throw new Error('Only one of `browser` or `entryPoints` may be provided.');
365
365
  }
366
- if (!browser && entryPoints.size === 0) {
367
- // Schema should normally reject this case, but programmatic usages of the builder might make this mistake.
368
- throw new Error('Either `browser` or at least one `entryPoints` value must be provided.');
369
- }
370
- // Schema types force `browser` to always be provided, but it may be omitted when the builder is invoked programmatically.
371
366
  if (browser) {
372
367
  // Use `browser` alone.
373
368
  return { 'main': node_path_1.default.join(workspaceRoot, browser) };
374
369
  }
370
+ else if (!entryPoints) {
371
+ // Default browser entry if no explicit entry points
372
+ return { 'main': node_path_1.default.join(projectSourceRoot, 'main.ts') };
373
+ }
375
374
  else if (entryPoints instanceof Map) {
376
375
  return Object.fromEntries(Array.from(entryPoints.entries(), ([name, entryPoint]) => {
377
376
  // Get the full file path to a relative entry point input. Leave bare specifiers alone so they are resolved as modules.
@@ -27,7 +27,7 @@ export type Schema = {
27
27
  * The full path for the browser entry point to the application, relative to the current
28
28
  * workspace.
29
29
  */
30
- browser: string;
30
+ browser?: string;
31
31
  /**
32
32
  * Budget thresholds to ensure parts of your application stay within boundaries which you
33
33
  * set.
@@ -616,7 +616,7 @@
616
616
  }
617
617
  },
618
618
  "additionalProperties": false,
619
- "required": ["browser", "tsConfig"],
619
+ "required": ["tsConfig"],
620
620
  "definitions": {
621
621
  "assetPattern": {
622
622
  "oneOf": [
@@ -88,7 +88,7 @@ class AngularAssetsMiddleware {
88
88
  }
89
89
  switch (file.origin) {
90
90
  case 'disk':
91
- this.serveFile(file.inputPath, undefined, res);
91
+ this.serveFile(file.inputPath, undefined, res, undefined, undefined, /* doNotCache */ true);
92
92
  break;
93
93
  case 'memory':
94
94
  // Include pathname to help with Content-Type headers.
@@ -16,6 +16,7 @@ const node_crypto_1 = require("node:crypto");
16
16
  const node_module_1 = require("node:module");
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin");
19
+ const error_1 = require("../../utils/error");
19
20
  const load_esm_1 = require("../../utils/load-esm");
20
21
  const application_1 = require("../application");
21
22
  const results_1 = require("../application/results");
@@ -27,6 +28,7 @@ const options_1 = require("./options");
27
28
  /**
28
29
  * @experimental Direct usage of this function is considered experimental.
29
30
  */
31
+ // eslint-disable-next-line max-lines-per-function
30
32
  async function* execute(options, context, extensions = {}) {
31
33
  // Determine project name from builder context target
32
34
  const projectName = context.target?.project;
@@ -55,7 +57,19 @@ async function* execute(options, context, extensions = {}) {
55
57
  }
56
58
  const entryPoints = (0, find_tests_1.getTestEntrypoints)(testFiles, { projectSourceRoot, workspaceRoot });
57
59
  entryPoints.set('init-testbed', 'angular:test-bed-init');
58
- const { startVitest } = await (0, load_esm_1.loadEsmModule)('vitest/node');
60
+ let vitestNodeModule;
61
+ try {
62
+ vitestNodeModule = await (0, load_esm_1.loadEsmModule)('vitest/node');
63
+ }
64
+ catch (error) {
65
+ (0, error_1.assertIsError)(error);
66
+ if (error.code !== 'ERR_MODULE_NOT_FOUND') {
67
+ throw error;
68
+ }
69
+ context.logger.error('The `vitest` package was not found. Please install the package and rerun the test command.');
70
+ return;
71
+ }
72
+ const { startVitest } = vitestNodeModule;
59
73
  // Setup test file build options based on application build target options
60
74
  const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), await context.getBuilderNameForTarget(normalizedOptions.buildTarget)));
61
75
  if (buildTargetOptions.polyfills?.includes('zone.js')) {
@@ -65,6 +79,7 @@ async function* execute(options, context, extensions = {}) {
65
79
  const buildOptions = {
66
80
  ...buildTargetOptions,
67
81
  watch: normalizedOptions.watch,
82
+ incrementalResults: normalizedOptions.watch,
68
83
  outputPath,
69
84
  index: false,
70
85
  browser: undefined,
@@ -89,9 +104,28 @@ async function* execute(options, context, extensions = {}) {
89
104
  loadContent: async () => {
90
105
  const contents = [
91
106
  // Initialize the Angular testing environment
92
- `import { getTestBed } from '@angular/core/testing';`,
107
+ `import { NgModule } from '@angular/core';`,
108
+ `import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing';`,
93
109
  `import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
94
- `getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {`,
110
+ `import { beforeEach, afterEach } from 'vitest';`,
111
+ '',
112
+ normalizedOptions.providersFile
113
+ ? `import providers from './${node_path_1.default
114
+ .relative(projectSourceRoot, normalizedOptions.providersFile)
115
+ .replace(/.[mc]?ts$/, '')
116
+ .replace(/\\/g, '/')}'`
117
+ : 'const providers = [];',
118
+ '',
119
+ // Same as https://github.com/angular/angular/blob/05a03d3f975771bb59c7eefd37c01fa127ee2229/packages/core/testing/src/test_hooks.ts#L21-L29
120
+ `beforeEach(getCleanupHook(false));`,
121
+ `afterEach(getCleanupHook(true));`,
122
+ '',
123
+ `@NgModule({`,
124
+ ` providers,`,
125
+ `})`,
126
+ `export class TestModule {}`,
127
+ '',
128
+ `getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {`,
95
129
  ` errorOnUnknownElements: true,`,
96
130
  ` errorOnUnknownProperties: true,`,
97
131
  '});',
@@ -106,59 +140,58 @@ async function* execute(options, context, extensions = {}) {
106
140
  extensions.codePlugins.unshift(virtualTestBedInit);
107
141
  let instance;
108
142
  // Setup vitest browser options if configured
109
- let browser;
110
- if (normalizedOptions.browsers) {
111
- const provider = findBrowserProvider(projectSourceRoot);
112
- if (!provider) {
113
- context.logger.error('The "browsers" option requires either "playwright" or "webdriverio" to be installed within the project.' +
114
- ' Please install one of these packages and rerun the test command.');
115
- return { success: false };
116
- }
117
- browser = {
118
- enabled: true,
119
- provider,
120
- instances: normalizedOptions.browsers.map((browserName) => ({
121
- browser: browserName,
122
- })),
123
- };
124
- }
125
- for await (const result of (0, application_1.buildApplicationInternal)(buildOptions, context, extensions)) {
126
- if (result.kind === results_1.ResultKind.Failure) {
127
- continue;
128
- }
129
- else if (result.kind !== results_1.ResultKind.Full) {
130
- node_assert_1.default.fail('A full build result is required from the application builder.');
143
+ const { browser, errors } = setupBrowserConfiguration(normalizedOptions.browsers, projectSourceRoot);
144
+ if (errors?.length) {
145
+ errors.forEach((error) => context.logger.error(error));
146
+ return { success: false };
147
+ }
148
+ // Add setup file entries for TestBed initialization and project polyfills
149
+ const setupFiles = ['init-testbed.js'];
150
+ if (buildTargetOptions?.polyfills?.length) {
151
+ setupFiles.unshift('polyfills.js');
152
+ }
153
+ try {
154
+ for await (const result of (0, application_1.buildApplicationInternal)(buildOptions, context, extensions)) {
155
+ if (result.kind === results_1.ResultKind.Failure) {
156
+ continue;
157
+ }
158
+ else if (result.kind !== results_1.ResultKind.Full && result.kind !== results_1.ResultKind.Incremental) {
159
+ node_assert_1.default.fail('A full and/or incremental build result is required from the application builder.');
160
+ }
161
+ (0, node_assert_1.default)(result.files, 'Builder did not provide result files.');
162
+ await (0, application_builder_1.writeTestFiles)(result.files, outputPath);
163
+ instance ??= await startVitest('test', undefined /* cliFilters */, undefined /* options */, {
164
+ test: {
165
+ root: outputPath,
166
+ globals: true,
167
+ setupFiles,
168
+ // Use `jsdom` if no browsers are explicitly configured.
169
+ // `node` is effectively no "environment" and the default.
170
+ environment: browser ? 'node' : 'jsdom',
171
+ watch: normalizedOptions.watch,
172
+ browser,
173
+ reporters: normalizedOptions.reporters ?? ['default'],
174
+ coverage: {
175
+ enabled: normalizedOptions.codeCoverage,
176
+ exclude: normalizedOptions.codeCoverageExclude,
177
+ excludeAfterRemap: true,
178
+ },
179
+ },
180
+ });
181
+ // Check if all the tests pass to calculate the result
182
+ const testModules = instance.state.getTestModules();
183
+ yield { success: testModules.every((testModule) => testModule.ok()) };
131
184
  }
132
- (0, node_assert_1.default)(result.files, 'Builder did not provide result files.');
133
- await (0, application_builder_1.writeTestFiles)(result.files, outputPath);
134
- const setupFiles = ['init-testbed.js'];
135
- if (buildTargetOptions?.polyfills?.length) {
136
- setupFiles.push('polyfills.js');
185
+ }
186
+ finally {
187
+ if (normalizedOptions.watch) {
188
+ // Vitest will automatically close if not using watch mode
189
+ await instance?.close();
137
190
  }
138
- instance ??= await startVitest('test', undefined /* cliFilters */, undefined /* options */, {
139
- test: {
140
- root: outputPath,
141
- setupFiles,
142
- // Use `jsdom` if no browsers are explicitly configured.
143
- // `node` is effectively no "environment" and the default.
144
- environment: browser ? 'node' : 'jsdom',
145
- watch: normalizedOptions.watch,
146
- browser,
147
- coverage: {
148
- enabled: normalizedOptions.codeCoverage,
149
- exclude: normalizedOptions.codeCoverageExclude,
150
- excludeAfterRemap: true,
151
- },
152
- },
153
- });
154
- // Check if all the tests pass to calculate the result
155
- const testModules = instance.state.getTestModules();
156
- yield { success: testModules.every((testModule) => testModule.ok()) };
157
191
  }
158
192
  }
159
- function findBrowserProvider(projectSourceRoot) {
160
- const projectResolver = (0, node_module_1.createRequire)(projectSourceRoot + '/').resolve;
161
- // These must be installed in the project to be used
193
+ function findBrowserProvider(projectResolver) {
194
+ // One of these must be installed in the project to use browser testing
162
195
  const vitestBuiltinProviders = ['playwright', 'webdriverio'];
163
196
  for (const providerName of vitestBuiltinProviders) {
164
197
  try {
@@ -168,3 +201,35 @@ function findBrowserProvider(projectSourceRoot) {
168
201
  catch { }
169
202
  }
170
203
  }
204
+ function setupBrowserConfiguration(browsers, projectSourceRoot) {
205
+ if (browsers === undefined) {
206
+ return {};
207
+ }
208
+ const projectResolver = (0, node_module_1.createRequire)(projectSourceRoot + '/').resolve;
209
+ let errors;
210
+ try {
211
+ projectResolver('@vitest/browser');
212
+ }
213
+ catch {
214
+ errors ??= [];
215
+ errors.push('The "browsers" option requires the "@vitest/browser" package to be installed within the project.' +
216
+ ' Please install this package and rerun the test command.');
217
+ }
218
+ const provider = findBrowserProvider(projectResolver);
219
+ if (!provider) {
220
+ errors ??= [];
221
+ errors.push('The "browsers" option requires either "playwright" or "webdriverio" to be installed within the project.' +
222
+ ' Please install one of these packages and rerun the test command.');
223
+ }
224
+ if (errors) {
225
+ return { errors };
226
+ }
227
+ const browser = {
228
+ enabled: true,
229
+ provider,
230
+ instances: browsers.map((browserName) => ({
231
+ browser: browserName,
232
+ })),
233
+ };
234
+ return { browser };
235
+ }
@@ -22,5 +22,6 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
22
22
  tsConfig: string;
23
23
  reporters: string[] | undefined;
24
24
  browsers: string[] | undefined;
25
- watch: boolean;
25
+ watch: boolean | undefined;
26
+ providersFile: string | undefined;
26
27
  }>;
@@ -26,7 +26,7 @@ async function normalizeOptions(context, projectName, options) {
26
26
  // Target specifier defaults to the current project's build target using a development configuration
27
27
  const buildTargetSpecifier = options.buildTarget ?? `::development`;
28
28
  const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
29
- const { codeCoverage, codeCoverageExclude, tsConfig, runner, reporters, browsers } = options;
29
+ const { codeCoverage, codeCoverageExclude, tsConfig, runner, reporters, browsers, watch } = options;
30
30
  return {
31
31
  // Project/workspace information
32
32
  workspaceRoot,
@@ -43,8 +43,8 @@ async function normalizeOptions(context, projectName, options) {
43
43
  tsConfig,
44
44
  reporters,
45
45
  browsers,
46
- // TODO: Implement watch support
47
- watch: false,
46
+ watch,
47
+ providersFile: options.providersFile && node_path_1.default.join(workspaceRoot, options.providersFile),
48
48
  };
49
49
  }
50
50
  /**
@@ -34,6 +34,11 @@ export type Schema = {
34
34
  * instead.
35
35
  */
36
36
  include?: string[];
37
+ /**
38
+ * TypeScript file that exports an array of Angular providers to use during test execution.
39
+ * The array must be a default export.
40
+ */
41
+ providersFile?: string;
37
42
  /**
38
43
  * Test runner reporters to use. Directly passed to the test runner.
39
44
  */
@@ -65,6 +65,11 @@
65
65
  "items": {
66
66
  "type": "string"
67
67
  }
68
+ },
69
+ "providersFile": {
70
+ "type": "string",
71
+ "description": "TypeScript file that exports an array of Angular providers to use during test execution. The array must be a default export.",
72
+ "minLength": 1
68
73
  }
69
74
  },
70
75
  "additionalProperties": false,
@@ -121,10 +121,7 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
121
121
  const updateId = encodeURIComponent(`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`);
122
122
  const updateText = angularCompiler.emitHmrUpdateModule(node);
123
123
  // If compiler cannot generate an update for the component, prevent template updates.
124
- // Also prevent template updates if $localize is directly present which also currently
125
- // prevents a template update at runtime.
126
- // TODO: Support localized template update modules and remove this check.
127
- if (updateText === null || updateText.includes('$localize')) {
124
+ if (updateText === null) {
128
125
  // Build is needed if a template cannot be updated
129
126
  templateUpdates = undefined;
130
127
  break;
@@ -13,6 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.BundlerContext = exports.BuildOutputFileType = void 0;
14
14
  const esbuild_1 = require("esbuild");
15
15
  const node_assert_1 = __importDefault(require("node:assert"));
16
+ const node_module_1 = require("node:module");
16
17
  const node_path_1 = require("node:path");
17
18
  const load_result_cache_1 = require("./load-result-cache");
18
19
  const utils_1 = require("./utils");
@@ -198,10 +199,11 @@ class BundlerContext {
198
199
  if (this.incremental) {
199
200
  // Add input files except virtual angular files which do not exist on disk
200
201
  for (const input of Object.keys(result.metafile.inputs)) {
201
- if (!isInternalAngularFile(input)) {
202
- // input file paths are always relative to the workspace root
203
- this.watchFiles.add((0, node_path_1.join)(this.workspaceRoot, input));
202
+ if (isInternalAngularFile(input) || isInternalBundlerFile(input)) {
203
+ continue;
204
204
  }
205
+ // Input file paths are always relative to the workspace root
206
+ this.watchFiles.add((0, node_path_1.join)(this.workspaceRoot, input));
205
207
  }
206
208
  }
207
209
  // Return if the build encountered any errors
@@ -380,3 +382,16 @@ exports.BundlerContext = BundlerContext;
380
382
  function isInternalAngularFile(file) {
381
383
  return file.startsWith('angular:');
382
384
  }
385
+ function isInternalBundlerFile(file) {
386
+ // Bundler virtual files such as "<define:???>" or "<runtime>"
387
+ if (file[0] === '<' && file.at(-1) === '>') {
388
+ return true;
389
+ }
390
+ const DISABLED_BUILTIN = '(disabled):';
391
+ // Disabled node builtins such as "/some/path/(disabled):fs"
392
+ const disabledIndex = file.indexOf(DISABLED_BUILTIN);
393
+ if (disabledIndex >= 0) {
394
+ return node_module_1.builtinModules.includes(file.slice(disabledIndex + DISABLED_BUILTIN.length));
395
+ }
396
+ return false;
397
+ }
@@ -8,7 +8,7 @@
8
8
  /**
9
9
  * The options passed to the inliner for each file request
10
10
  */
11
- interface InlineRequest {
11
+ interface InlineFileRequest {
12
12
  /**
13
13
  * The filename that should be processed. The data for the file is provided to the Worker
14
14
  * during Worker initialization.
@@ -23,14 +23,35 @@ interface InlineRequest {
23
23
  */
24
24
  translation?: Record<string, unknown>;
25
25
  }
26
+ /**
27
+ * The options passed to the inliner for each code request
28
+ */
29
+ interface InlineCodeRequest {
30
+ /**
31
+ * The code that should be processed.
32
+ */
33
+ code: string;
34
+ /**
35
+ * The filename to use in error and warning messages for the provided code.
36
+ */
37
+ filename: string;
38
+ /**
39
+ * The locale specifier that should be used during the inlining process of the file.
40
+ */
41
+ locale: string;
42
+ /**
43
+ * The translation messages for the locale that should be used during the inlining process of the file.
44
+ */
45
+ translation?: Record<string, unknown>;
46
+ }
26
47
  /**
27
48
  * Inlines the provided locale and translation into a JavaScript file that contains `$localize` usage.
28
49
  * This function is the main entry for the Worker's action that is called by the worker pool.
29
50
  *
30
51
  * @param request An InlineRequest object representing the options for inlining
31
- * @returns An array containing the inlined file and optional map content.
52
+ * @returns An object containing the inlined file and optional map content.
32
53
  */
33
- export default function inlineLocale(request: InlineRequest): Promise<{
54
+ export default function inlineFile(request: InlineFileRequest): Promise<{
34
55
  file: string;
35
56
  code: string;
36
57
  map: string | undefined;
@@ -39,4 +60,18 @@ export default function inlineLocale(request: InlineRequest): Promise<{
39
60
  message: string;
40
61
  }[];
41
62
  }>;
63
+ /**
64
+ * Inlines the provided locale and translation into JavaScript code that contains `$localize` usage.
65
+ * This function is a secondary entry primarily for use with component HMR update modules.
66
+ *
67
+ * @param request An InlineRequest object representing the options for inlining
68
+ * @returns An object containing the inlined code.
69
+ */
70
+ export declare function inlineCode(request: InlineCodeRequest): Promise<{
71
+ output: string;
72
+ messages: {
73
+ type: "warning" | "error";
74
+ message: string;
75
+ }[];
76
+ }>;
42
77
  export {};
@@ -10,7 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.default = inlineLocale;
13
+ exports.default = inlineFile;
14
+ exports.inlineCode = inlineCode;
14
15
  const remapping_1 = __importDefault(require("@ampproject/remapping"));
15
16
  const core_1 = require("@babel/core");
16
17
  const node_assert_1 = __importDefault(require("node:assert"));
@@ -25,9 +26,9 @@ const { files, missingTranslation, shouldOptimize } = (node_worker_threads_1.wor
25
26
  * This function is the main entry for the Worker's action that is called by the worker pool.
26
27
  *
27
28
  * @param request An InlineRequest object representing the options for inlining
28
- * @returns An array containing the inlined file and optional map content.
29
+ * @returns An object containing the inlined file and optional map content.
29
30
  */
30
- async function inlineLocale(request) {
31
+ async function inlineFile(request) {
31
32
  const data = files.get(request.filename);
32
33
  (0, node_assert_1.default)(data !== undefined, `Invalid inline request for file '${request.filename}'.`);
33
34
  const code = await data.text();
@@ -40,6 +41,20 @@ async function inlineLocale(request) {
40
41
  messages: result.diagnostics.messages,
41
42
  };
42
43
  }
44
+ /**
45
+ * Inlines the provided locale and translation into JavaScript code that contains `$localize` usage.
46
+ * This function is a secondary entry primarily for use with component HMR update modules.
47
+ *
48
+ * @param request An InlineRequest object representing the options for inlining
49
+ * @returns An object containing the inlined code.
50
+ */
51
+ async function inlineCode(request) {
52
+ const result = await transformWithBabel(request.code, undefined, request);
53
+ return {
54
+ output: result.code,
55
+ messages: result.diagnostics.messages,
56
+ };
57
+ }
43
58
  /**
44
59
  * Cached instance of the `@angular/localize/tools` module.
45
60
  * This is used to remove the need to repeatedly import the module per file translation.
@@ -38,6 +38,11 @@ export declare class I18nInliner {
38
38
  errors: string[];
39
39
  warnings: string[];
40
40
  }>;
41
+ inlineTemplateUpdate(locale: string, translation: Record<string, unknown> | undefined, templateCode: string, templateId: string): Promise<{
42
+ code: string;
43
+ errors: string[];
44
+ warnings: string[];
45
+ }>;
41
46
  /**
42
47
  * Stops all active transformation tasks and shuts down all workers.
43
48
  * @returns A void promise that resolves when closing is complete.
@@ -201,6 +201,32 @@ class I18nInliner {
201
201
  warnings,
202
202
  };
203
203
  }
204
+ async inlineTemplateUpdate(locale, translation, templateCode, templateId) {
205
+ const hasLocalize = templateCode.includes(LOCALIZE_KEYWORD);
206
+ if (!hasLocalize) {
207
+ return {
208
+ code: templateCode,
209
+ errors: [],
210
+ warnings: [],
211
+ };
212
+ }
213
+ const { output, messages } = await this.#workerPool.run({ code: templateCode, filename: templateId, locale, translation }, { name: 'inlineCode' });
214
+ const errors = [];
215
+ const warnings = [];
216
+ for (const message of messages) {
217
+ if (message.type === 'error') {
218
+ errors.push(message.message);
219
+ }
220
+ else {
221
+ warnings.push(message.message);
222
+ }
223
+ }
224
+ return {
225
+ code: output,
226
+ errors,
227
+ warnings,
228
+ };
229
+ }
204
230
  /**
205
231
  * Stops all active transformation tasks and shuts down all workers.
206
232
  * @returns A void promise that resolves when closing is complete.
@@ -315,7 +315,7 @@ function transformSupportedBrowsersToTargets(supportedBrowsers) {
315
315
  }
316
316
  return transformed;
317
317
  }
318
- const SUPPORTED_NODE_VERSIONS = '^20.11.1 || >=22.11.0';
318
+ const SUPPORTED_NODE_VERSIONS = '^20.11.1 || ^22.11.0 || >=24.0.0';
319
319
  /**
320
320
  * Transform supported Node.js versions to esbuild target.
321
321
  * @see https://esbuild.github.io/api/#target
@@ -93,7 +93,7 @@ async function loadProxyConfiguration(root, proxyConfig) {
93
93
  }
94
94
  catch (e) {
95
95
  (0, error_1.assertIsError)(e);
96
- if (e.code === 'ERR_REQUIRE_ESM') {
96
+ if (e.code === 'ERR_REQUIRE_ESM' || e.code === 'ERR_REQUIRE_ASYNC_MODULE') {
97
97
  // Load the ESM configuration file using the TypeScript dynamic import workaround.
98
98
  // Once TypeScript provides support for keeping the dynamic import this workaround can be
99
99
  // changed to a direct dynamic import.
@@ -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 = '20.0.0-next.9';
13
+ const VERSION = '20.0.0-rc.1';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -17,7 +17,7 @@ class WorkerPool extends piscina_1.Piscina {
17
17
  idleTimeout: 1000,
18
18
  // Web containers do not support transferable objects with receiveOnMessagePort which
19
19
  // is used when the Atomics based wait loop is enable.
20
- useAtomics: !process.versions.webcontainer,
20
+ atomics: process.versions.webcontainer ? 'disabled' : 'sync',
21
21
  recordTiming: false,
22
22
  ...options,
23
23
  };