feat(core): allow to add precompiled tokens via a provider

Introduces the new `ANALYZE_FOR_PRECOMPILE` token. This token can be used to
create a virtual provider that will populate the `precompile` fields of
components and app modules based on its
`useValue`. All components that are referenced in the `useValue`
value (either directly or in a nested array or map) will be added
to the `precompile` property.

closes #9874
related to #9726
This commit is contained in:
Tobias Bosch
2016-07-07 10:05:55 -07:00
parent 9d265b6f61
commit 7073cf74fe
12 changed files with 215 additions and 30 deletions

View File

@ -18,6 +18,7 @@ import * as cpl from './compile_metadata';
import {CompilerConfig} from './config';
import {hasLifecycleHook} from './directive_lifecycle_reflector';
import {DirectiveResolver} from './directive_resolver';
import {Identifiers, identifierToken} from './identifiers';
import {PipeResolver} from './pipe_resolver';
import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
@ -137,7 +138,7 @@ export class CompileMetadataResolver {
changeDetectionStrategy = cmpMeta.changeDetection;
if (isPresent(dirMeta.viewProviders)) {
viewProviders = this.getProvidersMetadata(
verifyNonBlankProviders(directiveType, dirMeta.viewProviders, 'viewProviders'));
verifyNonBlankProviders(directiveType, dirMeta.viewProviders, 'viewProviders'), []);
}
moduleUrl = componentModuleUrl(this._reflector, directiveType, cmpMeta);
if (cmpMeta.precompile) {
@ -149,10 +150,11 @@ export class CompileMetadataResolver {
var providers: any[] /** TODO #9100 */ = [];
if (isPresent(dirMeta.providers)) {
providers = this.getProvidersMetadata(
verifyNonBlankProviders(directiveType, dirMeta.providers, 'providers'));
verifyNonBlankProviders(directiveType, dirMeta.providers, 'providers'),
precompileTypes);
}
var queries: any[] /** TODO #9100 */ = [];
var viewQueries: any[] /** TODO #9100 */ = [];
var queries: cpl.CompileQueryMetadata[] = [];
var viewQueries: cpl.CompileQueryMetadata[] = [];
if (isPresent(dirMeta.queries)) {
queries = this.getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType);
@ -214,7 +216,7 @@ export class CompileMetadataResolver {
}
if (meta.providers) {
providers.push(...this.getProvidersMetadata(meta.providers));
providers.push(...this.getProvidersMetadata(meta.providers, precompile));
}
if (meta.directives) {
directives.push(...flattenArray(meta.directives)
@ -410,23 +412,54 @@ export class CompileMetadataResolver {
return compileToken;
}
getProvidersMetadata(providers: any[]):
getProvidersMetadata(providers: any[], targetPrecompileComponents: cpl.CompileTypeMetadata[]):
Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
return providers.map((provider) => {
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
providers.forEach((provider) => {
provider = resolveForwardRef(provider);
if (isProviderLiteral(provider)) {
provider = createProvider(provider);
}
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
if (isArray(provider)) {
return this.getProvidersMetadata(provider);
compileProvider = this.getProvidersMetadata(provider, targetPrecompileComponents);
} else if (provider instanceof Provider) {
return this.getProviderMetadata(provider);
} else if (isProviderLiteral(provider)) {
return this.getProviderMetadata(createProvider(provider));
let tokenMeta = this.getTokenMetadata(provider.token);
if (tokenMeta.equalsTo(identifierToken(Identifiers.ANALYZE_FOR_PRECOMPILE))) {
targetPrecompileComponents.push(...this.getPrecompileComponentsFromProvider(provider));
} else {
compileProvider = this.getProviderMetadata(provider);
}
} else if (isValidType(provider)) {
return this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else {
throw new BaseException(
`Invalid provider - only instances of Provider and Type are allowed, got: ${stringify(provider)}`);
}
if (compileProvider) {
compileProviders.push(compileProvider);
}
});
return compileProviders;
}
getPrecompileComponentsFromProvider(provider: Provider): cpl.CompileTypeMetadata[] {
let components: cpl.CompileTypeMetadata[] = [];
let collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
if (provider.useFactory || provider.useExisting || provider.useClass) {
throw new BaseException(`The ANALYZE_FOR_PRECOMPILE token only supports useValue!`);
}
if (!provider.multi) {
throw new BaseException(`The ANALYZE_FOR_PRECOMPILE token only supports 'multi = true'!`);
}
convertToCompileValue(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => {
let dirMeta = this.maybeGetDirectiveMetadata(identifier.runtime);
if (dirMeta) {
components.push(dirMeta.type);
}
});
return components;
}
getProviderMetadata(provider: Provider): cpl.CompileProviderMetadata {
@ -447,7 +480,7 @@ export class CompileMetadataResolver {
return new cpl.CompileProviderMetadata({
token: this.getTokenMetadata(provider.token),
useClass: compileTypeMetadata,
useValue: convertToCompileValue(provider.useValue),
useValue: convertToCompileValue(provider.useValue, []),
useFactory: compileFactoryMetadata,
useExisting: isPresent(provider.useExisting) ? this.getTokenMetadata(provider.useExisting) :
null,
@ -566,17 +599,21 @@ function componentModuleUrl(
return reflector.importUri(type);
}
// Only fill CompileIdentifierMetadata.runtime if needed...
function convertToCompileValue(value: any): any {
return visitValue(value, new _CompileValueConverter(), null);
function convertToCompileValue(
value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
return visitValue(value, new _CompileValueConverter(), targetIdentifiers);
}
class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, context: any): any {
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
let identifier: cpl.CompileIdentifierMetadata;
if (cpl.isStaticSymbol(value)) {
return new cpl.CompileIdentifierMetadata({name: value.name, moduleUrl: value.filePath});
identifier = new cpl.CompileIdentifierMetadata(
{name: value.name, moduleUrl: value.filePath, runtime: value});
} else {
return new cpl.CompileIdentifierMetadata({runtime: value});
identifier = new cpl.CompileIdentifierMetadata({runtime: value});
}
targetIdentifiers.push(identifier);
return identifier;
}
}