From ccc8e0350810d123269f55de29acd7964e663f7e Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Mon, 12 Dec 2022 16:26:30 +0000 Subject: [PATCH] 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 (cherry picked from commit 8fd08491a88bc2b088dcb3ac68a6c328167e38c3) --- .../karma/tests/options/styles_spec.ts | 23 +++++++-- .../webpack/plugins/styles-webpack-plugin.ts | 49 ++++++++++--------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts index bc7623828784..699aeebcb551 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts @@ -16,7 +16,7 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { 'src/styles.css': 'p {display: none}', 'src/app/app.component.ts': ` import { Component } from '@angular/core'; - + @Component({ selector: 'app-root', template: '

Hello World

' @@ -27,7 +27,7 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { 'src/app/app.component.spec.ts': ` import { TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; - + describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ @@ -38,7 +38,7 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { ] }).compileComponents(); }); - + it('should not contain text that is hidden via css', () => { const fixture = TestBed.createComponent(AppComponent); expect(fixture.nativeElement.innerText).not.toContain('Hello World'); @@ -129,5 +129,22 @@ describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { const { result } = await harness.executeOnce(); expect(result?.success).toBeTrue(); }); + + it('fails and shows an error if style does not exist', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + styles: ['src/test-style-a.css'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringMatching(`Can't resolve 'src/test-style-a.css'`), + }), + ); + }); }); }); diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts index bb44a4232826..c364c2459820 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/styles-webpack-plugin.ts @@ -7,7 +7,6 @@ */ import assert from 'assert'; -import { pluginName } from 'mini-css-extract-plugin'; import type { Compilation, Compiler } from 'webpack'; import { assertIsError } from '../../utils/error'; import { addError } from '../../utils/webpack-diagnostics'; @@ -30,10 +29,6 @@ export class StylesWebpackPlugin { apply(compiler: Compiler): void { const { entryPoints, preserveSymlinks, root } = this.options; - const webpackOptions = compiler.options; - const entry = - typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry; - const resolver = compiler.resolverFactory.get('global-styles', { conditionNames: ['sass', 'less', 'style'], mainFields: ['sass', 'less', 'style', 'main', '...'], @@ -45,32 +40,38 @@ export class StylesWebpackPlugin { fileSystem: compiler.inputFileSystem, }); - webpackOptions.entry = async () => { - const entrypoints = await entry; + const webpackOptions = compiler.options; + compiler.hooks.environment.tap(PLUGIN_NAME, () => { + const entry = + typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry; - for (const [bundleName, paths] of Object.entries(entryPoints)) { - entrypoints[bundleName] ??= {}; - const entryImport = (entrypoints[bundleName].import ??= []); + webpackOptions.entry = async () => { + const entrypoints = await entry; - for (const path of paths) { - try { - const resolvedPath = resolver.resolveSync({}, root, path); - if (resolvedPath) { - entryImport.push(`${resolvedPath}?ngGlobalStyle`); - } else { + for (const [bundleName, paths] of Object.entries(entryPoints)) { + entrypoints[bundleName] ??= {}; + const entryImport = (entrypoints[bundleName].import ??= []); + + for (const path of paths) { + try { + const resolvedPath = resolver.resolveSync({}, root, path); + if (resolvedPath) { + entryImport.push(`${resolvedPath}?ngGlobalStyle`); + } else { + assert(this.compilation, 'Compilation cannot be undefined.'); + addError(this.compilation, `Cannot resolve '${path}'.`); + } + } catch (error) { assert(this.compilation, 'Compilation cannot be undefined.'); - addError(this.compilation, `Cannot resolve '${path}'.`); + assertIsError(error); + addError(this.compilation, error.message); } - } catch (error) { - assert(this.compilation, 'Compilation cannot be undefined.'); - assertIsError(error); - addError(this.compilation, error.message); } } - } - return entrypoints; - }; + return entrypoints; + }; + }); compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { this.compilation = compilation;