Skip to content

Commit e1efc35

Browse files
committed
fix(@ngtools/webpack): recover from component stylesheet errors
Webpack doesn't handle well expections and promise rejections. With this change we use the compilation errors. Closes #19892
1 parent 2cf374a commit e1efc35

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { logging } from '@angular-devkit/core';
9+
import { concatMap, count, take, timeout } from 'rxjs/operators';
10+
import { buildWebpackBrowser } from '../../index';
11+
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
12+
13+
describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
14+
describe('Behavior: "Rebuild Error"', () => {
15+
it('recovers from component stylesheet error', async () => {
16+
harness.useTarget('build', {
17+
...BASE_OPTIONS,
18+
watch: true,
19+
});
20+
21+
const buildCount = await harness
22+
.execute({ outputLogsOnFailure: false })
23+
.pipe(
24+
timeout(30000),
25+
concatMap(async ({ result, logs }, index) => {
26+
switch (index) {
27+
case 0:
28+
expect(result?.success).toBeTrue();
29+
await harness.writeFile('src/app/app.component.css', 'invalid-css-content');
30+
31+
break;
32+
case 1:
33+
expect(result?.success).toBeFalse();
34+
expect(logs).toContain(
35+
jasmine.objectContaining<logging.LogEntry>({
36+
message: jasmine.stringMatching('invalid-css-content'),
37+
}),
38+
);
39+
40+
await harness.writeFile('src/app/app.component.css', 'p { color: green }');
41+
42+
break;
43+
case 2:
44+
expect(result?.success).toBeTrue();
45+
expect(logs).not.toContain(
46+
jasmine.objectContaining<logging.LogEntry>({
47+
message: jasmine.stringMatching('invalid-css-content'),
48+
}),
49+
);
50+
51+
harness.expectFile('dist/main.js').content.toContain('p { color: green }');
52+
53+
break;
54+
}
55+
}),
56+
take(3),
57+
count(),
58+
)
59+
.toPromise();
60+
61+
expect(buildCount).toBe(3);
62+
});
63+
});
64+
});

packages/ngtools/webpack/src/resource_loader.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,22 @@ export class WebpackResourceLoader {
9393
new LibraryTemplatePlugin('resource', 'var').apply(childCompiler);
9494

9595
childCompiler.hooks.thisCompilation.tap('ngtools-webpack', (compilation: any) => {
96-
compilation.hooks.additionalAssets.tapPromise('ngtools-webpack', async () => {
96+
compilation.hooks.additionalAssets.tap('ngtools-webpack', () => {
9797
const asset = compilation.assets[filePath];
9898
if (!asset) {
9999
return;
100100
}
101101

102-
const output = await this._evaluate(filePath, asset.source());
103-
compilation.assets[filePath] = new RawSource(output);
102+
try {
103+
const output = this._evaluate(filePath, asset.source());
104+
105+
if (typeof output === 'string') {
106+
compilation.assets[filePath] = new RawSource(output);
107+
}
108+
} catch (error) {
109+
// Use compilation errors, as otherwise webpack will choke
110+
compilation.errors.push(error);
111+
}
104112
});
105113
});
106114

@@ -153,10 +161,16 @@ export class WebpackResourceLoader {
153161
return { outputName: filePath, source: finalOutput ?? '', success: !errors?.length };
154162
}
155163

156-
private async _evaluate(filename: string, source: string): Promise<string> {
164+
private _evaluate(filename: string, source: string): string | null {
157165
// Evaluate code
158166
const context: { resource?: string | { default?: string } } = {};
159-
vm.runInNewContext(source, context, { filename });
167+
168+
try {
169+
vm.runInNewContext(source, context, { filename });
170+
} catch {
171+
// Error are propagated through the child compilation.
172+
return null;
173+
}
160174

161175
if (typeof context.resource === 'string') {
162176
return context.resource;

0 commit comments

Comments
 (0)