Skip to content

Commit c53a178

Browse files
alan-agius4filipesilva
authored andcommitted
fix(@angular-devkit/build-angular): use new Webpack watch API in karma webpack plugin
In Webpack 5, the Webpack callback must be used when Webpack is running in watch mode. Related warning ``` .(node:6565) [DEP_WEBPACK_WATCH_WITHOUT_CALLBACK] DeprecationWarning: A 'callback' argument need to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback. ``` (cherry picked from commit 04f5dfe)
1 parent bf0b3d5 commit c53a178

File tree

2 files changed

+49
-83
lines changed

2 files changed

+49
-83
lines changed

packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma-webpack-failure-cb.ts

-24
This file was deleted.

packages/angular_devkit/build_angular/src/webpack/plugins/karma/karma.ts

+49-59
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,13 @@ import * as glob from 'glob';
1313
import * as webpack from 'webpack';
1414
const webpackDevMiddleware = require('webpack-dev-middleware');
1515

16-
import { KarmaWebpackFailureCb } from './karma-webpack-failure-cb';
1716
import { statsErrorsToString } from '../../utils/stats';
18-
import { getWebpackStatsConfig } from '../../configs/stats';
1917
import { createConsoleLogger } from '@angular-devkit/core/node';
2018
import { logging } from '@angular-devkit/core';
2119
import { WebpackTestOptions } from '../../../utils/build-options';
2220
import { normalizeSourceMaps } from '../../../utils/index';
2321

24-
/**
25-
* Enumerate needed (but not require/imported) dependencies from this file
26-
* to let the dependency validator know they are used.
27-
*
28-
* require('source-map-support')
29-
* require('karma-source-map-support')
30-
*/
31-
32-
const KARMA_APPLICATION_PATH = '_karma_webpack_';
22+
const KARMA_APPLICATION_PATH = '_karma_webpack_';
3323

3424

3525
let blocked: any[] = [];
@@ -64,7 +54,7 @@ function addKarmaFiles(files: any[], newFiles: any[], prepend = false) {
6454
const init: any = (config: any, emitter: any) => {
6555
if (!config.buildWebpack) {
6656
throw new Error(`The '@angular-devkit/build-angular/plugins/karma' karma plugin is meant to` +
67-
` be used from within Angular CLI and will not work correctly outside of it.`
57+
` be used from within Angular CLI and will not work correctly outside of it.`
6858
)
6959
}
7060
const options = config.buildWebpack.options as WebpackTestOptions;
@@ -94,7 +84,7 @@ const init: any = (config: any, emitter: any) => {
9484
if (options.codeCoverage) {
9585
config.plugins = config.plugins || [];
9686
config.reporters = config.reporters || [];
97-
const {plugins, reporters} = config;
87+
const { plugins, reporters } = config;
9888
const hasCoveragePlugin = plugins.some(isPlugin('karma-coverage', 'reporter:coverage'));
9989
const hasIstanbulPlugin = plugins.some(isPlugin('karma-coverage-istanbul-reporter', 'reporter:coverage-istanbul'));
10090
const hasCoverageReporter = reporters.includes('coverage');
@@ -110,8 +100,8 @@ const init: any = (config: any, emitter: any) => {
110100

111101
if (hasIstanbulPlugin) {
112102
logger.warn(`'karma-coverage-istanbul-reporter' usage has been deprecated since version 11.\n` +
113-
`Please install 'karma-coverage' and update 'karma.conf.js.' ` +
114-
'For more info, see https://github.com/karma-runner/karma-coverage/blob/master/README.md');
103+
`Please install 'karma-coverage' and update 'karma.conf.js.' ` +
104+
'For more info, see https://github.com/karma-runner/karma-coverage/blob/master/README.md');
115105
}
116106
}
117107

@@ -123,21 +113,9 @@ const init: any = (config: any, emitter: any) => {
123113
publicPath: `/${KARMA_APPLICATION_PATH}/`,
124114
};
125115

126-
const compilationErrorCb = (error: string | undefined, errors: string[]) => {
127-
// Notify potential listeners of the compile error
128-
emitter.emit('compile_error', errors);
129-
130-
// Finish Karma run early in case of compilation error.
131-
emitter.emit('run_complete', [], { exitCode: 1 });
132-
133-
// Unblock any karma requests (potentially started using `karma run`)
134-
unblock();
135-
}
136-
webpackConfig.plugins.push(new KarmaWebpackFailureCb(compilationErrorCb));
137-
138116
// Use existing config if any.
139-
config.webpack = Object.assign(webpackConfig, config.webpack);
140-
config.webpackMiddleware = Object.assign(webpackMiddlewareConfig, config.webpackMiddleware);
117+
config.webpack = { ...webpackConfig, ...config.webpack };
118+
config.webpackMiddleware = { ...webpackMiddlewareConfig, ...config.webpackMiddleware }
141119

142120
// Our custom context and debug files list the webpack bundles directly instead of using
143121
// the karma files array.
@@ -151,7 +129,11 @@ const init: any = (config: any, emitter: any) => {
151129
config.middleware.push('@angular-devkit/build-angular--fallback');
152130

153131
// The webpack tier owns the watch behavior so we want to force it in the config.
154-
webpackConfig.watch = !config.singleRun;
132+
// When not in watch mode, webpack-dev-middleware will call `compiler.watch` anyway.
133+
// https://github.com/webpack/webpack-dev-middleware/blob/698c9ae5e9bb9a013985add6189ff21c1a1ec185/src/index.js#L65
134+
// https://github.com/webpack/webpack/blob/cde1b73e12eb8a77eb9ba42e7920c9ec5d29c2c9/lib/Compiler.js#L379-L388
135+
webpackConfig.watch = true;
136+
155137
if (config.singleRun) {
156138
// There's no option to turn off file watching in webpack-dev-server, but
157139
// we can override the file watcher instead.
@@ -167,46 +149,54 @@ const init: any = (config: any, emitter: any) => {
167149
webpackConfig.output.path = `/${KARMA_APPLICATION_PATH}/`;
168150
webpackConfig.output.publicPath = `/${KARMA_APPLICATION_PATH}/`;
169151

170-
let compiler;
171-
try {
172-
compiler = webpack(webpackConfig);
173-
} catch (e) {
174-
logger.error(e.stack || e)
175-
if (e.details) {
176-
logger.error(e.details)
152+
const compiler = webpack(webpackConfig, (error, stats) => {
153+
if (error) {
154+
throw error;
177155
}
178-
throw e;
179-
}
180156

181-
function handler(callback?: () => void) {
182-
isBlocked = true;
157+
if (stats?.hasErrors()) {
158+
// Only generate needed JSON stats and when needed.
159+
const statsJson = stats?.toJson({
160+
all: false,
161+
children: true,
162+
errors: true,
163+
warnings: true,
164+
});
183165

184-
if (typeof callback === 'function') {
185-
callback();
166+
logger.error(statsErrorsToString(statsJson, { colors: true }));
167+
168+
// Notify potential listeners of the compile error.
169+
emitter.emit('compile_error', {
170+
errors: statsJson.errors?.map(e => e.message),
171+
});
172+
173+
// Finish Karma run early in case of compilation error.
174+
emitter.emit('run_complete', [], { exitCode: 1 });
175+
176+
// Emit a failure build event if there are compilation errors.
177+
failureCb();
186178
}
179+
});
180+
181+
function handler(callback?: () => void): void {
182+
isBlocked = true;
183+
callback?.();
187184
}
188185

189186
compiler.hooks.invalid.tap('karma', () => handler());
190-
191187
compiler.hooks.watchRun.tapAsync('karma', (_: any, callback: () => void) => handler(callback));
192-
193188
compiler.hooks.run.tapAsync('karma', (_: any, callback: () => void) => handler(callback));
194189

195-
function unblock(){
190+
function unblock() {
196191
isBlocked = false;
197192
blocked.forEach((cb) => cb());
198193
blocked = [];
199194
}
200195

201196
let lastCompilationHash: string | undefined;
202-
const statsConfig = getWebpackStatsConfig();
203197
compiler.hooks.done.tap('karma', (stats) => {
204198
if (stats.hasErrors()) {
205-
// Print compilation errors.
206-
logger.error(statsErrorsToString(stats.compilation, statsConfig));
207199
lastCompilationHash = undefined;
208-
// Emit a failure build event if there are compilation errors.
209-
failureCb();
210200
} else if (stats.hash != lastCompilationHash) {
211201
// Refresh karma only when there are no webpack errors, and if the compilation changed.
212202
lastCompilationHash = stats.hash;
@@ -215,7 +205,7 @@ const init: any = (config: any, emitter: any) => {
215205
unblock();
216206
});
217207

218-
webpackMiddleware = new webpackDevMiddleware(compiler, webpackMiddlewareConfig);
208+
webpackMiddleware = webpackDevMiddleware(compiler, webpackMiddlewareConfig);
219209

220210
emitter.on('exit', (done: any) => {
221211
webpackMiddleware.close();
@@ -244,12 +234,12 @@ function requestBlocker() {
244234
// browser log, because it is an utility reporter,
245235
// unless it's alone in the "reporters" option and base reporter is used.
246236
function muteDuplicateReporterLogging(context: any, config: any) {
247-
context.writeCommonMsg = function () { };
237+
context.writeCommonMsg = () => { }
248238
const reporterName = '@angular/cli';
249239
const hasTrailingReporters = config.reporters.slice(-1).pop() !== reporterName;
250240

251241
if (hasTrailingReporters) {
252-
context.writeCommonMsg = function () { };
242+
context.writeCommonMsg = () => { };
253243
}
254244
}
255245

@@ -268,7 +258,7 @@ const eventReporter: any = function (this: any, baseReporterDecorator: any, conf
268258
}
269259

270260
// avoid duplicate failure message
271-
this.specFailure = () => {};
261+
this.specFailure = () => { };
272262
};
273263

274264
eventReporter.$inject = ['baseReporterDecorator', 'config'];
@@ -287,10 +277,10 @@ const sourceMapReporter: any = function (this: any, baseReporterDecorator: any,
287277
};
288278

289279
// avoid duplicate complete message
290-
this.onRunComplete = () => {};
280+
this.onRunComplete = () => { };
291281

292282
// avoid duplicate failure message
293-
this.specFailure = () => {};
283+
this.specFailure = () => { };
294284
};
295285

296286
sourceMapReporter.$inject = ['baseReporterDecorator', 'config'];
@@ -332,7 +322,7 @@ function fallbackMiddleware() {
332322
* @param pluginName name of the karma plugin (e.g. reporter:coverage)
333323
*/
334324
function isPlugin(moduleId: string, pluginName: string) {
335-
return (plugin: string|{}): boolean => {
325+
return (plugin: string | {}): boolean => {
336326
if (typeof plugin === 'string') {
337327
if (!plugin.includes('*')) {
338328
return plugin === moduleId;
@@ -342,7 +332,7 @@ function isPlugin(moduleId: string, pluginName: string) {
342332
try {
343333
require.resolve(moduleId);
344334
return true;
345-
} catch {}
335+
} catch { }
346336
}
347337
return false;
348338
}

0 commit comments

Comments
 (0)