feat(compiler): mark @NgModules in provider lists for identification at runtime (#22005)

All of the providers in a module get compiled into a module definition in the
factory file. Some of these providers are for the actual module types, as those
are available for injection in Angular. For tree-shakeable tokens, the runtime
needs to be able to distinguish which modules are present in an injector.

This change adds a NodeFlag which tags those module providers for later
identification.

PR Close #22005
This commit is contained in:
Alex Rickabaugh 2018-02-02 09:31:58 -08:00 committed by Miško Hevery
parent 647b8595d0
commit 2d5e7d1b52
7 changed files with 28 additions and 10 deletions

View File

@ -194,6 +194,7 @@ export const enum NodeFlags {
TypeViewQuery = 1 << 27, TypeViewQuery = 1 << 27,
StaticQuery = 1 << 28, StaticQuery = 1 << 28,
DynamicQuery = 1 << 29, DynamicQuery = 1 << 29,
TypeModuleProvider = 1 << 30,
CatQuery = TypeContentQuery | TypeViewQuery, CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values... // mutually exclusive values...

View File

@ -321,11 +321,11 @@ export class NgModuleProviderAnalyzer {
const ngModuleProvider = {token: {identifier: ngModuleType}, useClass: ngModuleType}; const ngModuleProvider = {token: {identifier: ngModuleType}, useClass: ngModuleType};
_resolveProviders( _resolveProviders(
[ngModuleProvider], ProviderAstType.PublicService, true, sourceSpan, this._errors, [ngModuleProvider], ProviderAstType.PublicService, true, sourceSpan, this._errors,
this._allProviders); this._allProviders, true);
}); });
_resolveProviders( _resolveProviders(
ngModule.transitiveModule.providers.map(entry => entry.provider).concat(extraProviders), ngModule.transitiveModule.providers.map(entry => entry.provider).concat(extraProviders),
ProviderAstType.PublicService, false, sourceSpan, this._errors, this._allProviders); ProviderAstType.PublicService, false, sourceSpan, this._errors, this._allProviders, false);
} }
parse(): ProviderAst[] { parse(): ProviderAst[] {
@ -448,7 +448,7 @@ function _transformProviderAst(
{eager, providers}: {eager: boolean, providers: CompileProviderMetadata[]}): ProviderAst { {eager, providers}: {eager: boolean, providers: CompileProviderMetadata[]}): ProviderAst {
return new ProviderAst( return new ProviderAst(
provider.token, provider.multiProvider, provider.eager || eager, providers, provider.token, provider.multiProvider, provider.eager || eager, providers,
provider.providerType, provider.lifecycleHooks, provider.sourceSpan); provider.providerType, provider.lifecycleHooks, provider.sourceSpan, provider.isModule);
} }
function _resolveProvidersFromDirectives( function _resolveProvidersFromDirectives(
@ -461,7 +461,7 @@ function _resolveProvidersFromDirectives(
_resolveProviders( _resolveProviders(
[dirProvider], [dirProvider],
directive.isComponent ? ProviderAstType.Component : ProviderAstType.Directive, true, directive.isComponent ? ProviderAstType.Component : ProviderAstType.Directive, true,
sourceSpan, targetErrors, providersByToken); sourceSpan, targetErrors, providersByToken, false);
}); });
// Note: directives need to be able to overwrite providers of a component! // Note: directives need to be able to overwrite providers of a component!
@ -470,10 +470,10 @@ function _resolveProvidersFromDirectives(
directivesWithComponentFirst.forEach((directive) => { directivesWithComponentFirst.forEach((directive) => {
_resolveProviders( _resolveProviders(
directive.providers, ProviderAstType.PublicService, false, sourceSpan, targetErrors, directive.providers, ProviderAstType.PublicService, false, sourceSpan, targetErrors,
providersByToken); providersByToken, false);
_resolveProviders( _resolveProviders(
directive.viewProviders, ProviderAstType.PrivateService, false, sourceSpan, targetErrors, directive.viewProviders, ProviderAstType.PrivateService, false, sourceSpan, targetErrors,
providersByToken); providersByToken, false);
}); });
return providersByToken; return providersByToken;
} }
@ -481,7 +481,7 @@ function _resolveProvidersFromDirectives(
function _resolveProviders( function _resolveProviders(
providers: CompileProviderMetadata[], providerType: ProviderAstType, eager: boolean, providers: CompileProviderMetadata[], providerType: ProviderAstType, eager: boolean,
sourceSpan: ParseSourceSpan, targetErrors: ParseError[], sourceSpan: ParseSourceSpan, targetErrors: ParseError[],
targetProvidersByToken: Map<any, ProviderAst>) { targetProvidersByToken: Map<any, ProviderAst>, isModule: boolean) {
providers.forEach((provider) => { providers.forEach((provider) => {
let resolvedProvider = targetProvidersByToken.get(tokenReference(provider.token)); let resolvedProvider = targetProvidersByToken.get(tokenReference(provider.token));
if (resolvedProvider != null && !!resolvedProvider.multiProvider !== !!provider.multi) { if (resolvedProvider != null && !!resolvedProvider.multiProvider !== !!provider.multi) {
@ -497,7 +497,7 @@ function _resolveProviders(
const isUseValue = !(provider.useClass || provider.useExisting || provider.useFactory); const isUseValue = !(provider.useClass || provider.useExisting || provider.useFactory);
resolvedProvider = new ProviderAst( resolvedProvider = new ProviderAst(
provider.token, !!provider.multi, eager || isUseValue, [provider], providerType, provider.token, !!provider.multi, eager || isUseValue, [provider], providerType,
lifecycleHooks, sourceSpan); lifecycleHooks, sourceSpan, isModule);
targetProvidersByToken.set(tokenReference(provider.token), resolvedProvider); targetProvidersByToken.set(tokenReference(provider.token), resolvedProvider);
} else { } else {
if (!provider.multi) { if (!provider.multi) {

View File

@ -192,7 +192,8 @@ export class ProviderAst implements TemplateAst {
constructor( constructor(
public token: CompileTokenMetadata, public multiProvider: boolean, public eager: boolean, public token: CompileTokenMetadata, public multiProvider: boolean, public eager: boolean,
public providers: CompileProviderMetadata[], public providerType: ProviderAstType, public providers: CompileProviderMetadata[], public providerType: ProviderAstType,
public lifecycleHooks: LifecycleHooks[], public sourceSpan: ParseSourceSpan) {} public lifecycleHooks: LifecycleHooks[], public sourceSpan: ParseSourceSpan,
readonly isModule: boolean) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
// No visit method in the visitor for now... // No visit method in the visitor for now...

View File

@ -29,6 +29,9 @@ export function providerDef(ctx: OutputContext, providerAst: ProviderAst): {
if (providerAst.providerType === ProviderAstType.PrivateService) { if (providerAst.providerType === ProviderAstType.PrivateService) {
flags |= NodeFlags.PrivateProvider; flags |= NodeFlags.PrivateProvider;
} }
if (providerAst.isModule) {
flags |= NodeFlags.TypeModuleProvider;
}
providerAst.lifecycleHooks.forEach((lifecycleHook) => { providerAst.lifecycleHooks.forEach((lifecycleHook) => {
// for regular providers, we only support ngOnDestroy // for regular providers, we only support ngOnDestroy
if (lifecycleHook === LifecycleHooks.OnDestroy || if (lifecycleHook === LifecycleHooks.OnDestroy ||

View File

@ -36,8 +36,12 @@ export function moduleProvideDef(
export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition { export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {}; const providersByKey: {[key: string]: NgModuleProviderDef} = {};
const modules = [];
for (let i = 0; i < providers.length; i++) { for (let i = 0; i < providers.length; i++) {
const provider = providers[i]; const provider = providers[i];
if (provider.flags & NodeFlags.TypeNgModule) {
modules.push(provider.token);
}
provider.index = i; provider.index = i;
providersByKey[tokenKey(provider.token)] = provider; providersByKey[tokenKey(provider.token)] = provider;
} }
@ -45,7 +49,8 @@ export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition
// Will be filled later... // Will be filled later...
factory: null, factory: null,
providersByKey, providersByKey,
providers providers,
modules,
}; };
} }

View File

@ -480,6 +480,7 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
private _destroyed: boolean = false; private _destroyed: boolean = false;
/** @internal */ /** @internal */
_providers: any[]; _providers: any[];
_modules: any[];
readonly injector: Injector = this; readonly injector: Injector = this;

View File

@ -42,6 +42,7 @@ export interface Definition<DF extends DefinitionFactory<any>> { factory: DF|nul
export interface NgModuleDefinition extends Definition<NgModuleDefinitionFactory> { export interface NgModuleDefinition extends Definition<NgModuleDefinitionFactory> {
providers: NgModuleProviderDef[]; providers: NgModuleProviderDef[];
providersByKey: {[tokenKey: string]: NgModuleProviderDef}; providersByKey: {[tokenKey: string]: NgModuleProviderDef};
modules: any[];
} }
export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {} export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {}
@ -192,6 +193,7 @@ export const enum NodeFlags {
TypeViewQuery = 1 << 27, TypeViewQuery = 1 << 27,
StaticQuery = 1 << 28, StaticQuery = 1 << 28,
DynamicQuery = 1 << 29, DynamicQuery = 1 << 29,
TypeNgModule = 1 << 30,
CatQuery = TypeContentQuery | TypeViewQuery, CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values... // mutually exclusive values...
@ -293,6 +295,11 @@ export const enum DepFlags {
Value = 2 << 2, Value = 2 << 2,
} }
export interface InjectableDef {
scope: any;
factory: () => any;
}
export interface TextDef { prefix: string; } export interface TextDef { prefix: string; }
export interface QueryDef { export interface QueryDef {