refactor: use view engine also for NgModuleFactorys (#16658)

* refactor(core): provide error message in stack for reflective DI

Fixes #16355

* fix(compiler): make AOT work with `noUnusedParameters`

Fixes #15532

* refactor: use view engine also for `NgModuleFactory`s

This is a prerequisite for being able to mock providers
in AOTed code later on.
This commit is contained in:
Tobias Bosch
2017-05-11 10:26:02 -07:00
committed by Jason Aden
parent b9521b568f
commit ce1d7c4a6e
25 changed files with 800 additions and 604 deletions

View File

@ -10,7 +10,7 @@ import {RendererType2} from '../render/api';
import {SecurityContext} from '../security';
import {BindingDef, BindingFlags, ElementData, ElementHandleEventFn, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, ViewData, ViewDefinitionFactory, asElementData} from './types';
import {NOOP, calcBindingFlags, checkAndUpdateBinding, dispatchEvent, elementEventFullName, getParentRenderElement, resolveRendererType2, resolveViewDefinition, splitMatchedQueriesDsl, splitNamespace} from './util';
import {NOOP, calcBindingFlags, checkAndUpdateBinding, dispatchEvent, elementEventFullName, getParentRenderElement, resolveDefinition, resolveRendererType2, splitMatchedQueriesDsl, splitNamespace} from './util';
export function anchorDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
@ -18,7 +18,7 @@ export function anchorDef(
templateFactory?: ViewDefinitionFactory): NodeDef {
flags |= NodeFlags.TypeElement;
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
const template = templateFactory ? resolveDefinition(templateFactory) : null;
return {
// will bet set by the view definition

View File

@ -8,13 +8,15 @@
export {anchorDef, elementDef} from './element';
export {ngContentDef} from './ng_content';
export {moduleDef, moduleProvideDef} from './ng_module';
export {directiveDef, pipeDef, providerDef} from './provider';
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
export {queryDef} from './query';
export {ViewRef_, createComponentFactory, getComponentViewDefinitionFactory, nodeValue} from './refs';
export {createNgModuleFactory} from './refs';
export {initServicesIfNeeded} from './services';
export {textDef} from './text';
export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, unwrapValue} from './util';
export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, tokenKey, unwrapValue} from './util';
export {viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';

View File

@ -0,0 +1,184 @@
/**
* @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
*/
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {NgModuleRef} from '../linker/ng_module_factory';
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NgModuleProviderDef, NodeFlags} from './types';
import {tokenKey} from './util';
const NOT_CREATED = new Object();
const InjectorRefTokenKey = tokenKey(Injector);
const NgModuleRefTokenKey = tokenKey(NgModuleRef);
export function moduleProvideDef(
flags: NodeFlags, token: any, value: any,
deps: ([DepFlags, any] | any)[]): NgModuleProviderDef {
const depDefs: DepDef[] = deps.map(value => {
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
return {
// will bet set by the module definition
index: -1,
deps: depDefs, flags, token, value
};
}
export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {};
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
provider.index = i;
providersByKey[tokenKey(provider.token)] = provider;
}
return {
// Will be filled later...
factory: null,
providersByKey,
providers
};
}
export function initNgModule(data: NgModuleData) {
const def = data._def;
const providers = data._providers = new Array(def.providers.length);
for (let i = 0; i < def.providers.length; i++) {
const provDef = def.providers[i];
providers[i] = provDef.flags & NodeFlags.LazyProvider ? NOT_CREATED :
_createProviderInstance(data, provDef);
}
}
export function resolveNgModuleDep(
data: NgModuleData, depDef: DepDef, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
if (depDef.flags & DepFlags.Value) {
return depDef.token;
}
if (depDef.flags & DepFlags.Optional) {
notFoundValue = null;
}
if (depDef.flags & DepFlags.SkipSelf) {
return data._parent.get(depDef.token, notFoundValue);
}
const tokenKey = depDef.tokenKey;
switch (tokenKey) {
case InjectorRefTokenKey:
case NgModuleRefTokenKey:
return data;
}
const providerDef = data._def.providersByKey[tokenKey];
if (providerDef) {
let providerInstance = data._providers[providerDef.index];
if (providerInstance === NOT_CREATED) {
providerInstance = data._providers[providerDef.index] =
_createProviderInstance(data, providerDef);
}
return providerInstance;
}
return data._parent.get(depDef.token, notFoundValue);
}
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any {
let injectable: any;
switch (providerDef.flags & NodeFlags.Types) {
case NodeFlags.TypeClassProvider:
injectable = _createClass(ngModule, providerDef !.value, providerDef !.deps);
break;
case NodeFlags.TypeFactoryProvider:
injectable = _callFactory(ngModule, providerDef !.value, providerDef !.deps);
break;
case NodeFlags.TypeUseExistingProvider:
injectable = resolveNgModuleDep(ngModule, providerDef !.deps[0]);
break;
case NodeFlags.TypeValueProvider:
injectable = providerDef !.value;
break;
}
return injectable;
}
function _createClass(ngModule: NgModuleData, ctor: any, deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = new ctor();
break;
case 1:
injectable = new ctor(resolveNgModuleDep(ngModule, deps[0]));
break;
case 2:
injectable =
new ctor(resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]));
break;
case 3:
injectable = new ctor(
resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]),
resolveNgModuleDep(ngModule, deps[2]));
break;
default:
const depValues = new Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveNgModuleDep(ngModule, deps[i]);
}
injectable = new ctor(...depValues);
}
return injectable;
}
function _callFactory(ngModule: NgModuleData, factory: any, deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = factory();
break;
case 1:
injectable = factory(resolveNgModuleDep(ngModule, deps[0]));
break;
case 2:
injectable =
factory(resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]));
break;
case 3:
injectable = factory(
resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]),
resolveNgModuleDep(ngModule, deps[2]));
break;
default:
const depValues = Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveNgModuleDep(ngModule, deps[i]);
}
injectable = factory(...depValues);
}
return injectable;
}
export function callNgModuleLifecycle(ngModule: NgModuleData, lifecycles: NodeFlags) {
const def = ngModule._def;
for (let i = 0; i < def.providers.length; i++) {
const provDef = def.providers[i];
if (provDef.flags & NodeFlags.OnDestroy) {
const instance = ngModule._providers[i];
if (instance && instance !== NOT_CREATED) {
instance.ngOnDestroy();
}
}
}
}

View File

@ -105,7 +105,7 @@ export function _def(
ngContentIndex: -1, childCount, bindings,
bindingFlags: calcBindingFlags(bindings), outputs,
element: null,
provider: {token, tokenKey: tokenKey(token), value, deps: depDefs},
provider: {token, value, deps: depDefs},
text: null,
query: null,
ngContent: null

View File

@ -8,20 +8,22 @@
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di';
import {Injector} from '../di/injector';
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
import {ComponentFactoryBoundToModule} from '../linker/component_factory_resolver';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {ElementRef} from '../linker/element_ref';
import {NgModuleRef} from '../linker/ng_module_factory';
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref';
import {Renderer as RendererV1, Renderer2} from '../render/api';
import {Type} from '../type';
import {stringify} from '../util';
import {VERSION} from '../version';
import {DepFlags, ElementData, NodeDef, NodeFlags, Services, TemplateData, ViewContainerData, ViewData, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {markParentViewsForCheck, resolveViewDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
import {callNgModuleLifecycle, initNgModule, resolveNgModuleDep} from './ng_module';
import {DepFlags, ElementData, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NodeDef, NodeFlags, Services, TemplateData, ViewContainerData, ViewData, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {markParentViewsForCheck, resolveDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachView} from './view_attach';
const EMPTY_CONTEXT = new Object();
@ -41,6 +43,14 @@ export function getComponentViewDefinitionFactory(componentFactory: ComponentFac
return (componentFactory as ComponentFactory_).viewDefFactory;
}
// Attention: this function is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
export function createNgModuleFactory(
ngModuleType: Type<any>, bootstrapComponents: Type<any>[],
defFactory: NgModuleDefinitionFactory): NgModuleFactory<any> {
return new NgModuleFactory_(ngModuleType, bootstrapComponents, defFactory);
}
class ComponentFactory_ extends ComponentFactory<any> {
/**
* @internal
@ -85,7 +95,7 @@ class ComponentFactory_ extends ComponentFactory<any> {
if (!ngModule) {
throw new Error('ngModule should be provided');
}
const viewDef = resolveViewDefinition(this.viewDefFactory);
const viewDef = resolveDefinition(this.viewDefFactory);
const componentNodeIndex = viewDef.nodes[0].element !.componentProvider !.index;
const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT);
@ -452,3 +462,57 @@ class RendererAdapter implements RendererV1 {
animate(): any { throw new Error('Renderer.animate is no longer supported!'); }
}
class NgModuleFactory_ extends NgModuleFactory<any> {
constructor(
private _moduleType: Type<any>, private _bootstrapComponents: Type<any>[],
private _ngModuleDefFactory: NgModuleDefinitionFactory, ) {
// Attention: this ctor is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
super();
}
get moduleType(): Type<any> { return this._moduleType; }
create(parentInjector: Injector|null): NgModuleRef<any> {
const def = resolveDefinition(this._ngModuleDefFactory);
return new NgModuleRef_(
this._moduleType, parentInjector || Injector.NULL, this._bootstrapComponents, def);
}
}
class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;
public _providers: any[];
constructor(
private _moduleType: any, public _parent: Injector, public _bootstrapComponents: Type<any>[],
public _def: NgModuleDefinition) {
initNgModule(this);
}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
return resolveNgModuleDep(
this, {token: token, tokenKey: tokenKey(token), flags: DepFlags.None}, notFoundValue);
}
get instance() { return this.get(this._moduleType); }
get componentFactoryResolver() { return this.get(ComponentFactoryResolver); }
get injector(): Injector { return this; }
destroy(): void {
if (this._destroyed) {
throw new Error(
`The ng module ${stringify(this.instance.constructor)} has already been destroyed.`);
}
this._destroyed = true;
callNgModuleLifecycle(this, NodeFlags.OnDestroy);
this._destroyListeners.forEach((listener) => listener());
}
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
}

View File

@ -19,8 +19,32 @@ import {Sanitizer, SecurityContext} from '../security';
// Defs
// -------------------------------------
export interface ViewDefinition {
factory: ViewDefinitionFactory|null;
/**
* Factory for ViewDefinitions/NgModuleDefinitions.
* We use a function so we can reexeute it in case an error happens and use the given logger
* function to log the error from the definition of the node, which is shown in all browser
* logs.
*/
export interface DefinitionFactory<D extends Definition<any>> { (logger: NodeLogger): D; }
/**
* Function to call console.error at the right source location. This is an indirection
* via another function as browser will log the location that actually called
* `console.error`.
*/
export interface NodeLogger { (): () => void; }
export interface Definition<DF extends DefinitionFactory<any>> { factory: DF|null; }
export interface NgModuleDefinition extends Definition<NgModuleDefinitionFactory> {
providers: NgModuleProviderDef[];
providersByKey: {[tokenKey: string]: NgModuleProviderDef};
}
export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {}
;
export interface ViewDefinition extends Definition<ViewDefinitionFactory> {
flags: ViewFlags;
updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
@ -44,20 +68,8 @@ export interface ViewDefinition {
nodeMatchedQueries: number;
}
/**
* Factory for ViewDefinitions.
* We use a function so we can reexeute it in case an error happens and use the given logger
* function to log the error from the definition of the node, which is shown in all browser
* logs.
*/
export interface ViewDefinitionFactory { (logger: NodeLogger): ViewDefinition; }
export interface ViewDefinitionFactory extends DefinitionFactory<ViewDefinition> {}
/**
* Function to call console.error at the right source location. This is an indirection
* via another function as browser will log the location that actually called
* `console.error`.
*/
export interface NodeLogger { (): () => void; }
export interface ViewUpdateFn { (check: NodeCheckFn, view: ViewData): void; }
@ -245,7 +257,14 @@ export interface ElementHandleEventFn { (view: ViewData, eventName: string, even
export interface ProviderDef {
token: any;
tokenKey: string;
value: any;
deps: DepDef[];
}
export interface NgModuleProviderDef {
flags: NodeFlags;
index: number;
token: any;
value: any;
deps: DepDef[];
}
@ -296,6 +315,14 @@ export interface NgContentDef {
// Data
// -------------------------------------
export interface NgModuleData extends Injector, NgModuleRef<any> {
// Note: we are using the prefix _ as NgModuleData is an NgModuleRef and therefore directly
// exposed to the user.
_def: NgModuleDefinition;
_parent: Injector;
_providers: any[];
}
/**
* View instance data.
* Attention: Adding fields to this is performance sensitive!
@ -379,13 +406,20 @@ export interface ElementData {
template: TemplateData;
}
export interface ViewContainerData extends ViewContainerRef { _embeddedViews: ViewData[]; }
export interface ViewContainerData extends ViewContainerRef {
// Note: we are using the prefix _ as ViewContainerData is a ViewContainerRef and therefore
// directly
// exposed to the user.
_embeddedViews: ViewData[];
}
export interface TemplateData extends TemplateRef<any> {
// views that have been created from the template
// of this element,
// but inserted into the embeddedViews of another element.
// By default, this is undefined.
// Note: we are using the prefix _ as TemplateData is a TemplateRef and therefore directly
// exposed to the user.
_projectedViews: ViewData[];
}

View File

@ -12,7 +12,7 @@ import {RendererType2} from '../render/api';
import {looseIdentical, stringify} from '../util';
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {BindingDef, BindingFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
import {BindingDef, BindingFlags, Definition, DefinitionFactory, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
export const NOOP: any = () => {};
@ -220,14 +220,14 @@ export function getParentRenderElement(view: ViewData, renderHost: any, def: Nod
}
}
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
const DEFINITION_CACHE = new WeakMap<any, Definition<any>>();
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory) !;
export function resolveDefinition<D extends Definition<any>>(factory: DefinitionFactory<D>): D {
let value = DEFINITION_CACHE.get(factory) !as D;
if (!value) {
value = factory(() => NOOP);
value.factory = factory;
VIEW_DEFINITION_CACHE.set(factory, value);
DEFINITION_CACHE.set(factory, value);
}
return value;
}

View File

@ -17,7 +17,7 @@ import {checkAndUpdateQuery, createQuery} from './query';
import {createTemplateData, createViewContainerData} from './refs';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, CheckType, ElementData, NodeData, NodeDef, NodeFlags, ProviderData, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asQueryList, asTextData} from './types';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveViewDefinition} from './util';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveDefinition, tokenKey} from './util';
import {detachProjectedView} from './view_attach';
export function viewDef(
@ -102,7 +102,7 @@ export function viewDef(
const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0;
const isComponent = (node.flags & NodeFlags.Component) !== 0;
if (!isPrivateService || isComponent) {
currentParent !.element !.publicProviders ![node.provider !.tokenKey] = node;
currentParent !.element !.publicProviders ![tokenKey(node.provider !.token)] = node;
} else {
if (!currentElementHasPrivateProviders) {
currentElementHasPrivateProviders = true;
@ -110,7 +110,7 @@ export function viewDef(
currentParent !.element !.allProviders =
Object.create(currentParent !.element !.publicProviders);
}
currentParent !.element !.allProviders ![node.provider !.tokenKey] = node;
currentParent !.element !.allProviders ![tokenKey(node.provider !.token)] = node;
}
if (isComponent) {
currentParent !.element !.componentProvider = node;
@ -240,7 +240,7 @@ function createViewNodes(view: ViewData) {
const el = createElement(view, renderHost, nodeDef) as any;
let componentView: ViewData = undefined !;
if (nodeDef.flags & NodeFlags.ComponentView) {
const compViewDef = resolveViewDefinition(nodeDef.element !.componentView !);
const compViewDef = resolveDefinition(nodeDef.element !.componentView !);
const rendererType = nodeDef.element !.componentRendererType;
let compRenderer: Renderer2;
if (!rendererType) {