UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

451 lines (450 loc) 23.2 kB
import { isPresent, StringWrapper } from 'angular2/src/facade/lang'; import { ListWrapper, StringMapWrapper, SetWrapper } from 'angular2/src/facade/collection'; import * as o from '../output/output_ast'; import { Identifiers, identifierToken } from '../identifiers'; import { ViewConstructorVars, InjectMethodVars, DetectChangesVars, ViewTypeEnum, ViewEncapsulationEnum, ChangeDetectionStrategyEnum, ViewProperties } from './constants'; import { ChangeDetectionStrategy, isDefaultChangeDetectionStrategy } from 'angular2/src/core/change_detection/change_detection'; import { CompileView } from './compile_view'; import { CompileElement, CompileNode } from './compile_element'; import { templateVisitAll } from '../template_ast'; import { getViewFactoryName, createFlatArray, createDiTokenExpression } from './util'; import { ViewType } from 'angular2/src/core/linker/view_type'; import { ViewEncapsulation } from 'angular2/src/core/metadata/view'; import { CompileIdentifierMetadata } from '../compile_metadata'; const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const CLASS_ATTR = 'class'; const STYLE_ATTR = 'style'; var parentRenderNodeVar = o.variable('parentRenderNode'); var rootSelectorVar = o.variable('rootSelector'); export class ViewCompileDependency { constructor(comp, factoryPlaceholder) { this.comp = comp; this.factoryPlaceholder = factoryPlaceholder; } } export function buildView(view, template, targetDependencies) { var builderVisitor = new ViewBuilderVisitor(view, targetDependencies); templateVisitAll(builderVisitor, template, view.declarationElement.isNull() ? view.declarationElement : view.declarationElement.parent); return builderVisitor.nestedViewCount; } export function finishView(view, targetStatements) { view.afterNodes(); createViewTopLevelStmts(view, targetStatements); view.nodes.forEach((node) => { if (node instanceof CompileElement && node.hasEmbeddedView) { finishView(node.embeddedView, targetStatements); } }); } class ViewBuilderVisitor { constructor(view, targetDependencies) { this.view = view; this.targetDependencies = targetDependencies; this.nestedViewCount = 0; } _isRootNode(parent) { return parent.view !== this.view; } _addRootNodeAndProject(node, ngContentIndex, parent) { var vcAppEl = (node instanceof CompileElement && node.hasViewContainer) ? node.appElement : null; if (this._isRootNode(parent)) { // store appElement as root node only for ViewContainers if (this.view.viewType !== ViewType.COMPONENT) { this.view.rootNodesOrAppElements.push(isPresent(vcAppEl) ? vcAppEl : node.renderNode); } } else if (isPresent(parent.component) && isPresent(ngContentIndex)) { parent.addContentNode(ngContentIndex, isPresent(vcAppEl) ? vcAppEl : node.renderNode); } } _getParentRenderNode(parent) { if (this._isRootNode(parent)) { if (this.view.viewType === ViewType.COMPONENT) { return parentRenderNodeVar; } else { // root node of an embedded/host view return o.NULL_EXPR; } } else { return isPresent(parent.component) && parent.component.template.encapsulation !== ViewEncapsulation.Native ? o.NULL_EXPR : parent.renderNode; } } visitBoundText(ast, parent) { return this._visitText(ast, '', ast.ngContentIndex, parent); } visitText(ast, parent) { return this._visitText(ast, ast.value, ast.ngContentIndex, parent); } _visitText(ast, value, ngContentIndex, parent) { var fieldName = `_text_${this.view.nodes.length}`; this.view.fields.push(new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderText), [o.StmtModifier.Private])); var renderNode = o.THIS_EXPR.prop(fieldName); var compileNode = new CompileNode(parent, this.view, this.view.nodes.length, renderNode, ast); var createRenderNode = o.THIS_EXPR.prop(fieldName) .set(ViewProperties.renderer.callMethod('createText', [ this._getParentRenderNode(parent), o.literal(value), this.view.createMethod.resetDebugInfoExpr(this.view.nodes.length, ast) ])) .toStmt(); this.view.nodes.push(compileNode); this.view.createMethod.addStmt(createRenderNode); this._addRootNodeAndProject(compileNode, ngContentIndex, parent); return renderNode; } visitNgContent(ast, parent) { // the projected nodes originate from a different view, so we don't // have debug information for them... this.view.createMethod.resetDebugInfo(null, ast); var parentRenderNode = this._getParentRenderNode(parent); var nodesExpression = ViewProperties.projectableNodes.key(o.literal(ast.index), new o.ArrayType(o.importType(this.view.genConfig.renderTypes.renderNode))); if (parentRenderNode !== o.NULL_EXPR) { this.view.createMethod.addStmt(ViewProperties.renderer.callMethod('projectNodes', [ parentRenderNode, o.importExpr(Identifiers.flattenNestedViewRenderNodes) .callFn([nodesExpression]) ]) .toStmt()); } else if (this._isRootNode(parent)) { if (this.view.viewType !== ViewType.COMPONENT) { // store root nodes only for embedded/host views this.view.rootNodesOrAppElements.push(nodesExpression); } } else { if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) { parent.addContentNode(ast.ngContentIndex, nodesExpression); } } return null; } visitElement(ast, parent) { var nodeIndex = this.view.nodes.length; var createRenderNodeExpr; var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast); if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) { createRenderNodeExpr = o.THIS_EXPR.callMethod('selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]); } else { createRenderNodeExpr = ViewProperties.renderer.callMethod('createElement', [this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]); } var fieldName = `_el_${nodeIndex}`; this.view.fields.push(new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderElement), [o.StmtModifier.Private])); this.view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName).set(createRenderNodeExpr).toStmt()); var renderNode = o.THIS_EXPR.prop(fieldName); var component = ast.getComponent(); var directives = ast.directives.map(directiveAst => directiveAst.directive); var htmlAttrs = _readHtmlAttrs(ast.attrs); var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives); for (var i = 0; i < attrNameAndValues.length; i++) { var attrName = attrNameAndValues[i][0]; var attrValue = attrNameAndValues[i][1]; this.view.createMethod.addStmt(ViewProperties.renderer.callMethod('setElementAttribute', [renderNode, o.literal(attrName), o.literal(attrValue)]) .toStmt()); } var compileElement = new CompileElement(parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers, ast.hasViewContainer, false, ast.references); this.view.nodes.push(compileElement); var compViewExpr = null; if (isPresent(component)) { var nestedComponentIdentifier = new CompileIdentifierMetadata({ name: getViewFactoryName(component, 0) }); this.targetDependencies.push(new ViewCompileDependency(component, nestedComponentIdentifier)); compViewExpr = o.variable(`compView_${nodeIndex}`); compileElement.setComponentView(compViewExpr); this.view.createMethod.addStmt(compViewExpr.set(o.importExpr(nestedComponentIdentifier) .callFn([ ViewProperties.viewUtils, compileElement.injector, compileElement.appElement ])) .toDeclStmt()); } compileElement.beforeChildren(); this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent); templateVisitAll(this, ast.children, compileElement); compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1); if (isPresent(compViewExpr)) { var codeGenContentNodes; if (this.view.component.type.isHost) { codeGenContentNodes = ViewProperties.projectableNodes; } else { codeGenContentNodes = o.literalArr(compileElement.contentNodesByNgContentIndex.map(nodes => createFlatArray(nodes))); } this.view.createMethod.addStmt(compViewExpr.callMethod('create', [codeGenContentNodes, o.NULL_EXPR]).toStmt()); } return null; } visitEmbeddedTemplate(ast, parent) { var nodeIndex = this.view.nodes.length; var fieldName = `_anchor_${nodeIndex}`; this.view.fields.push(new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderComment), [o.StmtModifier.Private])); this.view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName) .set(ViewProperties.renderer.callMethod('createTemplateAnchor', [ this._getParentRenderNode(parent), this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast) ])) .toStmt()); var renderNode = o.THIS_EXPR.prop(fieldName); var templateVariableBindings = ast.variables.map(varAst => [varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR, varAst.name]); var directives = ast.directives.map(directiveAst => directiveAst.directive); var compileElement = new CompileElement(parent, this.view, nodeIndex, renderNode, ast, null, directives, ast.providers, ast.hasViewContainer, true, ast.references); this.view.nodes.push(compileElement); this.nestedViewCount++; var embeddedView = new CompileView(this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR, this.view.viewIndex + this.nestedViewCount, compileElement, templateVariableBindings); this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies); compileElement.beforeChildren(); this._addRootNodeAndProject(compileElement, ast.ngContentIndex, parent); compileElement.afterChildren(0); return null; } visitAttr(ast, ctx) { return null; } visitDirective(ast, ctx) { return null; } visitEvent(ast, eventTargetAndNames) { return null; } visitReference(ast, ctx) { return null; } visitVariable(ast, ctx) { return null; } visitDirectiveProperty(ast, context) { return null; } visitElementProperty(ast, context) { return null; } } function _mergeHtmlAndDirectiveAttrs(declaredHtmlAttrs, directives) { var result = {}; StringMapWrapper.forEach(declaredHtmlAttrs, (value, key) => { result[key] = value; }); directives.forEach(directiveMeta => { StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => { var prevValue = result[name]; result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; }); }); return mapToKeyValueArray(result); } function _readHtmlAttrs(attrs) { var htmlAttrs = {}; attrs.forEach((ast) => { htmlAttrs[ast.name] = ast.value; }); return htmlAttrs; } function mergeAttributeValue(attrName, attrValue1, attrValue2) { if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) { return `${attrValue1} ${attrValue2}`; } else { return attrValue2; } } function mapToKeyValueArray(data) { var entryArray = []; StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); }); // We need to sort to get a defined output order // for tests and for caching generated artifacts... ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); var keyValueArray = []; entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); }); return keyValueArray; } function createViewTopLevelStmts(view, targetStatements) { var nodeDebugInfosVar = o.NULL_EXPR; if (view.genConfig.genDebugInfo) { nodeDebugInfosVar = o.variable(`nodeDebugInfos_${view.component.type.name}${view.viewIndex}`); targetStatements.push(nodeDebugInfosVar .set(o.literalArr(view.nodes.map(createStaticNodeDebugInfo), new o.ArrayType(new o.ExternalType(Identifiers.StaticNodeDebugInfo), [o.TypeModifier.Const]))) .toDeclStmt(null, [o.StmtModifier.Final])); } var renderCompTypeVar = o.variable(`renderType_${view.component.type.name}`); if (view.viewIndex === 0) { targetStatements.push(renderCompTypeVar.set(o.NULL_EXPR) .toDeclStmt(o.importType(Identifiers.RenderComponentType))); } var viewClass = createViewClass(view, renderCompTypeVar, nodeDebugInfosVar); targetStatements.push(viewClass); targetStatements.push(createViewFactory(view, viewClass, renderCompTypeVar)); } function createStaticNodeDebugInfo(node) { var compileElement = node instanceof CompileElement ? node : null; var providerTokens = []; var componentToken = o.NULL_EXPR; var varTokenEntries = []; if (isPresent(compileElement)) { providerTokens = compileElement.getProviderTokens(); if (isPresent(compileElement.component)) { componentToken = createDiTokenExpression(identifierToken(compileElement.component.type)); } StringMapWrapper.forEach(compileElement.referenceTokens, (token, varName) => { varTokenEntries.push([varName, isPresent(token) ? createDiTokenExpression(token) : o.NULL_EXPR]); }); } return o.importExpr(Identifiers.StaticNodeDebugInfo) .instantiate([ o.literalArr(providerTokens, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])), componentToken, o.literalMap(varTokenEntries, new o.MapType(o.DYNAMIC_TYPE, [o.TypeModifier.Const])) ], o.importType(Identifiers.StaticNodeDebugInfo, null, [o.TypeModifier.Const])); } function createViewClass(view, renderCompTypeVar, nodeDebugInfosVar) { var emptyTemplateVariableBindings = view.templateVariableBindings.map((entry) => [entry[0], o.NULL_EXPR]); var viewConstructorArgs = [ new o.FnParam(ViewConstructorVars.viewUtils.name, o.importType(Identifiers.ViewUtils)), new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)), new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement)) ]; var viewConstructor = new o.ClassMethod(null, viewConstructorArgs, [ o.SUPER_EXPR.callFn([ o.variable(view.className), renderCompTypeVar, ViewTypeEnum.fromValue(view.viewType), o.literalMap(emptyTemplateVariableBindings), ViewConstructorVars.viewUtils, ViewConstructorVars.parentInjector, ViewConstructorVars.declarationEl, ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view)), nodeDebugInfosVar ]) .toStmt() ]); var viewMethods = [ new o.ClassMethod('createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], generateCreateMethod(view), o.importType(Identifiers.AppElement)), new o.ClassMethod('injectorGetInternal', [ new o.FnParam(InjectMethodVars.token.name, o.DYNAMIC_TYPE), // Note: Can't use o.INT_TYPE here as the method in AppView uses number new o.FnParam(InjectMethodVars.requestNodeIndex.name, o.NUMBER_TYPE), new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE) ], addReturnValuefNotEmpty(view.injectorGetMethod.finish(), InjectMethodVars.notFoundResult), o.DYNAMIC_TYPE), new o.ClassMethod('detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)], generateDetectChangesMethod(view)), new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()), new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()) ].concat(view.eventHandlerMethods); var viewClass = new o.ClassStmt(view.className, o.importExpr(Identifiers.AppView, [getContextType(view)]), view.fields, view.getters, viewConstructor, viewMethods.filter((method) => method.body.length > 0)); return viewClass; } function createViewFactory(view, viewClass, renderCompTypeVar) { var viewFactoryArgs = [ new o.FnParam(ViewConstructorVars.viewUtils.name, o.importType(Identifiers.ViewUtils)), new o.FnParam(ViewConstructorVars.parentInjector.name, o.importType(Identifiers.Injector)), new o.FnParam(ViewConstructorVars.declarationEl.name, o.importType(Identifiers.AppElement)) ]; var initRenderCompTypeStmts = []; var templateUrlInfo; if (view.component.template.templateUrl == view.component.type.moduleUrl) { templateUrlInfo = `${view.component.type.moduleUrl} class ${view.component.type.name} - inline template`; } else { templateUrlInfo = view.component.template.templateUrl; } if (view.viewIndex === 0) { initRenderCompTypeStmts = [ new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [ renderCompTypeVar.set(ViewConstructorVars .viewUtils.callMethod('createRenderComponentType', [ o.literal(templateUrlInfo), o.literal(view.component .template.ngContentSelectors.length), ViewEncapsulationEnum .fromValue(view.component.template.encapsulation), view.styles ])) .toStmt() ]) ]; } return o.fn(viewFactoryArgs, initRenderCompTypeStmts.concat([ new o.ReturnStatement(o.variable(viewClass.name) .instantiate(viewClass.constructorMethod.params.map((param) => o.variable(param.name)))) ]), o.importType(Identifiers.AppView, [getContextType(view)])) .toDeclStmt(view.viewFactory.name, [o.StmtModifier.Final]); } function generateCreateMethod(view) { var parentRenderNodeExpr = o.NULL_EXPR; var parentRenderNodeStmts = []; if (view.viewType === ViewType.COMPONENT) { parentRenderNodeExpr = ViewProperties.renderer.callMethod('createViewRoot', [o.THIS_EXPR.prop('declarationAppElement').prop('nativeElement')]); parentRenderNodeStmts = [ parentRenderNodeVar.set(parentRenderNodeExpr) .toDeclStmt(o.importType(view.genConfig.renderTypes.renderNode), [o.StmtModifier.Final]) ]; } var resultExpr; if (view.viewType === ViewType.HOST) { resultExpr = view.nodes[0].appElement; } else { resultExpr = o.NULL_EXPR; } return parentRenderNodeStmts.concat(view.createMethod.finish()) .concat([ o.THIS_EXPR.callMethod('init', [ createFlatArray(view.rootNodesOrAppElements), o.literalArr(view.nodes.map(node => node.renderNode)), o.literalArr(view.disposables), o.literalArr(view.subscriptions) ]) .toStmt(), new o.ReturnStatement(resultExpr) ]); } function generateDetectChangesMethod(view) { var stmts = []; if (view.detectChangesInInputsMethod.isEmpty() && view.updateContentQueriesMethod.isEmpty() && view.afterContentLifecycleCallbacksMethod.isEmpty() && view.detectChangesRenderPropertiesMethod.isEmpty() && view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) { return stmts; } ListWrapper.addAll(stmts, view.detectChangesInInputsMethod.finish()); stmts.push(o.THIS_EXPR.callMethod('detectContentChildrenChanges', [DetectChangesVars.throwOnChange]) .toStmt()); var afterContentStmts = view.updateContentQueriesMethod.finish().concat(view.afterContentLifecycleCallbacksMethod.finish()); if (afterContentStmts.length > 0) { stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts)); } ListWrapper.addAll(stmts, view.detectChangesRenderPropertiesMethod.finish()); stmts.push(o.THIS_EXPR.callMethod('detectViewChildrenChanges', [DetectChangesVars.throwOnChange]) .toStmt()); var afterViewStmts = view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish()); if (afterViewStmts.length > 0) { stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterViewStmts)); } var varStmts = []; var readVars = o.findReadVarNames(stmts); if (SetWrapper.has(readVars, DetectChangesVars.changed.name)) { varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)); } if (SetWrapper.has(readVars, DetectChangesVars.changes.name)) { varStmts.push(DetectChangesVars.changes.set(o.NULL_EXPR) .toDeclStmt(new o.MapType(o.importType(Identifiers.SimpleChange)))); } if (SetWrapper.has(readVars, DetectChangesVars.valUnwrapper.name)) { varStmts.push(DetectChangesVars.valUnwrapper.set(o.importExpr(Identifiers.ValueUnwrapper).instantiate([])) .toDeclStmt(null, [o.StmtModifier.Final])); } return varStmts.concat(stmts); } function addReturnValuefNotEmpty(statements, value) { if (statements.length > 0) { return statements.concat([new o.ReturnStatement(value)]); } else { return statements; } } function getContextType(view) { var typeMeta = view.component.type; return typeMeta.isHost ? o.DYNAMIC_TYPE : o.importType(typeMeta); } function getChangeDetectionMode(view) { var mode; if (view.viewType === ViewType.COMPONENT) { mode = isDefaultChangeDetectionStrategy(view.component.changeDetection) ? ChangeDetectionStrategy.CheckAlways : ChangeDetectionStrategy.CheckOnce; } else { mode = ChangeDetectionStrategy.CheckAlways; } return mode; }