Skip to content

Commit b424645

Browse files
authored
Merge pull request webpack#6542 from mc-zone/feature/module-build-error-with-loader-name
Add loader name to error message. Resolves webpack#2878
2 parents 23beeab + 7765901 commit b424645

18 files changed

+303
-21
lines changed

lib/ModuleBuildError.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ const WebpackError = require("./WebpackError");
88
const { cutOffLoaderExecution } = require("./ErrorHelpers");
99

1010
class ModuleBuildError extends WebpackError {
11-
constructor(module, err) {
12-
let message = "Module build failed: ";
11+
constructor(module, err, { from = null } = {}) {
12+
let message = "Module build failed";
1313
let details = undefined;
14+
if (from) {
15+
message += ` (from ${from}):\n`;
16+
} else {
17+
message += ": ";
18+
}
1419
if (err !== null && typeof err === "object") {
1520
if (typeof err.stack === "string" && err.stack) {
16-
var stack = cutOffLoaderExecution(err.stack);
21+
const stack = cutOffLoaderExecution(err.stack);
1722
if (!err.hideStack) {
1823
message += stack;
1924
} else {
@@ -29,6 +34,8 @@ class ModuleBuildError extends WebpackError {
2934
} else {
3035
message += err;
3136
}
37+
} else {
38+
message = err;
3239
}
3340

3441
super(message);

lib/ModuleError.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@ const WebpackError = require("./WebpackError");
88
const { cleanUp } = require("./ErrorHelpers");
99

1010
class ModuleError extends WebpackError {
11-
constructor(module, err) {
12-
super(err && typeof err === "object" && err.message ? err.message : err);
13-
11+
constructor(module, err, { from = null } = {}) {
12+
let message = "Module Error";
13+
if (from) {
14+
message += ` (from ${from}):\n`;
15+
} else {
16+
message += ": ";
17+
}
18+
if (err && typeof err === "object" && err.message) {
19+
message += err.message;
20+
} else if (err) {
21+
message += err;
22+
}
23+
super(message);
1424
this.name = "ModuleError";
1525
this.module = module;
1626
this.error = err;

lib/ModuleWarning.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ const WebpackError = require("./WebpackError");
88
const { cleanUp } = require("./ErrorHelpers");
99

1010
class ModuleWarning extends WebpackError {
11-
constructor(module, warning) {
12-
super(
13-
warning && typeof warning === "object" && warning.message
14-
? warning.message
15-
: warning
16-
);
17-
11+
constructor(module, warning, { from = null } = {}) {
12+
let message = "Module Warning";
13+
if (from) {
14+
message += ` (from ${from}):\n`;
15+
} else {
16+
message += ": ";
17+
}
18+
if (warning && typeof warning === "object" && warning.message) {
19+
message += warning.message;
20+
} else if (warning) {
21+
message += warning;
22+
}
23+
super(message);
1824
this.name = "ModuleWarning";
1925
this.module = module;
2026
this.warning = warning;

lib/NormalModule.js

+47-6
Original file line numberDiff line numberDiff line change
@@ -166,19 +166,30 @@ class NormalModule extends Module {
166166
}
167167

168168
createLoaderContext(resolver, options, compilation, fs) {
169+
const requestShortener = compilation.runtimeTemplate.requestShortener;
169170
const loaderContext = {
170171
version: 2,
171172
emitWarning: warning => {
172173
if (!(warning instanceof Error)) {
173174
warning = new NonErrorEmittedError(warning);
174175
}
175-
this.warnings.push(new ModuleWarning(this, warning));
176+
const currentLoader = this.getCurrentLoader(loaderContext);
177+
this.warnings.push(
178+
new ModuleWarning(this, warning, {
179+
from: requestShortener.shorten(currentLoader.loader)
180+
})
181+
);
176182
},
177183
emitError: error => {
178184
if (!(error instanceof Error)) {
179185
error = new NonErrorEmittedError(error);
180186
}
181-
this.errors.push(new ModuleError(this, error));
187+
const currentLoader = this.getCurrentLoader(loaderContext);
188+
this.errors.push(
189+
new ModuleError(this, error, {
190+
from: requestShortener.shorten(currentLoader.loader)
191+
})
192+
);
182193
},
183194
exec: (code, filename) => {
184195
// @ts-ignore Argument of type 'this' is not assignable to parameter of type 'Module'.
@@ -219,6 +230,19 @@ class NormalModule extends Module {
219230
return loaderContext;
220231
}
221232

233+
getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) {
234+
if (
235+
this.loaders &&
236+
this.loaders.length &&
237+
index < this.loaders.length &&
238+
index >= 0 &&
239+
this.loaders[index]
240+
) {
241+
return this.loaders[index];
242+
}
243+
return null;
244+
}
245+
222246
createSource(source, resourceBuffer, sourceMap) {
223247
// if there is no identifier return raw source
224248
if (!this.identifier) {
@@ -272,7 +296,17 @@ class NormalModule extends Module {
272296
}
273297

274298
if (err) {
275-
const error = new ModuleBuildError(this, err);
299+
if (!(err instanceof Error)) {
300+
err = new NonErrorEmittedError(err);
301+
}
302+
const currentLoader = this.getCurrentLoader(loaderContext);
303+
const error = new ModuleBuildError(this, err, {
304+
from:
305+
currentLoader &&
306+
compilation.runtimeTemplate.requestShortener.shorten(
307+
currentLoader.loader
308+
)
309+
});
276310
return callback(error);
277311
}
278312

@@ -282,10 +316,17 @@ class NormalModule extends Module {
282316
const extraInfo = result.result.length >= 2 ? result.result[2] : null;
283317

284318
if (!Buffer.isBuffer(source) && typeof source !== "string") {
285-
const error = new ModuleBuildError(
286-
this,
287-
new Error("Final loader didn't return a Buffer or String")
319+
const currentLoader = this.getCurrentLoader(loaderContext, 0);
320+
const err = new Error(
321+
`Final loader (${
322+
currentLoader
323+
? compilation.runtimeTemplate.requestShortener.shorten(
324+
currentLoader.loader
325+
)
326+
: "unknown"
327+
}) didn't return a Buffer or String`
288328
);
329+
const error = new ModuleBuildError(this, err);
289330
return callback(error);
290331
}
291332

test/Errors.test.js

+167
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ describe("Errors", () => {
4343
callback(stats.errors, stats.warnings);
4444
});
4545
}
46+
47+
function getErrorsPromise(options, callback) {
48+
return new Promise((resolve, reject) => {
49+
getErrors(options, (errors, warnings) => {
50+
callback(errors, warnings);
51+
resolve();
52+
});
53+
});
54+
}
4655
it("should throw an error if file doesn't exist", done => {
4756
getErrors(
4857
{
@@ -190,4 +199,162 @@ describe("Errors", () => {
190199
}
191200
);
192201
});
202+
it("should show loader name when emit/throw errors or warnings from loaders", () => {
203+
return Promise.all([
204+
getErrorsPromise(
205+
{
206+
mode: "development",
207+
entry: "./entry-point-error-loader-required.js"
208+
},
209+
(errors, warnings) => {
210+
expect(warnings).toHaveLength(1);
211+
expect(warnings[0].split("\n")[1]).toMatch(
212+
/^Module Warning \(from .\/emit-error-loader.js\):$/
213+
);
214+
expect(errors).toHaveLength(1);
215+
expect(errors[0].split("\n")[1]).toMatch(
216+
/^Module Error \(from .\/emit-error-loader.js\):$/
217+
);
218+
}
219+
),
220+
getErrorsPromise(
221+
{
222+
mode: "development",
223+
entry: path.resolve(base, "./emit-error-loader") + "!./entry-point.js"
224+
},
225+
(errors, warnings) => {
226+
expect(warnings).toHaveLength(1);
227+
expect(warnings[0].split("\n")[1]).toMatch(
228+
/^Module Warning \(from .\/emit-error-loader.js\):$/
229+
);
230+
expect(errors).toHaveLength(1);
231+
expect(errors[0].split("\n")[1]).toMatch(
232+
/^Module Error \(from .\/emit-error-loader.js\):$/
233+
);
234+
}
235+
),
236+
getErrorsPromise(
237+
{
238+
mode: "development",
239+
entry: "./not-a-json.js",
240+
module: {
241+
rules: [
242+
{
243+
test: /not-a-json\.js$/,
244+
use: [
245+
"json-loader",
246+
{
247+
loader: path.resolve(base, "./emit-error-loader")
248+
}
249+
]
250+
}
251+
]
252+
}
253+
},
254+
(errors, warnings) => {
255+
expect(warnings).toHaveLength(1);
256+
expect(warnings[0].split("\n")[1]).toMatch(
257+
/^Module Warning \(from .\/emit-error-loader.js\):$/
258+
);
259+
expect(errors).toHaveLength(2);
260+
expect(errors[0].split("\n")[1]).toMatch(
261+
/^Module Error \(from .\/emit-error-loader.js\):$/
262+
);
263+
expect(errors[1].split("\n")[1]).toMatch(
264+
/^Module build failed \(from \(webpack\)\/node_modules\/json-loader\/index.js\):$/
265+
);
266+
}
267+
),
268+
getErrorsPromise(
269+
{
270+
mode: "development",
271+
entry: "./entry-point.js",
272+
module: {
273+
rules: [
274+
{
275+
test: /entry-point\.js$/,
276+
use: path.resolve(base, "./async-error-loader")
277+
}
278+
]
279+
}
280+
},
281+
(errors, warnings) => {
282+
expect(errors).toHaveLength(1);
283+
expect(errors[0].split("\n")[1]).toMatch(
284+
/^Module build failed \(from .\/async-error-loader.js\):$/
285+
);
286+
}
287+
),
288+
getErrorsPromise(
289+
{
290+
mode: "development",
291+
entry: "./entry-point.js",
292+
module: {
293+
rules: [
294+
{
295+
test: /entry-point\.js$/,
296+
use: path.resolve(base, "./throw-error-loader")
297+
}
298+
]
299+
}
300+
},
301+
(errors, warnings) => {
302+
expect(errors).toHaveLength(1);
303+
expect(errors[0].split("\n")[1]).toMatch(
304+
/^Module build failed \(from .\/throw-error-loader.js\):$/
305+
);
306+
}
307+
),
308+
getErrorsPromise(
309+
{
310+
mode: "development",
311+
entry: "./entry-point.js",
312+
module: {
313+
rules: [
314+
{
315+
test: /entry-point\.js$/,
316+
use: path.resolve(base, "./irregular-error-loader")
317+
}
318+
]
319+
}
320+
},
321+
(errors, warnings) => {
322+
expect(warnings).toHaveLength(2);
323+
expect(warnings[0].split("\n")[1]).toMatch(
324+
/^Module Warning \(from .\/irregular-error-loader.js\):$/
325+
);
326+
expect(warnings[1].split("\n")[1]).toMatch(
327+
/^Module Warning \(from .\/irregular-error-loader.js\):$/
328+
);
329+
330+
expect(errors).toHaveLength(3);
331+
expect(errors[0].split("\n")[1]).toMatch(
332+
/^Module Error \(from .\/irregular-error-loader.js\):$/
333+
);
334+
expect(errors[1].split("\n")[1]).toMatch(
335+
/^Module Error \(from .\/irregular-error-loader.js\):$/
336+
);
337+
expect(errors[2].split("\n")[1]).toMatch(
338+
/^Module build failed \(from .\/irregular-error-loader.js\):$/
339+
);
340+
}
341+
)
342+
]);
343+
});
344+
it("should throw a build error if no source be returned after run loaders", done => {
345+
getErrors(
346+
{
347+
mode: "development",
348+
entry: path.resolve(base, "./no-return-loader") + "!./entry-point.js"
349+
},
350+
(errors, warnings) => {
351+
expect(errors).toHaveLength(1);
352+
const messages = errors[0].split("\n");
353+
expect(messages[1]).toMatch(
354+
/^Module build failed: Error: Final loader \(.+\) didn't return a Buffer or String/
355+
);
356+
done();
357+
}
358+
);
359+
});
193360
});
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module.exports = [
2-
[/Module build failed: Message\nStack/]
3-
];
2+
[/Module build failed( \(from [^)]+\))?:\nMessage\nStack/]
3+
];
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = [
2+
[
3+
/\.\/loaders\/no-string\/file\.js \(\.\/loaders\/no-string\/loader\.js!\.\/loaders\/no-string\/file\.js\)/,
4+
/Module build failed: Error: Final loader \(\.\/loaders\/no-string\/loader\.js\) didn't return a Buffer or String/
5+
],
6+
[
7+
/\.\/loaders\/no-string\/file\.js \(\.\/loaders\/no-string\/loader\.js!\.\/loaders\/no-string\/pitch-loader\.js!\.\/loaders\/no-string\/file\.js\)/,
8+
/Module build failed: Error: Final loader \(\.\/loaders\/no-string\/loader\.js\) didn't return a Buffer or String/
9+
]
10+
];

test/cases/loaders/no-string/file.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
123

test/cases/loaders/no-string/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
it("should emit the correct error for loaders not returning buffer or string", function() {
2+
expect(() => require("./loader.js!./file.js")).toThrowError(
3+
/Module build failed/
4+
);
5+
expect(() => require("./loader.js!./pitch-loader.js!./file.js")).toThrowError(
6+
/Module build failed/
7+
);
8+
});
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = () => {
2+
return 123;
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = () => {
2+
return 123;
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = function(source) {
2+
const callback = this.async();
3+
const error = new Error("this is a callback error");
4+
callback(error, source);
5+
};

0 commit comments

Comments
 (0)