feat(ivy): bridge component styles into the component renderer (#25255)

PR Close #25255
This commit is contained in:
Matias Niemelä
2018-07-31 11:14:06 -07:00
parent a59d4da304
commit a37bcc3bfe
26 changed files with 3557 additions and 24 deletions

View File

@ -15,7 +15,7 @@ import {filterToMembersWithDecorator, reflectObjectLiteral, staticallyResolve} f
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {ResourceLoader} from './api';
import {extractDirectiveMetadata, extractQueriesFromDecorator, queriesFromFields} from './directive';
import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive';
import {SelectorScopeRegistry} from './selector_scope';
import {isAngularCore, unwrapExpression} from './util';
@ -135,11 +135,24 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
viewQueries.push(...queriesFromDecorator.view);
}
let styles: string[]|null = null;
if (component.has('styles')) {
styles = parseFieldArrayValue(component, 'styles', this.reflector, this.checker);
}
let encapsulation: number = 0;
if (component.has('encapsulation')) {
encapsulation = parseInt(staticallyResolve(
component.get('encapsulation') !, this.reflector, this.checker) as string);
}
return {
analysis: {
...metadata,
template,
viewQueries,
encapsulation,
styles: styles || [],
// These will be replaced during the compilation step, after all `NgModule`s have been
// analyzed and the full compilation scope for the component can be realized.

View File

@ -261,6 +261,22 @@ function isStringArrayOrDie(value: any, name: string): value is string[] {
return true;
}
export function parseFieldArrayValue(
directive: Map<string, ts.Expression>, field: string, reflector: ReflectionHost,
checker: ts.TypeChecker): null|string[] {
if (!directive.has(field)) {
return null;
}
// Resolve the field of interest from the directive metadata to a string[].
const value = staticallyResolve(directive.get(field) !, reflector, checker);
if (!isStringArrayOrDie(value, field)) {
throw new Error(`Failed to resolve @Directive.${field}`);
}
return value;
}
/**
* Interpret property mapping fields on the decorator (e.g. inputs or outputs) and return the
* correctly shaped metadata object.
@ -268,16 +284,11 @@ function isStringArrayOrDie(value: any, name: string): value is string[] {
function parseFieldToPropertyMapping(
directive: Map<string, ts.Expression>, field: string, reflector: ReflectionHost,
checker: ts.TypeChecker): {[field: string]: string} {
if (!directive.has(field)) {
const metaValues = parseFieldArrayValue(directive, field, reflector, checker);
if (!metaValues) {
return EMPTY_OBJECT;
}
// Resolve the field of interest from the directive metadata to a string[].
const metaValues = staticallyResolve(directive.get(field) !, reflector, checker);
if (!isStringArrayOrDie(metaValues, field)) {
throw new Error(`Failed to resolve @Directive.${field}`);
}
return metaValues.reduce(
(results, value) => {
// Either the value is 'field' or 'field: property'. In the first case, `property` will

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {InitialStylingFlags} from '@angular/compiler/src/core';
import {InitialStylingFlags, ViewEncapsulation} from '@angular/compiler/src/core';
import {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
import {compile, expectEmit} from './mock_compile';
@ -18,6 +18,89 @@ describe('compiler compliance: styling', () => {
compileAnimations: false,
});
describe('@Component.styles', () => {
it('should pass in the component metadata styles into the component definition and shim them using style encapsulation',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: "my-component",
styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"],
template: "..."
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template =
'styles: ["div.foo[_ngcontent-%COMP%] { color: red; }", "[_nghost-%COMP%] p[_ngcontent-%COMP%]:nth-child(even) { --webkit-transition: 1s linear all; }"]';
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should pass in styles, but skip shimming the styles if the view encapsulation signals not to',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: "my-component",
encapsulation: ${ViewEncapsulation.None},
styles: ["div.tall { height: 123px; }", ":host.small p { height:5px; }"],
template: "..."
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = 'div.tall { height: 123px; }", ":host.small p { height:5px; }';
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
it('should pass in the component metadata styles into the component definition but skip shimming when style encapsulation is set to native',
() => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
encapsulation: ${ViewEncapsulation.Native},
selector: "my-component",
styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"],
template: "..."
})
export class MyComponent {
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = 'div.cool { color: blue; }", ":host.nice p { color: gold; }';
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect template');
});
});
describe('[style] and [style.prop]', () => {
it('should create style instructions on the element', () => {
const files = {