2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2016-08-10 18:55:18 -04:00
|
|
|
|
2016-11-23 12:42:19 -05:00
|
|
|
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
2017-05-18 16:46:51 -04:00
|
|
|
import {CompileReflector} from './compile_reflector';
|
|
|
|
import {Identifiers, createTokenForExternalReference} from './identifiers';
|
2016-06-08 19:38:52 -04:00
|
|
|
import {ParseError, ParseSourceSpan} from './parse_util';
|
2017-02-15 11:36:49 -05:00
|
|
|
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst} from './template_parser/template_ast';
|
2016-01-06 17:13:44 -05:00
|
|
|
|
|
|
|
export class ProviderError extends ParseError {
|
|
|
|
constructor(message: string, span: ParseSourceSpan) { super(span, message); }
|
|
|
|
}
|
|
|
|
|
2017-02-02 18:01:35 -05:00
|
|
|
export interface QueryWithId {
|
|
|
|
meta: CompileQueryMetadata;
|
2017-02-15 11:36:49 -05:00
|
|
|
queryId: number;
|
2017-02-02 18:01:35 -05:00
|
|
|
}
|
|
|
|
|
2016-01-06 17:13:44 -05:00
|
|
|
export class ProviderViewContext {
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2017-02-02 18:01:35 -05:00
|
|
|
viewQueries: Map<any, QueryWithId[]>;
|
2016-01-06 17:13:44 -05:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2016-08-29 11:52:25 -04:00
|
|
|
viewProviders: Map<any, boolean>;
|
2016-01-06 17:13:44 -05:00
|
|
|
errors: ProviderError[] = [];
|
|
|
|
|
2017-05-18 16:46:51 -04:00
|
|
|
constructor(public reflector: CompileReflector, public component: CompileDirectiveMetadata) {
|
2016-01-06 17:13:44 -05:00
|
|
|
this.viewQueries = _getViewQueries(component);
|
2016-08-29 11:52:25 -04:00
|
|
|
this.viewProviders = new Map<any, boolean>();
|
2016-11-30 13:52:51 -05:00
|
|
|
component.viewProviders.forEach((provider) => {
|
2017-03-02 12:37:01 -05:00
|
|
|
if (this.viewProviders.get(tokenReference(provider.token)) == null) {
|
2016-11-23 12:42:19 -05:00
|
|
|
this.viewProviders.set(tokenReference(provider.token), true);
|
2016-06-08 19:38:52 -04:00
|
|
|
}
|
|
|
|
});
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ProviderElementContext {
|
2017-02-02 18:01:35 -05:00
|
|
|
private _contentQueries: Map<any, QueryWithId[]>;
|
2016-01-06 17:13:44 -05:00
|
|
|
|
2016-08-29 11:52:25 -04:00
|
|
|
private _transformedProviders = new Map<any, ProviderAst>();
|
|
|
|
private _seenProviders = new Map<any, boolean>();
|
|
|
|
private _allProviders: Map<any, ProviderAst>;
|
2016-01-06 17:13:44 -05:00
|
|
|
private _attrs: {[key: string]: string};
|
2017-02-02 18:01:35 -05:00
|
|
|
private _queriedTokens = new Map<any, QueryMatch[]>();
|
2016-01-06 17:13:44 -05:00
|
|
|
|
2017-09-11 18:10:19 -04:00
|
|
|
public readonly transformedHasViewContainer: boolean = false;
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
constructor(
|
2016-09-23 16:37:04 -04:00
|
|
|
public viewContext: ProviderViewContext, private _parent: ProviderElementContext,
|
2016-06-08 19:38:52 -04:00
|
|
|
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
|
2017-02-15 11:36:49 -05:00
|
|
|
refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number,
|
|
|
|
private _sourceSpan: ParseSourceSpan) {
|
2016-01-06 17:13:44 -05:00
|
|
|
this._attrs = {};
|
|
|
|
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
|
2016-11-12 08:08:58 -05:00
|
|
|
const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
|
2016-01-06 17:13:44 -05:00
|
|
|
this._allProviders =
|
2016-09-23 16:37:04 -04:00
|
|
|
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors);
|
2017-02-15 11:36:49 -05:00
|
|
|
this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta);
|
2016-11-03 19:58:27 -04:00
|
|
|
Array.from(this._allProviders.values()).forEach((provider) => {
|
2017-02-02 18:01:35 -05:00
|
|
|
this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens);
|
2016-08-29 11:52:25 -04:00
|
|
|
});
|
2017-02-15 11:36:49 -05:00
|
|
|
if (isTemplate) {
|
2017-05-18 16:46:51 -04:00
|
|
|
const templateRefId =
|
|
|
|
createTokenForExternalReference(this.viewContext.reflector, Identifiers.TemplateRef);
|
2017-02-15 11:36:49 -05:00
|
|
|
this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens);
|
|
|
|
}
|
2017-02-02 18:01:35 -05:00
|
|
|
refs.forEach((refAst) => {
|
2017-05-18 16:46:51 -04:00
|
|
|
let defaultQueryValue = refAst.value ||
|
|
|
|
createTokenForExternalReference(this.viewContext.reflector, Identifiers.ElementRef);
|
2017-02-02 18:01:35 -05:00
|
|
|
this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens);
|
|
|
|
});
|
2017-05-18 16:46:51 -04:00
|
|
|
if (this._queriedTokens.get(
|
|
|
|
this.viewContext.reflector.resolveExternalReference(Identifiers.ViewContainerRef))) {
|
2017-09-11 18:10:19 -04:00
|
|
|
this.transformedHasViewContainer = true;
|
2016-04-18 16:24:42 -04:00
|
|
|
}
|
|
|
|
|
2016-01-06 17:13:44 -05:00
|
|
|
// create the providers that we know are eager first
|
2016-11-03 19:58:27 -04:00
|
|
|
Array.from(this._allProviders.values()).forEach((provider) => {
|
2017-02-02 18:01:35 -05:00
|
|
|
const eager = provider.eager || this._queriedTokens.get(tokenReference(provider.token));
|
2016-04-18 16:24:42 -04:00
|
|
|
if (eager) {
|
|
|
|
this._getOrCreateLocalProvider(provider.providerType, provider.token, true);
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
afterElement() {
|
|
|
|
// collect lazy providers
|
2016-11-03 19:58:27 -04:00
|
|
|
Array.from(this._allProviders.values()).forEach((provider) => {
|
2016-04-18 16:24:42 -04:00
|
|
|
this._getOrCreateLocalProvider(provider.providerType, provider.token, false);
|
|
|
|
});
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
|
2016-11-03 19:58:27 -04:00
|
|
|
get transformProviders(): ProviderAst[] {
|
2017-05-11 13:26:02 -04:00
|
|
|
// Note: Maps keep their insertion order.
|
|
|
|
const lazyProviders: ProviderAst[] = [];
|
|
|
|
const eagerProviders: ProviderAst[] = [];
|
|
|
|
this._transformedProviders.forEach(provider => {
|
|
|
|
if (provider.eager) {
|
|
|
|
eagerProviders.push(provider);
|
|
|
|
} else {
|
|
|
|
lazyProviders.push(provider);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return lazyProviders.concat(eagerProviders);
|
2016-11-03 19:58:27 -04:00
|
|
|
}
|
2016-01-06 17:13:44 -05:00
|
|
|
|
|
|
|
get transformedDirectiveAsts(): DirectiveAst[] {
|
2016-11-12 08:08:58 -05:00
|
|
|
const sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier);
|
|
|
|
const sortedDirectives = this._directiveAsts.slice();
|
2016-10-21 18:14:44 -04:00
|
|
|
sortedDirectives.sort(
|
|
|
|
(dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) -
|
2016-06-08 19:38:52 -04:00
|
|
|
sortedProviderTypes.indexOf(dir2.directive.type));
|
2016-01-06 17:13:44 -05:00
|
|
|
return sortedDirectives;
|
|
|
|
}
|
|
|
|
|
2017-02-02 18:01:35 -05:00
|
|
|
get queryMatches(): QueryMatch[] {
|
|
|
|
const allMatches: QueryMatch[] = [];
|
|
|
|
this._queriedTokens.forEach((matches: QueryMatch[]) => { allMatches.push(...matches); });
|
|
|
|
return allMatches;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _addQueryReadsTo(
|
|
|
|
token: CompileTokenMetadata, defaultValue: CompileTokenMetadata,
|
|
|
|
queryReadTokens: Map<any, QueryMatch[]>) {
|
2016-04-18 16:24:42 -04:00
|
|
|
this._getQueriesFor(token).forEach((query) => {
|
2017-02-02 18:01:35 -05:00
|
|
|
const queryValue = query.meta.read || defaultValue;
|
|
|
|
const tokenRef = tokenReference(queryValue);
|
|
|
|
let queryMatches = queryReadTokens.get(tokenRef);
|
|
|
|
if (!queryMatches) {
|
|
|
|
queryMatches = [];
|
|
|
|
queryReadTokens.set(tokenRef, queryMatches);
|
2016-04-18 16:24:42 -04:00
|
|
|
}
|
2017-02-15 11:36:49 -05:00
|
|
|
queryMatches.push({queryId: query.queryId, value: queryValue});
|
2016-04-18 16:24:42 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-02 18:01:35 -05:00
|
|
|
private _getQueriesFor(token: CompileTokenMetadata): QueryWithId[] {
|
|
|
|
const result: QueryWithId[] = [];
|
2016-11-12 08:08:58 -05:00
|
|
|
let currentEl: ProviderElementContext = this;
|
|
|
|
let distance = 0;
|
2017-03-24 12:59:58 -04:00
|
|
|
let queries: QueryWithId[]|undefined;
|
2016-01-06 17:13:44 -05:00
|
|
|
while (currentEl !== null) {
|
2016-11-23 12:42:19 -05:00
|
|
|
queries = currentEl._contentQueries.get(tokenReference(token));
|
2017-01-04 16:59:43 -05:00
|
|
|
if (queries) {
|
2017-02-02 18:01:35 -05:00
|
|
|
result.push(...queries.filter((query) => query.meta.descendants || distance <= 1));
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
if (currentEl._directiveAsts.length > 0) {
|
|
|
|
distance++;
|
|
|
|
}
|
|
|
|
currentEl = currentEl._parent;
|
|
|
|
}
|
2016-11-23 12:42:19 -05:00
|
|
|
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
2017-01-04 16:59:43 -05:00
|
|
|
if (queries) {
|
2016-10-21 18:14:44 -04:00
|
|
|
result.push(...queries);
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
2016-04-18 16:24:42 -04:00
|
|
|
return result;
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
private _getOrCreateLocalProvider(
|
|
|
|
requestingProviderType: ProviderAstType, token: CompileTokenMetadata,
|
2017-03-24 12:59:58 -04:00
|
|
|
eager: boolean): ProviderAst|null {
|
2016-11-23 12:42:19 -05:00
|
|
|
const resolvedProvider = this._allProviders.get(tokenReference(token));
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!resolvedProvider || ((requestingProviderType === ProviderAstType.Directive ||
|
|
|
|
requestingProviderType === ProviderAstType.PublicService) &&
|
|
|
|
resolvedProvider.providerType === ProviderAstType.PrivateService) ||
|
2016-01-06 17:13:44 -05:00
|
|
|
((requestingProviderType === ProviderAstType.PrivateService ||
|
|
|
|
requestingProviderType === ProviderAstType.PublicService) &&
|
|
|
|
resolvedProvider.providerType === ProviderAstType.Builtin)) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-11-23 12:42:19 -05:00
|
|
|
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
2017-01-04 16:59:43 -05:00
|
|
|
if (transformedProviderAst) {
|
2016-01-06 17:13:44 -05:00
|
|
|
return transformedProviderAst;
|
|
|
|
}
|
2017-03-02 12:37:01 -05:00
|
|
|
if (this._seenProviders.get(tokenReference(token)) != null) {
|
2016-09-23 16:37:04 -04:00
|
|
|
this.viewContext.errors.push(new ProviderError(
|
2016-11-23 12:42:19 -05:00
|
|
|
`Cannot instantiate cyclic dependency! ${tokenName(token)}`, this._sourceSpan));
|
2016-01-06 17:13:44 -05:00
|
|
|
return null;
|
|
|
|
}
|
2016-11-23 12:42:19 -05:00
|
|
|
this._seenProviders.set(tokenReference(token), true);
|
2016-11-12 08:08:58 -05:00
|
|
|
const transformedProviders = resolvedProvider.providers.map((provider) => {
|
|
|
|
let transformedUseValue = provider.useValue;
|
2017-03-24 12:59:58 -04:00
|
|
|
let transformedUseExisting = provider.useExisting !;
|
|
|
|
let transformedDeps: CompileDiDependencyMetadata[] = undefined !;
|
2017-03-02 12:37:01 -05:00
|
|
|
if (provider.useExisting != null) {
|
2016-11-12 08:08:58 -05:00
|
|
|
const existingDiDep = this._getDependency(
|
2017-03-24 12:59:58 -04:00
|
|
|
resolvedProvider.providerType, {token: provider.useExisting}, eager) !;
|
2017-03-02 12:37:01 -05:00
|
|
|
if (existingDiDep.token != null) {
|
2016-01-06 17:13:44 -05:00
|
|
|
transformedUseExisting = existingDiDep.token;
|
|
|
|
} else {
|
2017-03-24 12:59:58 -04:00
|
|
|
transformedUseExisting = null !;
|
2016-01-06 17:13:44 -05:00
|
|
|
transformedUseValue = existingDiDep.value;
|
|
|
|
}
|
2017-01-04 16:59:43 -05:00
|
|
|
} else if (provider.useFactory) {
|
2016-11-12 08:08:58 -05:00
|
|
|
const deps = provider.deps || provider.useFactory.diDeps;
|
2016-01-06 17:13:44 -05:00
|
|
|
transformedDeps =
|
2017-03-24 12:59:58 -04:00
|
|
|
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager) !);
|
2017-01-04 16:59:43 -05:00
|
|
|
} else if (provider.useClass) {
|
2016-11-12 08:08:58 -05:00
|
|
|
const deps = provider.deps || provider.useClass.diDeps;
|
2016-01-06 17:13:44 -05:00
|
|
|
transformedDeps =
|
2017-03-24 12:59:58 -04:00
|
|
|
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager) !);
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
return _transformProvider(provider, {
|
|
|
|
useExisting: transformedUseExisting,
|
|
|
|
useValue: transformedUseValue,
|
|
|
|
deps: transformedDeps
|
|
|
|
});
|
|
|
|
});
|
|
|
|
transformedProviderAst =
|
|
|
|
_transformProviderAst(resolvedProvider, {eager: eager, providers: transformedProviders});
|
2016-11-23 12:42:19 -05:00
|
|
|
this._transformedProviders.set(tokenReference(token), transformedProviderAst);
|
2016-01-06 17:13:44 -05:00
|
|
|
return transformedProviderAst;
|
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
private _getLocalDependency(
|
|
|
|
requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata,
|
2017-03-24 12:59:58 -04:00
|
|
|
eager: boolean = false): CompileDiDependencyMetadata|null {
|
2016-01-06 17:13:44 -05:00
|
|
|
if (dep.isAttribute) {
|
2017-03-24 12:59:58 -04:00
|
|
|
const attrValue = this._attrs[dep.token !.value];
|
2016-11-30 13:52:51 -05:00
|
|
|
return {isValue: true, value: attrValue == null ? null : attrValue};
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
|
2017-03-02 12:37:01 -05:00
|
|
|
if (dep.token != null) {
|
2016-01-06 17:13:44 -05:00
|
|
|
// access builtints
|
|
|
|
if ((requestingProviderType === ProviderAstType.Directive ||
|
|
|
|
requestingProviderType === ProviderAstType.Component)) {
|
2017-05-18 16:46:51 -04:00
|
|
|
if (tokenReference(dep.token) ===
|
|
|
|
this.viewContext.reflector.resolveExternalReference(Identifiers.Renderer) ||
|
|
|
|
tokenReference(dep.token) ===
|
|
|
|
this.viewContext.reflector.resolveExternalReference(Identifiers.ElementRef) ||
|
|
|
|
tokenReference(dep.token) ===
|
|
|
|
this.viewContext.reflector.resolveExternalReference(
|
|
|
|
Identifiers.ChangeDetectorRef) ||
|
|
|
|
tokenReference(dep.token) ===
|
|
|
|
this.viewContext.reflector.resolveExternalReference(Identifiers.TemplateRef)) {
|
2016-01-06 17:13:44 -05:00
|
|
|
return dep;
|
|
|
|
}
|
2017-05-18 16:46:51 -04:00
|
|
|
if (tokenReference(dep.token) ===
|
|
|
|
this.viewContext.reflector.resolveExternalReference(Identifiers.ViewContainerRef)) {
|
2017-09-11 18:10:19 -04:00
|
|
|
(this as{transformedHasViewContainer: boolean}).transformedHasViewContainer = true;
|
2016-04-18 16:24:42 -04:00
|
|
|
}
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
// access the injector
|
2017-05-18 16:46:51 -04:00
|
|
|
if (tokenReference(dep.token) ===
|
|
|
|
this.viewContext.reflector.resolveExternalReference(Identifiers.Injector)) {
|
2016-01-06 17:13:44 -05:00
|
|
|
return dep;
|
|
|
|
}
|
|
|
|
// access providers
|
2017-03-02 12:37:01 -05:00
|
|
|
if (this._getOrCreateLocalProvider(requestingProviderType, dep.token, eager) != null) {
|
2016-01-06 17:13:44 -05:00
|
|
|
return dep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
private _getDependency(
|
|
|
|
requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata,
|
2017-03-24 12:59:58 -04:00
|
|
|
eager: boolean = false): CompileDiDependencyMetadata|null {
|
2016-11-12 08:08:58 -05:00
|
|
|
let currElement: ProviderElementContext = this;
|
|
|
|
let currEager: boolean = eager;
|
2017-03-24 12:59:58 -04:00
|
|
|
let result: CompileDiDependencyMetadata|null = null;
|
2016-01-06 17:13:44 -05:00
|
|
|
if (!dep.isSkipSelf) {
|
|
|
|
result = this._getLocalDependency(requestingProviderType, dep, eager);
|
|
|
|
}
|
|
|
|
if (dep.isSelf) {
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!result && dep.isOptional) {
|
2016-11-30 13:52:51 -05:00
|
|
|
result = {isValue: true, value: null};
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// check parent elements
|
2017-01-04 16:59:43 -05:00
|
|
|
while (!result && currElement._parent) {
|
2016-11-12 08:08:58 -05:00
|
|
|
const prevElement = currElement;
|
2016-01-06 17:13:44 -05:00
|
|
|
currElement = currElement._parent;
|
|
|
|
if (prevElement._isViewRoot) {
|
|
|
|
currEager = false;
|
|
|
|
}
|
|
|
|
result = currElement._getLocalDependency(ProviderAstType.PublicService, dep, currEager);
|
|
|
|
}
|
|
|
|
// check @Host restriction
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!result) {
|
2016-11-23 12:42:19 -05:00
|
|
|
if (!dep.isHost || this.viewContext.component.isHost ||
|
2017-03-24 12:59:58 -04:00
|
|
|
this.viewContext.component.type.reference === tokenReference(dep.token !) ||
|
|
|
|
this.viewContext.viewProviders.get(tokenReference(dep.token !)) != null) {
|
2016-01-06 17:13:44 -05:00
|
|
|
result = dep;
|
|
|
|
} else {
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
result = dep.isOptional ? {isValue: true, value: null} : null;
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!result) {
|
2016-09-23 16:37:04 -04:00
|
|
|
this.viewContext.errors.push(
|
2017-03-24 12:59:58 -04:00
|
|
|
new ProviderError(`No provider for ${tokenName(dep.token!)}`, this._sourceSpan));
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-28 12:54:42 -04:00
|
|
|
|
2016-07-18 06:50:31 -04:00
|
|
|
export class NgModuleProviderAnalyzer {
|
2016-08-29 11:52:25 -04:00
|
|
|
private _transformedProviders = new Map<any, ProviderAst>();
|
|
|
|
private _seenProviders = new Map<any, boolean>();
|
|
|
|
private _allProviders: Map<any, ProviderAst>;
|
2016-06-28 12:54:42 -04:00
|
|
|
private _errors: ProviderError[] = [];
|
|
|
|
|
2016-07-18 06:50:31 -04:00
|
|
|
constructor(
|
2017-05-18 16:46:51 -04:00
|
|
|
private reflector: CompileReflector, ngModule: CompileNgModuleMetadata,
|
|
|
|
extraProviders: CompileProviderMetadata[], sourceSpan: ParseSourceSpan) {
|
2016-08-29 11:52:25 -04:00
|
|
|
this._allProviders = new Map<any, ProviderAst>();
|
2016-11-29 11:08:22 -05:00
|
|
|
ngModule.transitiveModule.modules.forEach((ngModuleType: CompileTypeMetadata) => {
|
2016-11-30 13:52:51 -05:00
|
|
|
const ngModuleProvider = {token: {identifier: ngModuleType}, useClass: ngModuleType};
|
2016-06-28 12:54:42 -04:00
|
|
|
_resolveProviders(
|
2016-07-18 06:50:31 -04:00
|
|
|
[ngModuleProvider], ProviderAstType.PublicService, true, sourceSpan, this._errors,
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
this._allProviders, /* isModule */ true);
|
2016-06-28 12:54:42 -04:00
|
|
|
});
|
|
|
|
_resolveProviders(
|
2016-11-29 11:08:22 -05:00
|
|
|
ngModule.transitiveModule.providers.map(entry => entry.provider).concat(extraProviders),
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
ProviderAstType.PublicService, false, sourceSpan, this._errors, this._allProviders,
|
|
|
|
/* isModule */ false);
|
2016-06-28 12:54:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
parse(): ProviderAst[] {
|
2016-11-03 19:58:27 -04:00
|
|
|
Array.from(this._allProviders.values()).forEach((provider) => {
|
2016-08-29 11:52:25 -04:00
|
|
|
this._getOrCreateLocalProvider(provider.token, provider.eager);
|
|
|
|
});
|
2016-06-28 12:54:42 -04:00
|
|
|
if (this._errors.length > 0) {
|
2016-07-13 14:01:32 -04:00
|
|
|
const errorString = this._errors.join('\n');
|
2016-08-25 03:50:16 -04:00
|
|
|
throw new Error(`Provider parse errors:\n${errorString}`);
|
2016-06-28 12:54:42 -04:00
|
|
|
}
|
2017-05-11 13:26:02 -04:00
|
|
|
// Note: Maps keep their insertion order.
|
|
|
|
const lazyProviders: ProviderAst[] = [];
|
|
|
|
const eagerProviders: ProviderAst[] = [];
|
|
|
|
this._transformedProviders.forEach(provider => {
|
|
|
|
if (provider.eager) {
|
|
|
|
eagerProviders.push(provider);
|
|
|
|
} else {
|
|
|
|
lazyProviders.push(provider);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return lazyProviders.concat(eagerProviders);
|
2016-06-28 12:54:42 -04:00
|
|
|
}
|
|
|
|
|
2017-03-24 12:59:58 -04:00
|
|
|
private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst|null {
|
2016-11-23 12:42:19 -05:00
|
|
|
const resolvedProvider = this._allProviders.get(tokenReference(token));
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!resolvedProvider) {
|
2016-06-28 12:54:42 -04:00
|
|
|
return null;
|
|
|
|
}
|
2016-11-23 12:42:19 -05:00
|
|
|
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
2017-01-04 16:59:43 -05:00
|
|
|
if (transformedProviderAst) {
|
2016-06-28 12:54:42 -04:00
|
|
|
return transformedProviderAst;
|
|
|
|
}
|
2017-03-02 12:37:01 -05:00
|
|
|
if (this._seenProviders.get(tokenReference(token)) != null) {
|
2016-06-28 12:54:42 -04:00
|
|
|
this._errors.push(new ProviderError(
|
2016-11-23 12:42:19 -05:00
|
|
|
`Cannot instantiate cyclic dependency! ${tokenName(token)}`,
|
|
|
|
resolvedProvider.sourceSpan));
|
2016-06-28 12:54:42 -04:00
|
|
|
return null;
|
|
|
|
}
|
2016-11-23 12:42:19 -05:00
|
|
|
this._seenProviders.set(tokenReference(token), true);
|
2016-11-12 08:08:58 -05:00
|
|
|
const transformedProviders = resolvedProvider.providers.map((provider) => {
|
|
|
|
let transformedUseValue = provider.useValue;
|
2017-03-24 12:59:58 -04:00
|
|
|
let transformedUseExisting = provider.useExisting !;
|
|
|
|
let transformedDeps: CompileDiDependencyMetadata[] = undefined !;
|
2017-03-02 12:37:01 -05:00
|
|
|
if (provider.useExisting != null) {
|
2016-11-30 13:52:51 -05:00
|
|
|
const existingDiDep =
|
|
|
|
this._getDependency({token: provider.useExisting}, eager, resolvedProvider.sourceSpan);
|
2017-03-02 12:37:01 -05:00
|
|
|
if (existingDiDep.token != null) {
|
2016-06-28 12:54:42 -04:00
|
|
|
transformedUseExisting = existingDiDep.token;
|
|
|
|
} else {
|
2017-03-24 12:59:58 -04:00
|
|
|
transformedUseExisting = null !;
|
2016-06-28 12:54:42 -04:00
|
|
|
transformedUseValue = existingDiDep.value;
|
|
|
|
}
|
2017-01-04 16:59:43 -05:00
|
|
|
} else if (provider.useFactory) {
|
2016-11-12 08:08:58 -05:00
|
|
|
const deps = provider.deps || provider.useFactory.diDeps;
|
2016-06-28 12:54:42 -04:00
|
|
|
transformedDeps =
|
|
|
|
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
2017-01-04 16:59:43 -05:00
|
|
|
} else if (provider.useClass) {
|
2016-11-12 08:08:58 -05:00
|
|
|
const deps = provider.deps || provider.useClass.diDeps;
|
2016-06-28 12:54:42 -04:00
|
|
|
transformedDeps =
|
|
|
|
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
|
|
|
}
|
|
|
|
return _transformProvider(provider, {
|
|
|
|
useExisting: transformedUseExisting,
|
|
|
|
useValue: transformedUseValue,
|
|
|
|
deps: transformedDeps
|
|
|
|
});
|
|
|
|
});
|
|
|
|
transformedProviderAst =
|
|
|
|
_transformProviderAst(resolvedProvider, {eager: eager, providers: transformedProviders});
|
2016-11-23 12:42:19 -05:00
|
|
|
this._transformedProviders.set(tokenReference(token), transformedProviderAst);
|
2016-06-28 12:54:42 -04:00
|
|
|
return transformedProviderAst;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getDependency(
|
2017-03-24 12:59:58 -04:00
|
|
|
dep: CompileDiDependencyMetadata, eager: boolean = false,
|
2016-06-28 12:54:42 -04:00
|
|
|
requestorSourceSpan: ParseSourceSpan): CompileDiDependencyMetadata {
|
2016-11-12 08:08:58 -05:00
|
|
|
let foundLocal = false;
|
2017-03-02 12:37:01 -05:00
|
|
|
if (!dep.isSkipSelf && dep.token != null) {
|
2016-06-28 12:54:42 -04:00
|
|
|
// access the injector
|
2017-05-18 16:46:51 -04:00
|
|
|
if (tokenReference(dep.token) ===
|
|
|
|
this.reflector.resolveExternalReference(Identifiers.Injector) ||
|
|
|
|
tokenReference(dep.token) ===
|
|
|
|
this.reflector.resolveExternalReference(Identifiers.ComponentFactoryResolver)) {
|
2016-06-28 12:54:42 -04:00
|
|
|
foundLocal = true;
|
|
|
|
// access providers
|
2017-03-02 12:37:01 -05:00
|
|
|
} else if (this._getOrCreateLocalProvider(dep.token, eager) != null) {
|
2016-06-28 12:54:42 -04:00
|
|
|
foundLocal = true;
|
|
|
|
}
|
|
|
|
}
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
return dep;
|
2016-06-28 12:54:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-06 17:13:44 -05:00
|
|
|
function _transformProvider(
|
|
|
|
provider: CompileProviderMetadata,
|
|
|
|
{useExisting, useValue, deps}:
|
|
|
|
{useExisting: CompileTokenMetadata, useValue: any, deps: CompileDiDependencyMetadata[]}) {
|
2016-11-30 13:52:51 -05:00
|
|
|
return {
|
2016-01-06 17:13:44 -05:00
|
|
|
token: provider.token,
|
|
|
|
useClass: provider.useClass,
|
|
|
|
useExisting: useExisting,
|
|
|
|
useFactory: provider.useFactory,
|
|
|
|
useValue: useValue,
|
|
|
|
deps: deps,
|
|
|
|
multi: provider.multi
|
2016-11-30 13:52:51 -05:00
|
|
|
};
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function _transformProviderAst(
|
|
|
|
provider: ProviderAst,
|
|
|
|
{eager, providers}: {eager: boolean, providers: CompileProviderMetadata[]}): ProviderAst {
|
2016-06-08 19:38:52 -04:00
|
|
|
return new ProviderAst(
|
|
|
|
provider.token, provider.multiProvider, provider.eager || eager, providers,
|
2018-02-02 12:31:58 -05:00
|
|
|
provider.providerType, provider.lifecycleHooks, provider.sourceSpan, provider.isModule);
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
function _resolveProvidersFromDirectives(
|
2016-11-10 19:27:53 -05:00
|
|
|
directives: CompileDirectiveSummary[], sourceSpan: ParseSourceSpan,
|
2016-08-29 11:52:25 -04:00
|
|
|
targetErrors: ParseError[]): Map<any, ProviderAst> {
|
2016-11-12 08:08:58 -05:00
|
|
|
const providersByToken = new Map<any, ProviderAst>();
|
2016-01-06 17:13:44 -05:00
|
|
|
directives.forEach((directive) => {
|
2016-11-30 13:52:51 -05:00
|
|
|
const dirProvider:
|
|
|
|
CompileProviderMetadata = {token: {identifier: directive.type}, useClass: directive.type};
|
2016-06-08 19:38:52 -04:00
|
|
|
_resolveProviders(
|
|
|
|
[dirProvider],
|
|
|
|
directive.isComponent ? ProviderAstType.Component : ProviderAstType.Directive, true,
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
sourceSpan, targetErrors, providersByToken, /* isModule */ false);
|
2016-01-06 17:13:44 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// Note: directives need to be able to overwrite providers of a component!
|
2016-11-12 08:08:58 -05:00
|
|
|
const directivesWithComponentFirst =
|
2016-01-06 17:13:44 -05:00
|
|
|
directives.filter(dir => dir.isComponent).concat(directives.filter(dir => !dir.isComponent));
|
|
|
|
directivesWithComponentFirst.forEach((directive) => {
|
2016-06-08 19:38:52 -04:00
|
|
|
_resolveProviders(
|
2016-11-30 13:52:51 -05:00
|
|
|
directive.providers, ProviderAstType.PublicService, false, sourceSpan, targetErrors,
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
providersByToken, /* isModule */ false);
|
2016-06-08 19:38:52 -04:00
|
|
|
_resolveProviders(
|
2016-11-30 13:52:51 -05:00
|
|
|
directive.viewProviders, ProviderAstType.PrivateService, false, sourceSpan, targetErrors,
|
feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.
Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".
Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.
Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.
Additionally, this commit adds several unit and integration tests of various
flavors to test this change.
PR Close #22005
2018-02-02 13:33:48 -05:00
|
|
|
providersByToken, /* isModule */ false);
|
2016-01-06 17:13:44 -05:00
|
|
|
});
|
|
|
|
return providersByToken;
|
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
function _resolveProviders(
|
|
|
|
providers: CompileProviderMetadata[], providerType: ProviderAstType, eager: boolean,
|
|
|
|
sourceSpan: ParseSourceSpan, targetErrors: ParseError[],
|
2018-02-02 12:31:58 -05:00
|
|
|
targetProvidersByToken: Map<any, ProviderAst>, isModule: boolean) {
|
2016-01-06 17:13:44 -05:00
|
|
|
providers.forEach((provider) => {
|
2016-11-23 12:42:19 -05:00
|
|
|
let resolvedProvider = targetProvidersByToken.get(tokenReference(provider.token));
|
2017-03-02 12:37:01 -05:00
|
|
|
if (resolvedProvider != null && !!resolvedProvider.multiProvider !== !!provider.multi) {
|
2016-01-06 17:13:44 -05:00
|
|
|
targetErrors.push(new ProviderError(
|
2016-11-23 12:42:19 -05:00
|
|
|
`Mixing multi and non multi provider is not possible for token ${tokenName(resolvedProvider.token)}`,
|
2016-01-06 17:13:44 -05:00
|
|
|
sourceSpan));
|
|
|
|
}
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!resolvedProvider) {
|
2016-11-30 13:52:51 -05:00
|
|
|
const lifecycleHooks = provider.token.identifier &&
|
|
|
|
(<CompileTypeMetadata>provider.token.identifier).lifecycleHooks ?
|
|
|
|
(<CompileTypeMetadata>provider.token.identifier).lifecycleHooks :
|
2016-08-02 04:37:42 -04:00
|
|
|
[];
|
2017-03-14 17:32:26 -04:00
|
|
|
const isUseValue = !(provider.useClass || provider.useExisting || provider.useFactory);
|
2016-06-08 19:38:52 -04:00
|
|
|
resolvedProvider = new ProviderAst(
|
2017-03-24 12:59:58 -04:00
|
|
|
provider.token, !!provider.multi, eager || isUseValue, [provider], providerType,
|
2018-02-02 12:31:58 -05:00
|
|
|
lifecycleHooks, sourceSpan, isModule);
|
2016-11-23 12:42:19 -05:00
|
|
|
targetProvidersByToken.set(tokenReference(provider.token), resolvedProvider);
|
2016-01-06 17:13:44 -05:00
|
|
|
} else {
|
|
|
|
if (!provider.multi) {
|
2016-10-21 18:14:44 -04:00
|
|
|
resolvedProvider.providers.length = 0;
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
resolvedProvider.providers.push(provider);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-02-02 18:01:35 -05:00
|
|
|
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, QueryWithId[]> {
|
2017-02-15 11:36:49 -05:00
|
|
|
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
|
|
|
let viewQueryId = 1;
|
2017-02-02 18:01:35 -05:00
|
|
|
const viewQueries = new Map<any, QueryWithId[]>();
|
2017-01-04 16:59:43 -05:00
|
|
|
if (component.viewQueries) {
|
2017-02-02 18:01:35 -05:00
|
|
|
component.viewQueries.forEach(
|
2017-02-15 11:36:49 -05:00
|
|
|
(query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++}));
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
return viewQueries;
|
|
|
|
}
|
|
|
|
|
2017-02-02 18:01:35 -05:00
|
|
|
function _getContentQueries(
|
2017-02-15 11:36:49 -05:00
|
|
|
contentQueryStartId: number, directives: CompileDirectiveSummary[]): Map<any, QueryWithId[]> {
|
|
|
|
let contentQueryId = contentQueryStartId;
|
2017-02-02 18:01:35 -05:00
|
|
|
const contentQueries = new Map<any, QueryWithId[]>();
|
|
|
|
directives.forEach((directive, directiveIndex) => {
|
2017-01-04 16:59:43 -05:00
|
|
|
if (directive.queries) {
|
2017-02-02 18:01:35 -05:00
|
|
|
directive.queries.forEach(
|
2017-02-15 11:36:49 -05:00
|
|
|
(query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++}));
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return contentQueries;
|
|
|
|
}
|
|
|
|
|
2017-02-02 18:01:35 -05:00
|
|
|
function _addQueryToTokenMap(map: Map<any, QueryWithId[]>, query: QueryWithId) {
|
|
|
|
query.meta.selectors.forEach((token: CompileTokenMetadata) => {
|
2016-11-23 12:42:19 -05:00
|
|
|
let entry = map.get(tokenReference(token));
|
2016-09-30 12:26:53 -04:00
|
|
|
if (!entry) {
|
2016-01-06 17:13:44 -05:00
|
|
|
entry = [];
|
2016-11-23 12:42:19 -05:00
|
|
|
map.set(tokenReference(token), entry);
|
2016-01-06 17:13:44 -05:00
|
|
|
}
|
|
|
|
entry.push(query);
|
|
|
|
});
|
|
|
|
}
|