Skip to content

Commit c92021b

Browse files
committed
fix(@angular-devkit/build-angular): display actionable error when a style does not exist in Karma builder
Prior to this change the the error was not displayed correctly due to compilation being undefined. Closes #24416
1 parent 67ffa8b commit c92021b

File tree

3 files changed

+75
-55
lines changed

3 files changed

+75
-55
lines changed

packages/angular_devkit/build_angular/src/builders/karma/find-tests-plugin.ts

+30-28
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,38 @@ export class FindTestsPlugin {
3333
constructor(private options: FindTestsPluginOptions) {}
3434

3535
apply(compiler: Compiler): void {
36-
const { include = ['**/*.spec.ts'], projectSourceRoot, workspaceRoot } = this.options;
37-
const webpackOptions = compiler.options;
38-
const entry =
39-
typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry;
40-
4136
let originalImport: string[] | undefined;
37+
const { include = ['**/*.spec.ts'], projectSourceRoot, workspaceRoot } = this.options;
4238

43-
// Add tests files are part of the entry-point.
44-
webpackOptions.entry = async () => {
45-
const specFiles = await findTests(include, workspaceRoot, projectSourceRoot);
46-
47-
if (!specFiles.length) {
48-
assert(this.compilation, 'Compilation cannot be undefined.');
49-
addError(
50-
this.compilation,
51-
`Specified patterns: "${include.join(', ')}" did not match any spec files.`,
52-
);
53-
}
54-
55-
const entrypoints = await entry;
56-
const entrypoint = entrypoints['main'];
57-
if (!entrypoint.import) {
58-
throw new Error(`Cannot find 'main' entrypoint.`);
59-
}
60-
61-
originalImport ??= entrypoint.import;
62-
entrypoint.import = [...originalImport, ...specFiles];
63-
64-
return entrypoints;
65-
};
39+
const webpackOptions = compiler.options;
40+
compiler.hooks.environment.tap(PLUGIN_NAME, () => {
41+
const entry =
42+
typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry;
43+
44+
// Add tests files are part of the entry-point.
45+
webpackOptions.entry = async () => {
46+
const specFiles = await findTests(include, workspaceRoot, projectSourceRoot);
47+
48+
if (!specFiles.length) {
49+
assert(this.compilation, 'Compilation cannot be undefined.');
50+
addError(
51+
this.compilation,
52+
`Specified patterns: "${include.join(', ')}" did not match any spec files.`,
53+
);
54+
}
55+
56+
const entrypoints = await entry;
57+
const entrypoint = entrypoints['main'];
58+
if (!entrypoint.import) {
59+
throw new Error(`Cannot find 'main' entrypoint.`);
60+
}
61+
62+
originalImport ??= entrypoint.import;
63+
entrypoint.import = [...originalImport, ...specFiles];
64+
65+
return entrypoints;
66+
};
67+
});
6668

6769
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
6870
this.compilation = compilation;

packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => {
1616
'src/styles.css': 'p {display: none}',
1717
'src/app/app.component.ts': `
1818
import { Component } from '@angular/core';
19-
19+
2020
@Component({
2121
selector: 'app-root',
2222
template: '<p>Hello World</p>'
@@ -27,7 +27,7 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => {
2727
'src/app/app.component.spec.ts': `
2828
import { TestBed } from '@angular/core/testing';
2929
import { AppComponent } from './app.component';
30-
30+
3131
describe('AppComponent', () => {
3232
beforeEach(async () => {
3333
await TestBed.configureTestingModule({
@@ -38,7 +38,7 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => {
3838
]
3939
}).compileComponents();
4040
});
41-
41+
4242
it('should not contain text that is hidden via css', () => {
4343
const fixture = TestBed.createComponent(AppComponent);
4444
expect(fixture.nativeElement.innerText).not.toContain('Hello World');
@@ -129,5 +129,22 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => {
129129
const { result } = await harness.executeOnce();
130130
expect(result?.success).toBeTrue();
131131
});
132+
133+
it('fails and shows an error if style does not exist', async () => {
134+
harness.useTarget('test', {
135+
...BASE_OPTIONS,
136+
styles: ['src/test-style-a.css'],
137+
});
138+
139+
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
140+
141+
expect(result?.success).toBeFalse();
142+
expect(logs).toContain(
143+
jasmine.objectContaining({
144+
level: 'error',
145+
message: jasmine.stringMatching(`Can't resolve 'src/test-style-a.css'`),
146+
}),
147+
);
148+
});
132149
});
133150
});

packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts

+25-24
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import assert from 'assert';
10-
import { pluginName } from 'mini-css-extract-plugin';
1110
import type { Compilation, Compiler } from 'webpack';
1211
import { assertIsError } from '../../utils/error';
1312
import { addError } from '../../utils/webpack-diagnostics';
@@ -30,10 +29,6 @@ export class StylesWebpackPlugin {
3029

3130
apply(compiler: Compiler): void {
3231
const { entryPoints, preserveSymlinks, root } = this.options;
33-
const webpackOptions = compiler.options;
34-
const entry =
35-
typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry;
36-
3732
const resolver = compiler.resolverFactory.get('global-styles', {
3833
conditionNames: ['sass', 'less', 'style'],
3934
mainFields: ['sass', 'less', 'style', 'main', '...'],
@@ -45,32 +40,38 @@ export class StylesWebpackPlugin {
4540
fileSystem: compiler.inputFileSystem,
4641
});
4742

48-
webpackOptions.entry = async () => {
49-
const entrypoints = await entry;
43+
const webpackOptions = compiler.options;
44+
compiler.hooks.environment.tap(PLUGIN_NAME, () => {
45+
const entry =
46+
typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry;
5047

51-
for (const [bundleName, paths] of Object.entries(entryPoints)) {
52-
entrypoints[bundleName] ??= {};
53-
const entryImport = (entrypoints[bundleName].import ??= []);
48+
webpackOptions.entry = async () => {
49+
const entrypoints = await entry;
5450

55-
for (const path of paths) {
56-
try {
57-
const resolvedPath = resolver.resolveSync({}, root, path);
58-
if (resolvedPath) {
59-
entryImport.push(`${resolvedPath}?ngGlobalStyle`);
60-
} else {
51+
for (const [bundleName, paths] of Object.entries(entryPoints)) {
52+
entrypoints[bundleName] ??= {};
53+
const entryImport = (entrypoints[bundleName].import ??= []);
54+
55+
for (const path of paths) {
56+
try {
57+
const resolvedPath = resolver.resolveSync({}, root, path);
58+
if (resolvedPath) {
59+
entryImport.push(`${resolvedPath}?ngGlobalStyle`);
60+
} else {
61+
assert(this.compilation, 'Compilation cannot be undefined.');
62+
addError(this.compilation, `Cannot resolve '${path}'.`);
63+
}
64+
} catch (error) {
6165
assert(this.compilation, 'Compilation cannot be undefined.');
62-
addError(this.compilation, `Cannot resolve '${path}'.`);
66+
assertIsError(error);
67+
addError(this.compilation, error.message);
6368
}
64-
} catch (error) {
65-
assert(this.compilation, 'Compilation cannot be undefined.');
66-
assertIsError(error);
67-
addError(this.compilation, error.message);
6869
}
6970
}
70-
}
7171

72-
return entrypoints;
73-
};
72+
return entrypoints;
73+
};
74+
});
7475

7576
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
7677
this.compilation = compilation;

0 commit comments

Comments
 (0)