diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 8e40ecfe9e..b32786d8bc 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -57,6 +57,7 @@ export class ComponentDecoratorHandler implements preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise|undefined { const meta = this._resolveLiteral(decorator); const component = reflectObjectLiteral(meta); + const promises: Promise[] = []; if (this.resourceLoader.preload !== undefined && component.has('templateUrl')) { const templateUrlExpr = component.get('templateUrl') !; @@ -66,9 +67,27 @@ export class ComponentDecoratorHandler implements ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string'); } const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), templateUrl); - return this.resourceLoader.preload(url); + const promise = this.resourceLoader.preload(url); + if (promise !== undefined) { + promises.push(promise); + } + } + + const styleUrls = this._extractStyleUrls(component); + if (this.resourceLoader.preload !== undefined && styleUrls !== null) { + for (const styleUrl of styleUrls) { + const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), styleUrl); + const promise = this.resourceLoader.preload(url); + if (promise !== undefined) { + promises.push(promise); + } + } + } + if (promises.length !== 0) { + return Promise.all(promises).then(() => undefined); + } else { + return undefined; } - return undefined; } analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput { @@ -186,6 +205,14 @@ export class ComponentDecoratorHandler implements styles = parseFieldArrayValue(component, 'styles', this.reflector, this.checker); } + let styleUrls = this._extractStyleUrls(component); + if (styleUrls !== null) { + if (styles === null) { + styles = []; + } + styles.push(...styleUrls.map(styleUrl => this.resourceLoader.load(styleUrl))); + } + let encapsulation: number = 0; if (component.has('encapsulation')) { encapsulation = parseInt(staticallyResolve( @@ -282,4 +309,18 @@ export class ComponentDecoratorHandler implements this.literalCache.set(decorator, meta); return meta; } + + private _extractStyleUrls(component: Map): string[]|null { + if (!component.has('styleUrls')) { + return null; + } + + const styleUrlsExpr = component.get('styleUrls') !; + const styleUrls = staticallyResolve(styleUrlsExpr, this.reflector, this.checker); + if (!Array.isArray(styleUrls) || !styleUrls.every(url => typeof url === 'string')) { + throw new FatalDiagnosticError( + ErrorCode.VALUE_HAS_WRONG_TYPE, styleUrlsExpr, 'styleUrls must be an array of strings'); + } + return styleUrls as string[]; + } } diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 210abe4cc5..29fc307877 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -89,6 +89,26 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).toContain('Hello World'); }); + it('should compile components with styleUrls', () => { + env.tsconfig(); + env.write('test.ts', ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'test-cmp', + styleUrls: ['./dir/style.css'], + template: '', + }) + export class TestCmp {} + `); + env.write('dir/style.css', ':host { background-color: blue; }'); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('background-color: blue'); + }); + it('should compile NgModules without errors', () => { env.tsconfig(); env.write('test.ts', `