@angular-devkit/build-angular 19.0.0-rc.0 → 19.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,18 +1,18 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "19.0.0-rc.0",
3
+ "version": "19.0.0-rc.1",
4
4
  "description": "Angular Webpack Build Facade",
5
5
  "main": "src/index.js",
6
6
  "typings": "src/index.d.ts",
7
7
  "builders": "builders.json",
8
8
  "dependencies": {
9
9
  "@ampproject/remapping": "2.3.0",
10
- "@angular-devkit/architect": "0.1900.0-rc.0",
11
- "@angular-devkit/build-webpack": "0.1900.0-rc.0",
12
- "@angular-devkit/core": "19.0.0-rc.0",
13
- "@angular/build": "19.0.0-rc.0",
10
+ "@angular-devkit/architect": "0.1900.0-rc.1",
11
+ "@angular-devkit/build-webpack": "0.1900.0-rc.1",
12
+ "@angular-devkit/core": "19.0.0-rc.1",
13
+ "@angular/build": "19.0.0-rc.1",
14
14
  "@babel/core": "7.26.0",
15
- "@babel/generator": "7.26.0",
15
+ "@babel/generator": "7.26.2",
16
16
  "@babel/helper-annotate-as-pure": "7.25.9",
17
17
  "@babel/helper-split-export-declaration": "7.24.7",
18
18
  "@babel/plugin-transform-async-generator-functions": "7.25.9",
@@ -21,7 +21,7 @@
21
21
  "@babel/preset-env": "7.26.0",
22
22
  "@babel/runtime": "7.26.0",
23
23
  "@discoveryjs/json-ext": "0.6.3",
24
- "@ngtools/webpack": "19.0.0-rc.0",
24
+ "@ngtools/webpack": "19.0.0-rc.1",
25
25
  "@vitejs/plugin-basic-ssl": "1.1.0",
26
26
  "ansi-colors": "4.1.3",
27
27
  "autoprefixer": "10.4.20",
@@ -39,7 +39,7 @@
39
39
  "less-loader": "12.2.0",
40
40
  "license-webpack-plugin": "4.0.2",
41
41
  "loader-utils": "3.3.1",
42
- "mini-css-extract-plugin": "2.9.1",
42
+ "mini-css-extract-plugin": "2.9.2",
43
43
  "open": "10.1.0",
44
44
  "ora": "5.4.1",
45
45
  "picomatch": "4.0.2",
@@ -48,15 +48,15 @@
48
48
  "postcss-loader": "8.1.1",
49
49
  "resolve-url-loader": "5.0.0",
50
50
  "rxjs": "7.8.1",
51
- "sass": "1.80.5",
52
- "sass-loader": "16.0.2",
51
+ "sass": "1.80.6",
52
+ "sass-loader": "16.0.3",
53
53
  "semver": "7.6.3",
54
54
  "source-map-loader": "5.0.0",
55
55
  "source-map-support": "0.5.21",
56
56
  "terser": "5.36.0",
57
57
  "tree-kill": "1.2.2",
58
- "tslib": "2.8.0",
59
- "webpack": "5.95.0",
58
+ "tslib": "2.8.1",
59
+ "webpack": "5.96.1",
60
60
  "webpack-dev-middleware": "7.4.2",
61
61
  "webpack-dev-server": "5.1.0",
62
62
  "webpack-merge": "6.0.1",
@@ -70,7 +70,7 @@
70
70
  "@angular/localize": "^19.0.0-next.0",
71
71
  "@angular/platform-server": "^19.0.0-next.0",
72
72
  "@angular/service-worker": "^19.0.0-next.0",
73
- "@angular/ssr": "^19.0.0-rc.0",
73
+ "@angular/ssr": "^19.0.0-rc.1",
74
74
  "@web/test-runner": "^0.19.0",
75
75
  "browser-sync": "^3.0.2",
76
76
  "jest": "^29.5.0",
@@ -72,6 +72,8 @@ function execute(options, context, transforms = {}, extensions) {
72
72
  if (options.disableHostCheck) {
73
73
  context.logger.warn(`The "disableHostCheck" option will not be used because it is not supported by the "${builderName}" builder.`);
74
74
  }
75
+ // New build system defaults hmr option to the value of liveReload
76
+ normalizedOptions.hmr ??= normalizedOptions.liveReload;
75
77
  return (0, rxjs_1.defer)(() => Promise.all([Promise.resolve().then(() => __importStar(require('@angular/build/private'))), Promise.resolve().then(() => __importStar(require('../browser-esbuild')))])).pipe((0, rxjs_1.switchMap)(([{ serveWithVite, buildApplicationInternal }, { convertBrowserOptions }]) => serveWithVite(normalizedOptions, builderName, (options, context, codePlugins) => {
76
78
  return builderName === '@angular-devkit/build-angular:browser-esbuild'
77
79
  ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -91,6 +93,8 @@ function execute(options, context, transforms = {}, extensions) {
91
93
  if (extensions?.middleware?.length) {
92
94
  throw new Error('Only the "application" and "browser-esbuild" builders support middleware.');
93
95
  }
96
+ // Webpack based build systems default to false for hmr option
97
+ normalizedOptions.hmr ??= false;
94
98
  // Use Webpack for all other browser targets
95
99
  return (0, rxjs_1.defer)(() => Promise.resolve().then(() => __importStar(require('./webpack-server')))).pipe((0, rxjs_1.switchMap)(({ serveWebpackBrowser }) => serveWebpackBrowser(normalizedOptions, builderName, context, transforms)));
96
100
  }));
@@ -26,7 +26,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
26
26
  open: boolean | undefined;
27
27
  verbose: boolean | undefined;
28
28
  watch: boolean | undefined;
29
- liveReload: boolean | undefined;
29
+ liveReload: boolean;
30
30
  hmr: boolean | undefined;
31
31
  headers: {
32
32
  [key: string]: string;
@@ -85,7 +85,7 @@ async function normalizeOptions(context, projectName, options) {
85
85
  open,
86
86
  verbose,
87
87
  watch,
88
- liveReload,
88
+ liveReload: !!liveReload,
89
89
  hmr,
90
90
  headers,
91
91
  workspaceRoot,
@@ -84,8 +84,7 @@
84
84
  },
85
85
  "hmr": {
86
86
  "type": "boolean",
87
- "description": "Enable hot module replacement.",
88
- "default": false
87
+ "description": "Enable hot module replacement."
89
88
  },
90
89
  "watch": {
91
90
  "type": "boolean",
@@ -50,26 +50,84 @@ class ApplicationBuildError extends Error {
50
50
  this.name = 'ApplicationBuildError';
51
51
  }
52
52
  }
53
- function injectKarmaReporter(context, buildOptions, karmaConfig, subscriber) {
53
+ const LATEST_BUILD_FILES_TOKEN = 'angularLatestBuildFiles';
54
+ class AngularAssetsMiddleware {
55
+ serveFile;
56
+ latestBuildFiles;
57
+ static $inject = ['serveFile', LATEST_BUILD_FILES_TOKEN];
58
+ static NAME = 'angular-test-assets';
59
+ constructor(serveFile, latestBuildFiles) {
60
+ this.serveFile = serveFile;
61
+ this.latestBuildFiles = latestBuildFiles;
62
+ }
63
+ handle(req, res, next) {
64
+ let err = null;
65
+ try {
66
+ const url = new URL(`http://${req.headers['host']}${req.url}`);
67
+ const file = this.latestBuildFiles.files[url.pathname.slice(1)];
68
+ if (file?.origin === 'disk') {
69
+ this.serveFile(file.inputPath, undefined, res);
70
+ return;
71
+ }
72
+ else if (file?.origin === 'memory') {
73
+ // Include pathname to help with Content-Type headers.
74
+ this.serveFile(`/unused/${url.pathname}`, undefined, res, undefined, file.contents, true);
75
+ return;
76
+ }
77
+ }
78
+ catch (e) {
79
+ err = e;
80
+ }
81
+ next(err);
82
+ }
83
+ static createPlugin(initialFiles) {
84
+ return {
85
+ [LATEST_BUILD_FILES_TOKEN]: ['value', { files: { ...initialFiles.files } }],
86
+ [`middleware:${AngularAssetsMiddleware.NAME}`]: [
87
+ 'factory',
88
+ Object.assign((...args) => {
89
+ const inst = new AngularAssetsMiddleware(...args);
90
+ return inst.handle.bind(inst);
91
+ }, AngularAssetsMiddleware),
92
+ ],
93
+ };
94
+ }
95
+ }
96
+ function injectKarmaReporter(context, buildOptions, buildIterator, karmaConfig, subscriber) {
54
97
  const reporterName = 'angular-progress-notifier';
55
98
  class ProgressNotifierReporter {
56
99
  emitter;
57
- static $inject = ['emitter'];
58
- constructor(emitter) {
100
+ latestBuildFiles;
101
+ static $inject = ['emitter', LATEST_BUILD_FILES_TOKEN];
102
+ constructor(emitter, latestBuildFiles) {
59
103
  this.emitter = emitter;
104
+ this.latestBuildFiles = latestBuildFiles;
60
105
  this.startWatchingBuild();
61
106
  }
62
107
  startWatchingBuild() {
63
108
  void (async () => {
64
- for await (const buildOutput of (0, private_1.buildApplicationInternal)({
65
- ...buildOptions,
66
- watch: true,
67
- }, context)) {
109
+ // This is effectively "for await of but skip what's already consumed".
110
+ let isDone = false; // to mark the loop condition as "not constant".
111
+ while (!isDone) {
112
+ const { done, value: buildOutput } = await buildIterator.next();
113
+ if (done) {
114
+ isDone = true;
115
+ break;
116
+ }
68
117
  if (buildOutput.kind === private_1.ResultKind.Failure) {
69
118
  subscriber.next({ success: false, message: 'Build failed' });
70
119
  }
71
120
  else if (buildOutput.kind === private_1.ResultKind.Incremental ||
72
121
  buildOutput.kind === private_1.ResultKind.Full) {
122
+ if (buildOutput.kind === private_1.ResultKind.Full) {
123
+ this.latestBuildFiles.files = buildOutput.files;
124
+ }
125
+ else {
126
+ this.latestBuildFiles.files = {
127
+ ...this.latestBuildFiles.files,
128
+ ...buildOutput.files,
129
+ };
130
+ }
73
131
  await writeTestFiles(buildOutput.files, buildOptions.outputPath);
74
132
  this.emitter.refreshFiles();
75
133
  }
@@ -96,11 +154,11 @@ function injectKarmaReporter(context, buildOptions, karmaConfig, subscriber) {
96
154
  });
97
155
  }
98
156
  function execute(options, context, karmaOptions, transforms = {}) {
99
- return (0, rxjs_1.from)(initializeApplication(options, context, karmaOptions, transforms)).pipe((0, rxjs_1.switchMap)(([karma, karmaConfig, buildOptions]) => new rxjs_1.Observable((subscriber) => {
157
+ return (0, rxjs_1.from)(initializeApplication(options, context, karmaOptions, transforms)).pipe((0, rxjs_1.switchMap)(([karma, karmaConfig, buildOptions, buildIterator]) => new rxjs_1.Observable((subscriber) => {
100
158
  // If `--watch` is explicitly enabled or if we are keeping the Karma
101
159
  // process running, we should hook Karma into the build.
102
- if (options.watch ?? !karmaConfig.singleRun) {
103
- injectKarmaReporter(context, buildOptions, karmaConfig, subscriber);
160
+ if (buildIterator) {
161
+ injectKarmaReporter(context, buildOptions, buildIterator, karmaConfig, subscriber);
104
162
  }
105
163
  // Complete the observable once the Karma server returns.
106
164
  const karmaServer = new karma.Server(karmaConfig, (exitCode) => {
@@ -138,7 +196,21 @@ function normalizePolyfills(polyfills) {
138
196
  async function collectEntrypoints(options, context, projectSourceRoot) {
139
197
  // Glob for files to test.
140
198
  const testFiles = await (0, find_tests_1.findTests)(options.include ?? [], options.exclude ?? [], context.workspaceRoot, projectSourceRoot);
141
- return new Set(testFiles);
199
+ const seen = new Set();
200
+ return new Map(Array.from(testFiles, (testFile) => {
201
+ const relativePath = path
202
+ .relative(testFile.startsWith(projectSourceRoot) ? projectSourceRoot : context.workspaceRoot, testFile)
203
+ .replace(/^[./]+/, '_')
204
+ .replace(/\//g, '-');
205
+ let uniqueName = `spec-${path.basename(relativePath, path.extname(relativePath))}`;
206
+ let suffix = 2;
207
+ while (seen.has(uniqueName)) {
208
+ uniqueName = `${relativePath}-${suffix}`;
209
+ ++suffix;
210
+ }
211
+ seen.add(uniqueName);
212
+ return [uniqueName, testFile];
213
+ }));
142
214
  }
143
215
  async function initializeApplication(options, context, karmaOptions, transforms = {}) {
144
216
  if (transforms.webpackConfiguration) {
@@ -151,18 +223,18 @@ async function initializeApplication(options, context, karmaOptions, transforms
151
223
  collectEntrypoints(options, context, projectSourceRoot),
152
224
  fs.rm(outputPath, { recursive: true, force: true }),
153
225
  ]);
154
- let mainName = 'init_test_bed';
226
+ const mainName = 'test_main';
155
227
  if (options.main) {
156
- entryPoints.add(options.main);
157
- mainName = path.basename(options.main, path.extname(options.main));
228
+ entryPoints.set(mainName, options.main);
158
229
  }
159
230
  else {
160
- entryPoints.add('@angular-devkit/build-angular/src/builders/karma/init_test_bed.js');
231
+ entryPoints.set(mainName, '@angular-devkit/build-angular/src/builders/karma/init_test_bed.js');
161
232
  }
162
233
  const instrumentForCoverage = options.codeCoverage
163
234
  ? createInstrumentationFilter(projectSourceRoot, getInstrumentationExcludedPaths(context.workspaceRoot, options.codeCoverageExclude ?? []))
164
235
  : undefined;
165
236
  const buildOptions = {
237
+ assets: options.assets,
166
238
  entryPoints,
167
239
  tsConfig: options.tsConfig,
168
240
  outputPath,
@@ -179,9 +251,10 @@ async function initializeApplication(options, context, karmaOptions, transforms
179
251
  styles: options.styles,
180
252
  polyfills: normalizePolyfills(options.polyfills),
181
253
  webWorkerTsConfig: options.webWorkerTsConfig,
254
+ watch: options.watch ?? !karmaOptions.singleRun,
182
255
  };
183
256
  // Build tests with `application` builder, using test files as entry points.
184
- const buildOutput = await first((0, private_1.buildApplicationInternal)(buildOptions, context));
257
+ const [buildOutput, buildIterator] = await first((0, private_1.buildApplicationInternal)(buildOptions, context), { cancel: !buildOptions.watch });
185
258
  if (buildOutput.kind === private_1.ResultKind.Failure) {
186
259
  throw new ApplicationBuildError('Build failed');
187
260
  }
@@ -193,46 +266,55 @@ async function initializeApplication(options, context, karmaOptions, transforms
193
266
  karmaOptions.files ??= [];
194
267
  karmaOptions.files.push(
195
268
  // Serve polyfills first.
196
- { pattern: `${outputPath}/polyfills.js`, type: 'module' },
269
+ { pattern: `${outputPath}/polyfills.js`, type: 'module', watched: false },
197
270
  // Serve global setup script.
198
- { pattern: `${outputPath}/${mainName}.js`, type: 'module' },
271
+ { pattern: `${outputPath}/${mainName}.js`, type: 'module', watched: false },
199
272
  // Serve all source maps.
200
- { pattern: `${outputPath}/*.map`, included: false });
273
+ { pattern: `${outputPath}/*.map`, included: false, watched: false },
274
+ // These are the test entrypoints.
275
+ { pattern: `${outputPath}/spec-*.js`, type: 'module', watched: false });
201
276
  if (hasChunkOrWorkerFiles(buildOutput.files)) {
202
277
  karmaOptions.files.push(
203
278
  // Allow loading of chunk-* files but don't include them all on load.
204
- { pattern: `${outputPath}/{chunk,worker}-*.js`, type: 'module', included: false });
279
+ {
280
+ pattern: `${outputPath}/{chunk,worker}-*.js`,
281
+ type: 'module',
282
+ included: false,
283
+ watched: false,
284
+ });
205
285
  }
206
- karmaOptions.files.push(
207
- // Serve remaining JS on page load, these are the test entrypoints.
208
- { pattern: `${outputPath}/*.js`, type: 'module' });
209
286
  if (options.styles?.length) {
210
287
  // Serve CSS outputs on page load, these are the global styles.
211
- karmaOptions.files.push({ pattern: `${outputPath}/*.css`, type: 'css' });
288
+ karmaOptions.files.push({ pattern: `${outputPath}/*.css`, type: 'css', watched: false });
212
289
  }
213
290
  const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
214
291
  // Remove the webpack plugin/framework:
215
292
  // Alternative would be to make the Karma plugin "smart" but that's a tall order
216
293
  // with managing unneeded imports etc..
217
- const pluginLengthBefore = (parsedKarmaConfig.plugins ?? []).length;
218
- parsedKarmaConfig.plugins = (parsedKarmaConfig.plugins ?? []).filter((plugin) => {
294
+ parsedKarmaConfig.plugins ??= [];
295
+ const pluginLengthBefore = parsedKarmaConfig.plugins.length;
296
+ parsedKarmaConfig.plugins = parsedKarmaConfig.plugins.filter((plugin) => {
219
297
  if (typeof plugin === 'string') {
220
298
  return plugin !== 'framework:@angular-devkit/build-angular';
221
299
  }
222
300
  return !plugin['framework:@angular-devkit/build-angular'];
223
301
  });
224
- parsedKarmaConfig.frameworks = parsedKarmaConfig.frameworks?.filter((framework) => framework !== '@angular-devkit/build-angular');
225
- const pluginLengthAfter = (parsedKarmaConfig.plugins ?? []).length;
302
+ parsedKarmaConfig.frameworks ??= [];
303
+ parsedKarmaConfig.frameworks = parsedKarmaConfig.frameworks.filter((framework) => framework !== '@angular-devkit/build-angular');
304
+ const pluginLengthAfter = parsedKarmaConfig.plugins.length;
226
305
  if (pluginLengthBefore !== pluginLengthAfter) {
227
306
  context.logger.warn(`Ignoring framework "@angular-devkit/build-angular" from karma config file because it's not compatible with the application builder.`);
228
307
  }
308
+ parsedKarmaConfig.plugins.push(AngularAssetsMiddleware.createPlugin(buildOutput));
309
+ parsedKarmaConfig.middleware ??= [];
310
+ parsedKarmaConfig.middleware.push(AngularAssetsMiddleware.NAME);
229
311
  // When using code-coverage, auto-add karma-coverage.
230
312
  // This was done as part of the karma plugin for webpack.
231
313
  if (options.codeCoverage &&
232
314
  !parsedKarmaConfig.reporters?.some((r) => r === 'coverage' || r === 'coverage-istanbul')) {
233
315
  parsedKarmaConfig.reporters = (parsedKarmaConfig.reporters ?? []).concat(['coverage']);
234
316
  }
235
- return [karma, parsedKarmaConfig, buildOptions];
317
+ return [karma, parsedKarmaConfig, buildOptions, buildIterator];
236
318
  }
237
319
  function hasChunkOrWorkerFiles(files) {
238
320
  return Object.keys(files).some((filename) => {
@@ -264,9 +346,17 @@ async function writeTestFiles(files, testDir) {
264
346
  });
265
347
  }
266
348
  /** Returns the first item yielded by the given generator and cancels the execution. */
267
- async function first(generator) {
349
+ async function first(generator, { cancel }) {
350
+ if (!cancel) {
351
+ const iterator = generator[Symbol.asyncIterator]();
352
+ const firstValue = await iterator.next();
353
+ if (firstValue.done) {
354
+ throw new Error('Expected generator to emit at least once.');
355
+ }
356
+ return [firstValue.value, iterator];
357
+ }
268
358
  for await (const value of generator) {
269
- return value;
359
+ return [value, null];
270
360
  }
271
361
  throw new Error('Expected generator to emit at least once.');
272
362
  }
@@ -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.0-rc.0';
13
+ const VERSION = '19.0.0-rc.1';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&