Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): hide stacktraces from webpack errors
Browse files Browse the repository at this point in the history
In many cases Webpack will output errors with stacktraces even when `errorStack` is configured to false which bloats the terminal and makes the actual error harder to find.

With this change we output stacktraces only when using the `verbose` option.

Before
```
$ ng build

./src/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[2]!./node_modules/resolve-url-loader/index.js??ruleSet[1].rules[5].rules[1].use[0]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[5].rules[1].use[1]!./src/styles.scss - Error: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Can't find stylesheet to import.
  ╷
1 │ @import "invalid";
  │         ^^^^^^^^^
  ╵
  src/styles.scss 1:9  root stylesheet

./src/styles.scss - Error: Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
HookWebpackError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Can't find stylesheet to import.
  ╷
1 │ @import "invalid";
  │         ^^^^^^^^^
  ╵
  src/styles.scss 1:9  root stylesheet
    at tryRunOrWebpackError (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/HookWebpackError.js:88:9)
    at __webpack_require_module__ (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5051:12)
    at __webpack_require__ (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5008:18)
    at /usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5079:20
    at symbolIterator (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/neo-async/async.js:3485:9)
    at done (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/neo-async/async.js:3527:9)
    at Hook.eval [as callAsync] (eval at create (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:15:1)
    at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/tapable/lib/Hook.js:18:14)
    at /usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:4986:43
    at symbolIterator (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/neo-async/async.js:3482:9)
-- inner error --
Error: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Can't find stylesheet to import.
  ╷
1 │ @import "invalid";
  │         ^^^^^^^^^
  ╵
  src/styles.scss 1:9  root stylesheet
    at Object.<anonymous> (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[1]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[2]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/resolve-url-loader/index.js??ruleSet[1].rules[5].rules[1].use[0]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[5].rules[1].use[1]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/src/styles.scss:1:7)
    at /usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js:441:11
    at Hook.eval [as call] (eval at create (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:1)
    at Hook.CALL_DELEGATE [as _call] (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/tapable/lib/Hook.js:14:14)
    at /usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5053:39
    at tryRunOrWebpackError (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/HookWebpackError.js:83:7)
    at __webpack_require_module__ (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5051:12)
    at __webpack_require__ (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5008:18)
    at /usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/webpack/lib/Compilation.js:5079:20
    at symbolIterator (/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/neo-async/async.js:3485:9)

Generated code for /usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[1]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[5].rules[0].oneOf[0].use[2]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/resolve-url-loader/index.js??ruleSet[1].rules[5].rules[1].use[0]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[5].rules[1].use[1]!/usr/local/xxxxxxx/cli-reproductions/showwcase-v14-rc0/src/styles.scss
1 | throw new Error("Module build failed (from ./node_modules/sass-loader/dist/cjs.js):\nSassError: Can't find stylesheet to import.\n  ╷\n1 │ @import \"invalid\";\n  │         ^^^^^^^^^\n  ╵\n  src/styles.scss 1:9  root stylesheet");
```

After
```
$ ng build

./src/styles.scss - Error: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Can't find stylesheet to import.
  ╷
1 │ @import "invalid";
  │         ^^^^^^^^^
  ╵
  src/styles.scss 1:9  root stylesheet

./src/styles.scss - Error: Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
HookWebpackError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Can't find stylesheet to import.
  ╷
1 │ @import "invalid";
  │         ^^^^^^^^^
  ╵
  src/styles.scss 1:9  root stylesheet
```

(cherry picked from commit b40aeed)
  • Loading branch information
alan-agius4 authored and clydin committed Jun 27, 2022
1 parent 694b73d commit 282baff
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,59 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
)
.toPromise();
});

it('should not include error stacktraces when false', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
verbose: false,
styles: ['./src/styles.scss'],
});

// Create a compilatation error.
await harness.writeFile('./src/styles.scss', `@import 'invalid-module';`);

const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
expect(result?.success).toBeFalse();
expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching(`SassError: Can't find stylesheet to import.`),
}),
);
expect(logs).not.toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('styles.scss.webpack'),
}),
);
expect(logs).not.toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('at Object.loader'),
}),
);
});

it('should include error stacktraces when true', async () => {
harness.useTarget('build', {
...BASE_OPTIONS,
verbose: true,
styles: ['./src/styles.scss'],
});

// Create a compilatation error.
await harness.writeFile('./src/styles.scss', `@import 'invalid-module';`);

const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
expect(result?.success).toBeFalse();

expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('styles.scss.webpack'),
}),
);
expect(logs).toContain(
jasmine.objectContaining<logging.LogEntry>({
message: jasmine.stringMatching('at Object.loader'),
}),
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface HashFormat {
script: string;
}

export type WebpackStatsOptions = Exclude<Configuration['stats'], string | boolean | undefined>;

export function getOutputHashFormat(outputHashing = OutputHashing.None, length = 20): HashFormat {
const hashTemplate = `.[contenthash:${length}]`;

Expand Down Expand Up @@ -276,7 +278,6 @@ export function externalizePackages(
}
}

type WebpackStatsOptions = Exclude<Configuration['stats'], string | boolean>;
export function getStatsOptions(verbose = false): WebpackStatsOptions {
const webpackOutputOptions: WebpackStatsOptions = {
all: false, // Fallback value for stats options when an option is not defined. It has precedence over local webpack defaults.
Expand Down Expand Up @@ -306,6 +307,7 @@ export function getStatsOptions(verbose = false): WebpackStatsOptions {
version: true,
chunkModules: true,
errorDetails: true,
errorStack: true,
moduleTrace: true,
logging: 'verbose',
modulesSpace: Infinity,
Expand Down
43 changes: 35 additions & 8 deletions packages/angular_devkit/build_angular/src/webpack/utils/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Schema as BrowserBuilderOptions } from '../../builders/browser/schema';
import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
import { colors as ansiColors, removeColor } from '../../utils/color';
import { markAsyncChunksNonInitial } from './async-chunks';
import { getStatsOptions, normalizeExtraEntryPoints } from './helpers';
import { WebpackStatsOptions, getStatsOptions, normalizeExtraEntryPoints } from './helpers';

export function formatSize(size: number): string {
if (size <= 0) {
Expand Down Expand Up @@ -317,8 +317,10 @@ function statsToString(
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function statsWarningsToString(json: StatsCompilation, statsConfig: any): string {
export function statsWarningsToString(
json: StatsCompilation,
statsConfig: WebpackStatsOptions,
): string {
const colors = statsConfig.colors;
const c = (x: string) => (colors ? ansiColors.reset.cyan(x) : x);
const y = (x: string) => (colors ? ansiColors.reset.yellow(x) : x);
Expand Down Expand Up @@ -352,8 +354,10 @@ export function statsWarningsToString(json: StatsCompilation, statsConfig: any):
return output ? '\n' + output : output;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function statsErrorsToString(json: StatsCompilation, statsConfig: any): string {
export function statsErrorsToString(
json: StatsCompilation,
statsConfig: WebpackStatsOptions,
): string {
const colors = statsConfig.colors;
const c = (x: string) => (colors ? ansiColors.reset.cyan(x) : x);
const yb = (x: string) => (colors ? ansiColors.reset.yellowBright(x) : x);
Expand All @@ -369,18 +373,36 @@ export function statsErrorsToString(json: StatsCompilation, statsConfig: any): s
if (typeof error === 'string') {
output += r(`Error: ${error}\n\n`);
} else {
const file = error.file || error.moduleName;
let file = error.file || error.moduleName;
// Clean up error paths
// Ex: ./src/app/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js....
// to ./src/app/styles.scss.webpack
if (file && !statsConfig.errorDetails) {
const webpackPathIndex = file.indexOf('.webpack[');
if (webpackPathIndex !== -1) {
file = file.substring(0, webpackPathIndex);
}
}

if (file) {
output += c(file);
if (error.loc) {
output += ':' + yb(error.loc);
}
output += ' - ';
}
if (!/^error/i.test(error.message)) {

// In most cases webpack will add stack traces to error messages.
// This below cleans up the error from stacks.
// See: https://github.com/webpack/webpack/issues/15980
const message = statsConfig.errorStack
? error.message
: /[\s\S]+?(?=[\n\s]+at)/.exec(error.message)?.[0] ?? error.message;

if (!/^error/i.test(message)) {
output += r('Error: ');
}
output += `${error.message}\n\n`;
output += `${message}\n\n`;
}
}

Expand Down Expand Up @@ -428,9 +450,14 @@ export function webpackStatsLogger(
): void {
logger.info(statsToString(json, config.stats, budgetFailures));

if (typeof config.stats !== 'object') {
throw new Error('Invalid Webpack stats configuration.');
}

if (statsHasWarnings(json)) {
logger.warn(statsWarningsToString(json, config.stats));
}

if (statsHasErrors(json)) {
logger.error(statsErrorsToString(json, config.stats));
}
Expand Down

0 comments on commit 282baff

Please sign in to comment.