feat(ivy): support styleUrls in ngtsc (#27357)

This commit adds support for resolution of styleUrls to ngtsc. Previously
this field was never read, and so components with styleUrls would appear
unstyled after compilation.

PR Close #27357
This commit is contained in:
Alex Rickabaugh 2018-11-29 14:17:51 -08:00 committed by Igor Minar
parent b5ed403bc4
commit cfb67edd85
2 changed files with 63 additions and 2 deletions

View File

@ -57,6 +57,7 @@ export class ComponentDecoratorHandler implements
preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise<void>|undefined { preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise<void>|undefined {
const meta = this._resolveLiteral(decorator); const meta = this._resolveLiteral(decorator);
const component = reflectObjectLiteral(meta); const component = reflectObjectLiteral(meta);
const promises: Promise<void>[] = [];
if (this.resourceLoader.preload !== undefined && component.has('templateUrl')) { if (this.resourceLoader.preload !== undefined && component.has('templateUrl')) {
const templateUrlExpr = component.get('templateUrl') !; const templateUrlExpr = component.get('templateUrl') !;
@ -66,10 +67,28 @@ export class ComponentDecoratorHandler implements
ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string'); ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string');
} }
const url = path.posix.resolve(path.dirname(node.getSourceFile().fileName), templateUrl); 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<ComponentHandlerData> { analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<ComponentHandlerData> {
const meta = this._resolveLiteral(decorator); const meta = this._resolveLiteral(decorator);
@ -186,6 +205,14 @@ export class ComponentDecoratorHandler implements
styles = parseFieldArrayValue(component, 'styles', this.reflector, this.checker); 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; let encapsulation: number = 0;
if (component.has('encapsulation')) { if (component.has('encapsulation')) {
encapsulation = parseInt(staticallyResolve( encapsulation = parseInt(staticallyResolve(
@ -282,4 +309,18 @@ export class ComponentDecoratorHandler implements
this.literalCache.set(decorator, meta); this.literalCache.set(decorator, meta);
return meta; return meta;
} }
private _extractStyleUrls(component: Map<string, ts.Expression>): 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[];
}
} }

View File

@ -89,6 +89,26 @@ describe('ngtsc behavioral tests', () => {
expect(jsContents).toContain('Hello World'); 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', () => { it('should compile NgModules without errors', () => {
env.tsconfig(); env.tsconfig();
env.write('test.ts', ` env.write('test.ts', `