From 681f69ce97dc8b26aadaf39702ebdc28abe541c6 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 1 Oct 2015 10:07:49 -0700 Subject: [PATCH] refactor(compiler): use the new compiler everywhere Closes #3605 BREAKING CHANGE: - we don't mark an element as bound any more if it only contains text bindings E.g.
{{hello}}
This changes the indices when using `DebugElement.componentViewChildren` / `DebugElement.children`. - `@Directive.compileChildren` was removed, `ng-non-bindable` is now builtin and not a directive any more - angular no more adds the `ng-binding` class to elements with bindings - directives are now ordered as they are listed in the View.directives regarding change detection. Previously they had an undefined order. - the `Renderer` interface has new methods `createProtoView` and `registerComponentTemplate`. See `DomRenderer` for default implementations. - reprojection with `ng-content` is now all or nothing per `ng-content` element - angular2 transformer can't be used in tests that modify directive metadata. Use `angular2/src/transform/inliner_for_test` transformer instead. --- .../src/compiler/change_definition_factory.ts | 9 +- .../src/compiler/change_detector_compiler.ts | 26 +- .../angular2/src/compiler/command_compiler.ts | 7 +- modules/angular2/src/compiler/compiler.ts | 10 + .../src/compiler/directive_metadata.ts | 23 +- .../angular2/src/compiler/runtime_compiler.ts | 28 + .../angular2/src/compiler/runtime_metadata.ts | 11 +- .../angular2/src/compiler/source_module.ts | 16 +- .../angular2/src/compiler/style_compiler.ts | 29 +- .../src/compiler/template_compiler.ts | 42 +- .../src/compiler/template_normalizer.ts | 6 +- modules/angular2/src/compiler/util.ts | 13 +- modules/angular2/src/core/application.dart | 7 +- modules/angular2/src/core/application.ts | 20 +- modules/angular2/src/core/application_ref.ts | 15 +- modules/angular2/src/core/bootstrap.dart | 20 +- modules/angular2/src/core/bootstrap.ts | 2 +- .../angular2/src/core/compiler/compiler.ts | 371 +-------- .../src/core/compiler/directive_resolver.ts | 2 - .../src/core/compiler/element_binder.ts | 16 +- .../src/core/compiler/element_injector.ts | 5 +- .../angular2/src/core/compiler/element_ref.ts | 6 +- .../src/core/compiler/proto_view_factory.ts | 564 +++++++------ .../src/core/compiler/template_ref.ts | 4 +- modules/angular2/src/core/compiler/view.ts | 129 ++- .../src/core/compiler/view_manager.ts | 29 +- .../src/core/compiler/view_manager_utils.ts | 58 +- .../angular2/src/core/di/di_transformer.dart | 3 - modules/angular2/src/core/directives.ts | 7 +- .../src/core/directives/ng_non_bindable.ts | 18 - .../src/core/dom/abstract_html_adapter.dart | 13 +- modules/angular2/src/core/dom/dom_adapter.ts | 1 + .../src/core/dom/generic_browser_adapter.ts | 4 + .../angular2/src/core/dom/html_adapter.dart | 443 +--------- .../angular2/src/core/dom/parse5_adapter.ts | 14 +- .../src/core/dom/webworker_adapter.dart | 24 + modules/angular2/src/core/metadata.dart | 9 +- modules/angular2/src/core/metadata.ts | 4 - .../angular2/src/core/metadata/directives.ts | 44 +- modules/angular2/src/core/pipes/pipes.ts | 17 +- .../platform_reflection_capabilities.ts | 3 - .../reflection/reflection_capabilities.dart | 7 - .../reflection/reflection_capabilities.ts | 2 - .../angular2/src/core/reflection/reflector.ts | 2 - modules/angular2/src/core/render.ts | 10 +- modules/angular2/src/core/render/api.ts | 10 + .../src/core/render/dom/dom_renderer.ts | 268 +++--- .../dom/schema/dom_element_schema_registry.ts | 2 + .../src/core/services/url_resolver.dart | 8 +- .../src/mock/directive_resolver_mock.ts | 2 - .../angular2/src/test_lib/test_injector.ts | 11 +- .../angular2/src/web_workers/shared/api.ts | 68 +- .../src/web_workers/shared/messaging_api.ts | 1 - .../shared/render_proto_view_ref_store.ts | 28 +- .../src/web_workers/shared/serializer.ts | 104 ++- .../src/web_workers/ui/di_bindings.ts | 9 +- .../src/web_workers/ui/event_serializer.dart | 1 - modules/angular2/src/web_workers/ui/impl.ts | 2 - .../src/web_workers/ui/render_compiler.ts | 31 - .../angular2/src/web_workers/ui/renderer.ts | 17 +- .../src/web_workers/worker/application.dart | 2 + .../src/web_workers/worker/application.ts | 2 + .../web_workers/worker/application_common.ts | 6 +- .../src/web_workers/worker/renderer.ts | 78 +- .../change_definition_factory_spec.ts | 10 +- .../compiler/change_detector_compiler_spec.ts | 23 +- .../test/compiler/change_detector_mocks.ts | 8 - .../test/compiler/command_compiler_spec.ts | 16 +- .../test/compiler/directive_metadata_spec.ts | 3 +- .../angular2/test/compiler/eval_module.dart | 2 +- modules/angular2/test/compiler/eval_module.ts | 17 +- .../test/compiler/eval_module_spec.ts | 4 +- .../test/compiler/runtime_compiler_spec.ts | 92 +++ .../test/compiler/runtime_metadata_spec.ts | 14 +- .../test/compiler/source_module_spec.ts | 16 +- ...m.ts => style_compiler_import.css.shim.ts} | 0 ...import.ts => style_compiler_import.css.ts} | 0 .../test/compiler/style_compiler_spec.ts | 53 +- .../test/compiler/template_compiler_spec.ts | 48 +- .../test/compiler/template_normalizer_spec.ts | 77 +- .../angular2/test/core/application_spec.ts | 4 +- .../generator/gen_change_detectors.dart | 2 +- .../test/core/compiler/compiler_spec.ts | 771 +----------------- .../test/core/compiler/integration_spec.ts | 49 +- .../compiler/projection_integration_spec.ts | 6 +- .../core/compiler/proto_view_factory_spec.ts | 120 +-- .../core/compiler/view_container_ref_spec.ts | 2 +- .../test/core/compiler/view_manager_spec.ts | 46 +- .../core/compiler/view_manager_utils_spec.ts | 110 +-- .../test/core/compiler/view_pool_spec.ts | 6 +- .../test/core/debug/debug_element_spec.ts | 8 +- .../test/core/directives/ng_class_spec.ts | 104 +-- .../test/core/directives/non_bindable_spec.ts | 4 +- .../test/core/forms/integration_spec.ts | 34 +- .../angular2/test/core/pipes/pipes_spec.ts | 16 +- .../test/core/reflection/reflector_spec.ts | 14 +- .../dom/dom_renderer_integration_spec.ts | 268 +----- modules/angular2/test/core/spies.dart | 6 + modules/angular2/test/core/spies.ts | 5 + modules/angular2/test/public_api_spec.ts | 39 +- .../render_proto_view_ref_store_spec.ts | 60 +- .../worker/renderer_integration_spec.ts | 381 +++------ modules/angular2/web_worker/worker.ts | 10 +- modules/angular2_material/pubspec.yaml | 2 + modules/benchmarks/pubspec.yaml | 1 - .../src/compiler/compiler_benchmark.ts | 14 +- modules/examples/pubspec.yaml | 29 +- modules/upgrade/src/upgrade_module.ts | 4 +- .../analyzer_plugin/lib/src/tasks.dart | 37 +- .../transform/common/code/source_module.dart | 21 +- .../lib/src/transform/common/code/uri.dart | 2 +- .../lib/src/transform/common/ng_compiler.dart | 2 +- .../lib/src/transform/inliner_for_test.dart | 11 +- .../stylesheet_compiler/processor.dart | 8 +- .../compile_data_creator.dart | 9 +- .../template_compiler/generator.dart | 39 +- .../template_compiler/all_tests.dart | 11 + .../expected/hello.ng_deps.dart | 20 + .../directive_event_files/hello.ng_deps.dart | 19 + .../directive_event_files/hello.ng_meta.json | 32 + tools/broccoli/trees/browser_tree.ts | 7 +- tools/build/pubserve.js | 2 +- tools/cjs-jasmine/index.js | 2 +- 123 files changed, 2012 insertions(+), 3451 deletions(-) create mode 100644 modules/angular2/src/compiler/runtime_compiler.ts delete mode 100644 modules/angular2/src/core/di/di_transformer.dart delete mode 100644 modules/angular2/src/core/directives/ng_non_bindable.ts create mode 100644 modules/angular2/src/core/dom/webworker_adapter.dart delete mode 100644 modules/angular2/src/web_workers/ui/render_compiler.ts create mode 100644 modules/angular2/test/compiler/runtime_compiler_spec.ts rename modules/angular2/test/compiler/{style_compiler_import.shim.ts => style_compiler_import.css.shim.ts} (100%) rename modules/angular2/test/compiler/{style_compiler_import.ts => style_compiler_import.css.ts} (100%) create mode 100644 modules_dart/transform/test/transform/template_compiler/directive_event_files/expected/hello.ng_deps.dart create mode 100644 modules_dart/transform/test/transform/template_compiler/directive_event_files/hello.ng_deps.dart create mode 100644 modules_dart/transform/test/transform/template_compiler/directive_event_files/hello.ng_meta.json diff --git a/modules/angular2/src/compiler/change_definition_factory.ts b/modules/angular2/src/compiler/change_definition_factory.ts index 259b2e52b3314..ab0ca2839a820 100644 --- a/modules/angular2/src/compiler/change_definition_factory.ts +++ b/modules/angular2/src/compiler/change_definition_factory.ts @@ -189,8 +189,7 @@ function createChangeDefinitions(pvVisitors: ProtoViewVisitor[], componentType: genConfig: ChangeDetectorGenConfig): ChangeDetectorDefinition[] { var pvVariableNames = _collectNestedProtoViewsVariableNames(pvVisitors); return pvVisitors.map(pvVisitor => { - var viewType = pvVisitor.viewIndex === 0 ? 'component' : 'embedded'; - var id = _protoViewId(componentType, pvVisitor.viewIndex, viewType); + var id = `${componentType.name}_${pvVisitor.viewIndex}`; return new ChangeDetectorDefinition( id, pvVisitor.strategy, pvVariableNames[pvVisitor.viewIndex], pvVisitor.bindingRecords, pvVisitor.eventRecords, pvVisitor.directiveRecords, genConfig); @@ -207,9 +206,3 @@ function _collectNestedProtoViewsVariableNames(pvVisitors: ProtoViewVisitor[]): }); return nestedPvVariableNames; } - - -function _protoViewId(hostComponentType: CompileTypeMetadata, pvIndex: number, viewType: string): - string { - return `${hostComponentType.name}_${viewType}_${pvIndex}`; -} diff --git a/modules/angular2/src/compiler/change_detector_compiler.ts b/modules/angular2/src/compiler/change_detector_compiler.ts index f30f87cdfeede..5ca5d588699cd 100644 --- a/modules/angular2/src/compiler/change_detector_compiler.ts +++ b/modules/angular2/src/compiler/change_detector_compiler.ts @@ -16,17 +16,18 @@ import { import {TemplateAst} from './template_ast'; import {Codegen} from 'angular2/src/transform/template_compiler/change_detector_codegen'; -import {IS_DART} from './util'; +import {IS_DART, MODULE_SUFFIX} from './util'; import {Injectable} from 'angular2/src/core/di'; const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector"; const UTIL = "ChangeDetectionUtil"; -var ABSTRACT_CHANGE_DETECTOR_MODULE = - moduleRef('angular2/src/core/change_detection/abstract_change_detector'); -var UTIL_MODULE = moduleRef('angular2/src/core/change_detection/change_detection_util'); -var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = - moduleRef('angular2/src/core/change_detection/pregen_proto_change_detector'); +var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef( + `package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`); +var UTIL_MODULE = + moduleRef(`package:angular2/src/core/change_detection/change_detection_util${MODULE_SUFFIX}`); +var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = moduleRef( + `package:angular2/src/core/change_detection/pregen_proto_change_detector${MODULE_SUFFIX}`); @Injectable() export class ChangeDetectionCompiler { @@ -54,24 +55,31 @@ export class ChangeDetectionCompiler { var changeDetectorDefinitions = createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate); var factories = []; + var index = 0; var sourceParts = changeDetectorDefinitions.map(definition => { var codegen: any; + var sourcePart; // TODO(tbosch): move the 2 code generators to the same place, one with .dart and one with .ts // suffix // and have the same API for calling them! if (IS_DART) { codegen = new Codegen(PREGEN_PROTO_CHANGE_DETECTOR_MODULE); var className = definition.id; - codegen.generate(componentType.name, className, definition); + var typeRef = (index === 0 && componentType.isHost) ? + 'dynamic' : + `${moduleRef(componentType.moduleUrl)}${componentType.name}`; + codegen.generate(typeRef, className, definition); factories.push(`(dispatcher) => new ${className}(dispatcher)`); - return codegen.toString(); + sourcePart = codegen.toString(); } else { codegen = new ChangeDetectorJITGenerator( definition, `${UTIL_MODULE}${UTIL}`, `${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`); factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`); - return codegen.generateSource(); + sourcePart = codegen.generateSource(); } + index++; + return sourcePart; }); return new SourceExpressions(sourceParts, factories); } diff --git a/modules/angular2/src/compiler/command_compiler.ts b/modules/angular2/src/compiler/command_compiler.ts index 7bd6b3f4c37fd..04d07a4289fd5 100644 --- a/modules/angular2/src/compiler/command_compiler.ts +++ b/modules/angular2/src/compiler/command_compiler.ts @@ -37,10 +37,11 @@ import { shimContentAttributeExpr, shimHostAttributeExpr } from './style_compiler'; -import {escapeSingleQuoteString} from './util'; +import {escapeSingleQuoteString, MODULE_SUFFIX} from './util'; import {Injectable} from 'angular2/src/core/di'; -export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands'); +export var TEMPLATE_COMMANDS_MODULE_REF = + moduleRef(`package:angular2/src/core/compiler/template_commands${MODULE_SUFFIX}`); const IMPLICIT_TEMPLATE_VAR = '\$implicit'; @@ -353,6 +354,6 @@ function codeGenArray(data: any[]): string { function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string { var expressions = directives.map( - directiveType => `${moduleRef(directiveType.type.moduleId)}${directiveType.type.name}`); + directiveType => `${moduleRef(directiveType.type.moduleUrl)}${directiveType.type.name}`); return `[${expressions.join(',')}]`; } diff --git a/modules/angular2/src/compiler/compiler.ts b/modules/angular2/src/compiler/compiler.ts index 02b34da7836a7..079311c1fb51f 100644 --- a/modules/angular2/src/compiler/compiler.ts +++ b/modules/angular2/src/compiler/compiler.ts @@ -17,6 +17,12 @@ import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; import {CommandCompiler} from 'angular2/src/compiler/command_compiler'; import {TemplateCompiler} from 'angular2/src/compiler/template_compiler'; import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection'; +import {Compiler} from 'angular2/src/core/compiler/compiler'; +import {RuntimeCompiler} from 'angular2/src/compiler/runtime_compiler'; +import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; +import { + DomElementSchemaRegistry +} from 'angular2/src/core/render/dom/schema/dom_element_schema_registry'; export function compilerBindings(): Array { return [ @@ -31,5 +37,9 @@ export function compilerBindings(): Array { .toValue( new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), false, true)), TemplateCompiler, + RuntimeCompiler, + bind(Compiler).toAlias(RuntimeCompiler), + DomElementSchemaRegistry, + bind(ElementSchemaRegistry).toAlias(DomElementSchemaRegistry) ]; } diff --git a/modules/angular2/src/compiler/directive_metadata.ts b/modules/angular2/src/compiler/directive_metadata.ts index 73ca87cefbb9a..357895f4b3721 100644 --- a/modules/angular2/src/compiler/directive_metadata.ts +++ b/modules/angular2/src/compiler/directive_metadata.ts @@ -24,22 +24,27 @@ var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g; export class CompileTypeMetadata { runtime: Type; name: string; - moduleId: string; - constructor({runtime, name, moduleId}: {runtime?: Type, name?: string, moduleId?: string} = {}) { + moduleUrl: string; + isHost: boolean; + constructor({runtime, name, moduleUrl, isHost}: + {runtime?: Type, name?: string, moduleUrl?: string, isHost?: boolean} = {}) { this.runtime = runtime; this.name = name; - this.moduleId = moduleId; + this.moduleUrl = moduleUrl; + this.isHost = normalizeBool(isHost); } static fromJson(data: StringMap): CompileTypeMetadata { - return new CompileTypeMetadata({name: data['name'], moduleId: data['moduleId']}); + return new CompileTypeMetadata( + {name: data['name'], moduleUrl: data['moduleUrl'], isHost: data['isHost']}); } toJson(): StringMap { return { // Note: Runtime type can't be serialized... 'name': this.name, - 'moduleId': this.moduleId + 'moduleUrl': this.moduleUrl, + 'isHost': this.isHost }; } } @@ -248,8 +253,12 @@ export function createHostComponentMeta(componentType: CompileTypeMetadata, componentSelector: string): CompileDirectiveMetadata { var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate(); return CompileDirectiveMetadata.create({ - type: new CompileTypeMetadata( - {runtime: Object, name: `Host${componentType.name}`, moduleId: componentType.moduleId}), + type: new CompileTypeMetadata({ + runtime: Object, + name: `Host${componentType.name}`, + moduleUrl: componentType.moduleUrl, + isHost: true + }), template: new CompileTemplateMetadata( {template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}), changeDetection: ChangeDetectionStrategy.Default, diff --git a/modules/angular2/src/compiler/runtime_compiler.ts b/modules/angular2/src/compiler/runtime_compiler.ts new file mode 100644 index 0000000000000..7addd41f6b8a5 --- /dev/null +++ b/modules/angular2/src/compiler/runtime_compiler.ts @@ -0,0 +1,28 @@ +import {Compiler, internalCreateProtoView} from 'angular2/src/core/compiler/compiler'; +import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref'; +import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; +import {TemplateCompiler} from './template_compiler'; + +import {Injectable} from 'angular2/src/core/di'; +import {Type} from 'angular2/src/core/facade/lang'; +import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; + +@Injectable() +export class RuntimeCompiler extends Compiler { + /** + * @private + */ + constructor(_protoViewFactory: ProtoViewFactory, private _templateCompiler: TemplateCompiler) { + super(_protoViewFactory); + } + + compileInHost(componentType: Type): Promise { + return this._templateCompiler.compileHostComponentRuntime(componentType) + .then(compiledHostTemplate => internalCreateProtoView(this, compiledHostTemplate)); + } + + clearCache() { + super.clearCache(); + this._templateCompiler.clearCache(); + } +} diff --git a/modules/angular2/src/compiler/runtime_metadata.ts b/modules/angular2/src/compiler/runtime_metadata.ts index cdc6bf219d189..e75a70067d812 100644 --- a/modules/angular2/src/compiler/runtime_metadata.ts +++ b/modules/angular2/src/compiler/runtime_metadata.ts @@ -18,6 +18,7 @@ import {hasLifecycleHook} from 'angular2/src/core/compiler/directive_lifecycle_r import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/compiler/interfaces'; import {reflector} from 'angular2/src/core/reflection/reflection'; import {Injectable} from 'angular2/src/core/di'; +import {MODULE_SUFFIX} from './util'; // group 1: "property" from "[property]" // group 2: "event" from "(event)" @@ -33,7 +34,7 @@ export class RuntimeMetadataResolver { var meta = this._cache.get(directiveType); if (isBlank(meta)) { var directiveAnnotation = this._directiveResolver.resolve(directiveType); - var moduleId = calcModuleId(directiveType, directiveAnnotation); + var moduleUrl = calcModuleUrl(directiveType, directiveAnnotation); var templateMeta = null; var changeDetectionStrategy = null; @@ -55,7 +56,7 @@ export class RuntimeMetadataResolver { isComponent: isPresent(templateMeta), dynamicLoadable: true, type: new cpl.CompileTypeMetadata( - {name: stringify(directiveType), moduleId: moduleId, runtime: directiveType}), + {name: stringify(directiveType), moduleUrl: moduleUrl, runtime: directiveType}), template: templateMeta, changeDetection: changeDetectionStrategy, inputs: directiveAnnotation.inputs, @@ -111,10 +112,10 @@ function isValidDirective(value: Type): boolean { return isPresent(value) && (value instanceof Type); } -function calcModuleId(type: Type, directiveAnnotation: dirAnn.DirectiveMetadata): string { +function calcModuleUrl(type: Type, directiveAnnotation: dirAnn.DirectiveMetadata): string { if (isPresent(directiveAnnotation.moduleId)) { - return directiveAnnotation.moduleId; + return `package:${directiveAnnotation.moduleId}${MODULE_SUFFIX}`; } else { - return reflector.moduleId(type); + return reflector.importUri(type); } } diff --git a/modules/angular2/src/compiler/source_module.ts b/modules/angular2/src/compiler/source_module.ts index c68d80cfe1a88..e620987806db7 100644 --- a/modules/angular2/src/compiler/source_module.ts +++ b/modules/angular2/src/compiler/source_module.ts @@ -2,28 +2,28 @@ import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang'; var MODULE_REGEXP = /#MODULE\[([^\]]*)\]/g; -export function moduleRef(moduleId): string { - return `#MODULE[${moduleId}]`; +export function moduleRef(moduleUrl): string { + return `#MODULE[${moduleUrl}]`; } export class SourceModule { - constructor(public moduleId: string, public sourceWithModuleRefs: string) {} + constructor(public moduleUrl: string, public sourceWithModuleRefs: string) {} getSourceWithImports(): SourceWithImports { var moduleAliases = {}; var imports: string[][] = []; var newSource = StringWrapper.replaceAllMapped(this.sourceWithModuleRefs, MODULE_REGEXP, (match) => { - var moduleId = match[1]; - var alias = moduleAliases[moduleId]; + var moduleUrl = match[1]; + var alias = moduleAliases[moduleUrl]; if (isBlank(alias)) { - if (moduleId == this.moduleId) { + if (moduleUrl == this.moduleUrl) { alias = ''; } else { alias = `import${imports.length}`; - imports.push([moduleId, alias]); + imports.push([moduleUrl, alias]); } - moduleAliases[moduleId] = alias; + moduleAliases[moduleUrl] = alias; } return alias.length > 0 ? `${alias}.` : ''; }); diff --git a/modules/angular2/src/compiler/style_compiler.ts b/modules/angular2/src/compiler/style_compiler.ts index bff4e6e1649c1..66d618ed93f2f 100644 --- a/modules/angular2/src/compiler/style_compiler.ts +++ b/modules/angular2/src/compiler/style_compiler.ts @@ -14,7 +14,8 @@ import { codeGenMapArray, codeGenReplaceAll, codeGenExportVariable, - codeGenToString + codeGenToString, + MODULE_SUFFIX } from './util'; import {Injectable} from 'angular2/src/core/di'; @@ -56,13 +57,15 @@ export class StyleCompiler { return this._styleCodeGen(template.styles, template.styleUrls, shim, suffix); } - compileStylesheetCodeGen(moduleId: string, cssText: string): SourceModule[] { - var styleWithImports = resolveStyleUrls(this._urlResolver, moduleId, cssText); + compileStylesheetCodeGen(stylesheetUrl: string, cssText: string): SourceModule[] { + var styleWithImports = resolveStyleUrls(this._urlResolver, stylesheetUrl, cssText); return [ - this._styleModule(moduleId, false, this._styleCodeGen([styleWithImports.style], - styleWithImports.styleUrls, false, '')), - this._styleModule(moduleId, true, this._styleCodeGen([styleWithImports.style], - styleWithImports.styleUrls, true, '')) + this._styleModule( + stylesheetUrl, false, + this._styleCodeGen([styleWithImports.style], styleWithImports.styleUrls, false, '')), + this._styleModule( + stylesheetUrl, true, + this._styleCodeGen([styleWithImports.style], styleWithImports.styleUrls, true, '')) ]; } @@ -96,28 +99,28 @@ export class StyleCompiler { expressionSource += `[${plainStyles.map( plainStyle => escapeSingleQuoteString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`; for (var i = 0; i < absUrls.length; i++) { - var moduleId = this._shimModuleIdIfNeeded(absUrls[i], shim); - expressionSource += codeGenConcatArray(`${moduleRef(moduleId)}STYLES`); + var moduleUrl = this._createModuleUrl(absUrls[i], shim); + expressionSource += codeGenConcatArray(`${moduleRef(moduleUrl)}STYLES`); } expressionSource += `)${suffix}`; return new SourceExpression([], expressionSource); } - private _styleModule(moduleId: string, shim: boolean, + private _styleModule(stylesheetUrl: string, shim: boolean, expression: SourceExpression): SourceModule { var moduleSource = ` ${expression.declarations.join('\n')} ${codeGenExportVariable('STYLES')}${expression.expression}; `; - return new SourceModule(this._shimModuleIdIfNeeded(moduleId, shim), moduleSource); + return new SourceModule(this._createModuleUrl(stylesheetUrl, shim), moduleSource); } private _shimIfNeeded(style: string, shim: boolean): string { return shim ? this._shadowCss.shimCssText(style, CONTENT_ATTR, HOST_ATTR) : style; } - private _shimModuleIdIfNeeded(moduleId: string, shim: boolean): string { - return shim ? `${moduleId}.shim` : moduleId; + private _createModuleUrl(stylesheetUrl: string, shim: boolean): string { + return shim ? `${stylesheetUrl}.shim${MODULE_SUFFIX}` : `${stylesheetUrl}${MODULE_SUFFIX}`; } } diff --git a/modules/angular2/src/compiler/template_compiler.ts b/modules/angular2/src/compiler/template_compiler.ts index 6bb03677e0761..664dd38de2383 100644 --- a/modules/angular2/src/compiler/template_compiler.ts +++ b/modules/angular2/src/compiler/template_compiler.ts @@ -26,7 +26,13 @@ import {RuntimeMetadataResolver} from './runtime_metadata'; import {APP_ID} from 'angular2/src/core/render/dom/dom_tokens'; import {TEMPLATE_COMMANDS_MODULE_REF} from './command_compiler'; -import {IS_DART, codeGenExportVariable, escapeSingleQuoteString, codeGenValueFn} from './util'; +import { + IS_DART, + codeGenExportVariable, + escapeSingleQuoteString, + codeGenValueFn, + MODULE_SUFFIX +} from './util'; import {Inject} from 'angular2/src/core/di'; @Injectable() @@ -164,51 +170,54 @@ export class TemplateCompiler { }); } - compileTemplatesCodeGen(moduleId: string, - components: NormalizedComponentWithViewDirectives[]): SourceModule { + compileTemplatesCodeGen(components: NormalizedComponentWithViewDirectives[]): SourceModule { + if (components.length === 0) { + throw new BaseException('No components given'); + } var declarations = []; var templateArguments = []; var componentMetas: CompileDirectiveMetadata[] = []; - var isHost: boolean[] = []; var templateIdVariable = 'templateId'; var appIdVariable = 'appId'; components.forEach(componentWithDirs => { var compMeta = componentWithDirs.component; assertComponent(compMeta); componentMetas.push(compMeta); - isHost.push(false); this._processTemplateCodeGen(compMeta, appIdVariable, templateIdVariable, componentWithDirs.directives, declarations, templateArguments); if (compMeta.dynamicLoadable) { var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); componentMetas.push(hostMeta); - isHost.push(true); this._processTemplateCodeGen(hostMeta, appIdVariable, templateIdVariable, [compMeta], declarations, templateArguments); } }); ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata, index: number) => { - var templateDataFn = codeGenValueFn([templateIdVariable, appIdVariable], + var templateDataFn = codeGenValueFn([appIdVariable, templateIdVariable], `[${(templateArguments[index]).join(',')}]`); var compiledTemplateExpr = `new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${TEMPLATE_COMMANDS_MODULE_REF}nextTemplateId(),${templateDataFn})`; var variableValueExpr; - if (isHost[index]) { + if (compMeta.type.isHost) { + var factoryName = `_hostTemplateFactory${index}`; + declarations.push(`${codeGenValueFn([], compiledTemplateExpr, factoryName)};`); + var constructionKeyword = IS_DART ? 'const' : 'new'; variableValueExpr = - `new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledHostTemplate(${codeGenValueFn([], compiledTemplateExpr)})`; + `${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledHostTemplate(${factoryName})`; } else { variableValueExpr = compiledTemplateExpr; } declarations.push( - `${codeGenExportVariable(templateVariableName(compMeta.type))}${variableValueExpr};`); + `${codeGenExportVariable(templateVariableName(compMeta.type), compMeta.type.isHost)}${variableValueExpr};`); }); - return new SourceModule(`${templateModuleName(moduleId)}`, declarations.join('\n')); + var moduleUrl = components[0].component.type.moduleUrl; + return new SourceModule(`${templateModuleUrl(moduleUrl)}`, declarations.join('\n')); } - compileStylesheetCodeGen(moduleId: string, cssText: string): SourceModule[] { - return this._styleCompiler.compileStylesheetCodeGen(moduleId, cssText); + compileStylesheetCodeGen(stylesheetUrl: string, cssText: string): SourceModule[] { + return this._styleCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText); } private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata, appIdExpr: string, @@ -248,8 +257,9 @@ function templateVariableName(type: CompileTypeMetadata): string { return `${type.name}Template`; } -function templateModuleName(moduleId: string): string { - return `${moduleId}.template`; +function templateModuleUrl(moduleUrl: string): string { + var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length); + return `${urlWithoutSuffix}.template${MODULE_SUFFIX}`; } function addAll(source: any[], target: any[]) { @@ -259,5 +269,5 @@ function addAll(source: any[], target: any[]) { } function codeGenComponentTemplateFactory(nestedCompType: CompileDirectiveMetadata): string { - return `${moduleRef(templateModuleName(nestedCompType.type.moduleId))}${templateVariableName(nestedCompType.type)}`; + return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}${templateVariableName(nestedCompType.type)}`; } diff --git a/modules/angular2/src/compiler/template_normalizer.ts b/modules/angular2/src/compiler/template_normalizer.ts index eed8da94007ae..993dbb0fbacf1 100644 --- a/modules/angular2/src/compiler/template_normalizer.ts +++ b/modules/angular2/src/compiler/template_normalizer.ts @@ -34,9 +34,9 @@ export class TemplateNormalizer { template: CompileTemplateMetadata): Promise { if (isPresent(template.template)) { return PromiseWrapper.resolve(this.normalizeLoadedTemplate( - directiveType, template, template.template, directiveType.moduleId)); + directiveType, template, template.template, directiveType.moduleUrl)); } else if (isPresent(template.templateUrl)) { - var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleId, template.templateUrl); + var sourceAbsUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl); return this._xhr.get(sourceAbsUrl) .then(templateContent => this.normalizeLoadedTemplate(directiveType, template, templateContent, sourceAbsUrl)); @@ -55,7 +55,7 @@ export class TemplateNormalizer { var allStyleAbsUrls = visitor.styleUrls.map(url => this._urlResolver.resolve(templateAbsUrl, url)) .concat(templateMeta.styleUrls.map( - url => this._urlResolver.resolve(directiveType.moduleId, url))); + url => this._urlResolver.resolve(directiveType.moduleUrl, url))); var allResolvedStyles = allStyles.map(style => { var styleWithImports = resolveStyleUrls(this._urlResolver, templateAbsUrl, style); diff --git a/modules/angular2/src/compiler/util.ts b/modules/angular2/src/compiler/util.ts index 63daf7be83e7a..c2200d5ba58e0 100644 --- a/modules/angular2/src/compiler/util.ts +++ b/modules/angular2/src/compiler/util.ts @@ -7,6 +7,8 @@ var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n|\$/g; export var IS_DART = !isJsObject({}); +export var MODULE_SUFFIX = IS_DART ? '.dart' : '.js'; + export function camelCaseToDashCase(input: string): string { return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => { return '-' + m[1].toLowerCase(); }); @@ -43,8 +45,9 @@ function escapeString(input: string, re: RegExp): string { }); } -export function codeGenExportVariable(name: string): string { - return IS_DART ? `var ${name} = ` : `var ${name} = exports['${name}'] = `; +export function codeGenExportVariable(name: string, isConst: boolean = false): string { + var declaration = isConst ? `const ${name}` : `var ${name}`; + return IS_DART ? `${declaration} = ` : `${declaration} = exports['${name}'] = `; } export function codeGenConcatArray(expression: string): string { @@ -67,11 +70,11 @@ export function codeGenReplaceAll(pattern: string, expression: string): string { } } -export function codeGenValueFn(params: string[], value: string): string { +export function codeGenValueFn(params: string[], value: string, fnName: string = ''): string { if (IS_DART) { - return `(${params.join(',')}) => ${value}`; + return `${fnName}(${params.join(',')}) => ${value}`; } else { - return `function(${params.join(',')}) { return ${value}; }`; + return `function ${fnName}(${params.join(',')}) { return ${value}; }`; } } diff --git a/modules/angular2/src/core/application.dart b/modules/angular2/src/core/application.dart index 71bcf77943bee..f515ec31cbd9a 100644 --- a/modules/angular2/src/core/application.dart +++ b/modules/angular2/src/core/application.dart @@ -7,6 +7,7 @@ import 'package:angular2/src/core/reflection/reflection_capabilities.dart' show ReflectionCapabilities; import 'application_common.dart'; +import 'package:angular2/src/compiler/compiler.dart'; import 'package:angular2/src/core/compiler/dynamic_component_loader.dart'; export 'package:angular2/src/core/compiler/dynamic_component_loader.dart' show ComponentRef; @@ -19,5 +20,9 @@ export 'package:angular2/src/core/compiler/dynamic_component_loader.dart' show C Future bootstrap(Type appComponentType, [List componentInjectableBindings]) { reflector.reflectionCapabilities = new ReflectionCapabilities(); - return commonBootstrap(appComponentType, componentInjectableBindings); + var bindings = [compilerBindings()]; + if (componentInjectableBindings != null) { + bindings.add(componentInjectableBindings); + } + return commonBootstrap(appComponentType, bindings); } diff --git a/modules/angular2/src/core/application.ts b/modules/angular2/src/core/application.ts index 22c3078c089a3..995e533e9a6e2 100644 --- a/modules/angular2/src/core/application.ts +++ b/modules/angular2/src/core/application.ts @@ -1,6 +1,13 @@ // Public API for Application +import {Binding} from './di'; +import {Type, isPresent} from 'angular2/src/core/facade/lang'; +import {Promise} from 'angular2/src/core/facade/async'; +import {compilerBindings} from 'angular2/src/compiler/compiler'; +import {commonBootstrap} from './application_common'; +import {ComponentRef} from './compiler/dynamic_component_loader'; + export {APP_COMPONENT} from './application_tokens'; -export {platform, commonBootstrap as bootstrap} from './application_common'; +export {platform} from './application_common'; export { PlatformRef, ApplicationRef, @@ -9,3 +16,14 @@ export { platformCommon, platformBindings } from './application_ref'; + +/// See [commonBootstrap] for detailed documentation. +export function bootstrap(appComponentType: /*Type*/ any, + appBindings: Array = null): + Promise { + var bindings = [compilerBindings()]; + if (isPresent(appBindings)) { + bindings.push(appBindings); + } + return commonBootstrap(appComponentType, bindings); +} \ No newline at end of file diff --git a/modules/angular2/src/core/application_ref.ts b/modules/angular2/src/core/application_ref.ts index 730649c39e77e..ac84d1586ce76 100644 --- a/modules/angular2/src/core/application_ref.ts +++ b/modules/angular2/src/core/application_ref.ts @@ -18,7 +18,6 @@ import { import {DOM} from 'angular2/src/core/dom/dom_adapter'; import {internalView} from 'angular2/src/core/compiler/view_ref'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; -import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import { Parser, Lexer, @@ -35,7 +34,7 @@ import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/vi import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {AppViewListener} from 'angular2/src/core/compiler/view_listener'; -import {Compiler, CompilerCache} from './compiler/compiler'; +import {ProtoViewFactory} from './compiler/proto_view_factory'; import {DEFAULT_PIPES} from 'angular2/src/core/pipes'; import {ViewResolver} from './compiler/view_resolver'; import {DirectiveResolver} from './compiler/directive_resolver'; @@ -43,7 +42,10 @@ import {PipeResolver} from './compiler/pipe_resolver'; import {StyleUrlResolver} from 'angular2/src/core/render/dom/compiler/style_url_resolver'; import {UrlResolver} from 'angular2/src/core/services/url_resolver'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; -import {compilerBindings} from 'angular2/src/compiler/compiler'; +import { + APP_ID_RANDOM_BINDING, +} from 'angular2/src/core/render/render'; +import {Compiler} from 'angular2/src/core/compiler/compiler'; /** * Constructs the set of bindings meant for use at the platform level. @@ -95,15 +97,14 @@ export function applicationCommonBindings(): Array { bestChangeDetection = new JitChangeDetection(); } return [ - compilerBindings(), - ProtoViewFactory, + Compiler, + APP_ID_RANDOM_BINDING, AppViewPool, bind(APP_VIEW_POOL_CAPACITY).toValue(10000), AppViewManager, AppViewManagerUtils, AppViewListener, - Compiler, - CompilerCache, + ProtoViewFactory, ViewResolver, DEFAULT_PIPES, bind(IterableDiffers).toValue(defaultIterableDiffers), diff --git a/modules/angular2/src/core/bootstrap.dart b/modules/angular2/src/core/bootstrap.dart index 4a1340d4c543a..92eea16693f65 100644 --- a/modules/angular2/src/core/bootstrap.dart +++ b/modules/angular2/src/core/bootstrap.dart @@ -1,21 +1,3 @@ library angular2.src.core.bootstrap; -import 'dart:async'; - -import 'package:angular2/src/core/compiler/dynamic_component_loader.dart' show ComponentRef; -import 'package:angular2/src/core/reflection/reflection.dart' show reflector; -import 'package:angular2/src/core/reflection/reflection_capabilities.dart' - show ReflectionCapabilities; -import 'application_common.dart'; - -/// Starts an application from a root component. This implementation uses -/// mirrors. Angular 2 transformer automatically replaces this method with a -/// static implementation (see `application_static.dart`) that does not use -/// mirrors and produces a faster and more compact JS code. -/// -/// See [commonBootstrap] for detailed documentation. -Future bootstrap(Type appComponentType, - [List componentInjectableBindings]) { - reflector.reflectionCapabilities = new ReflectionCapabilities(); - return commonBootstrap(appComponentType, componentInjectableBindings); -} +export './application.dart' show bootstrap; diff --git a/modules/angular2/src/core/bootstrap.ts b/modules/angular2/src/core/bootstrap.ts index de60e5a36df51..aec7fd2afebbf 100644 --- a/modules/angular2/src/core/bootstrap.ts +++ b/modules/angular2/src/core/bootstrap.ts @@ -1,4 +1,4 @@ // Note: This file only exists so that Dart users can import // bootstrap from angular2/bootstrap. JS users should import // from angular2/core. -export {commonBootstrap as bootstrap} from './application_common'; +export {bootstrap} from './application'; diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts index 6877bab2b82d2..116cf7bd098a6 100644 --- a/modules/angular2/src/core/compiler/compiler.ts +++ b/modules/angular2/src/core/compiler/compiler.ts @@ -1,95 +1,13 @@ -import {Binding, resolveForwardRef, Injectable, Inject} from 'angular2/src/core/di'; -import {DEFAULT_PIPES_TOKEN} from 'angular2/src/core/pipes'; -import { - Type, - isBlank, - isType, - isPresent, - normalizeBlank, - stringify, - isArray, - isPromise -} from 'angular2/src/core/facade/lang'; +import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref'; +import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; + +import {Injectable} from 'angular2/src/core/di'; +import {Type, isBlank, stringify} from 'angular2/src/core/facade/lang'; import {BaseException} from 'angular2/src/core/facade/exceptions'; import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; -import {ListWrapper, Map, MapWrapper} from 'angular2/src/core/facade/collection'; - -import {DirectiveResolver} from './directive_resolver'; - -import {AppProtoView, AppProtoViewMergeMapping} from './view'; -import {ProtoViewRef} from './view_ref'; -import {DirectiveBinding} from './element_injector'; -import {ViewResolver} from './view_resolver'; -import {PipeResolver} from './pipe_resolver'; -import {ViewMetadata} from 'angular2/src/core/metadata'; -import {ComponentUrlMapper} from './component_url_mapper'; -import {ProtoViewFactory} from './proto_view_factory'; -import {UrlResolver} from 'angular2/src/core/services/url_resolver'; -import {AppRootUrl} from 'angular2/src/core/services/app_root_url'; -import {ElementBinder} from './element_binder'; -import {wtfStartTimeRange, wtfEndTimeRange} from '../profile/profile'; -import {PipeBinding} from '../pipes/pipe_binding'; - -import { - RenderDirectiveMetadata, - ViewDefinition, - RenderCompiler, - ViewType, - RenderProtoViewMergeMapping, - RenderProtoViewRef -} from 'angular2/src/core/render/api'; - -/** - * Cache that stores the AppProtoView of the template of a component. - * Used to prevent duplicate work and resolve cyclic dependencies. - */ -@Injectable() -export class CompilerCache { - _cache = new Map(); - _hostCache = new Map(); +import {reflector} from 'angular2/src/core/reflection/reflection'; +import {CompiledHostTemplate} from 'angular2/src/core/compiler/template_commands'; - set(component: Type, protoView: AppProtoView): void { this._cache.set(component, protoView); } - - get(component: Type): AppProtoView { - var result = this._cache.get(component); - return normalizeBlank(result); - } - - setHost(component: Type, protoView: AppProtoView): void { - this._hostCache.set(component, protoView); - } - - getHost(component: Type): AppProtoView { - var result = this._hostCache.get(component); - return normalizeBlank(result); - } - - clear(): void { - this._cache.clear(); - this._hostCache.clear(); - } -} - -/* - * ## URL Resolution - * - * ``` - * var appRootUrl: AppRootUrl = ...; - * var componentUrlMapper: ComponentUrlMapper = ...; - * var urlResolver: UrlResolver = ...; - * - * var componentType: Type = ...; - * var componentAnnotation: ComponentAnnotation = ...; - * var viewAnnotation: ViewAnnotation = ...; - * - * // Resolving a URL - * - * var url = viewAnnotation.templateUrl; - * var componentUrl = componentUrlMapper.getUrl(componentType); - * var componentResolvedUrl = urlResolver.resolve(appRootUrl.value, componentUrl); - * var templateResolvedUrl = urlResolver.resolve(componentResolvedUrl, url); - * ``` - */ /** * Low-level service for compiling {@link Component}s into {@link ProtoViewRef ProtoViews}s, which * can later be used to create and render a Component instance. @@ -99,271 +17,36 @@ export class CompilerCache { */ @Injectable() export class Compiler { - private _compiling = new Map>(); - private _appUrl: string; - private _defaultPipes: Type[]; - /** * @private */ - constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, - @Inject(DEFAULT_PIPES_TOKEN) _defaultPipes: Type[], - private _compilerCache: CompilerCache, private _viewResolver: ViewResolver, - private _componentUrlMapper: ComponentUrlMapper, private _urlResolver: UrlResolver, - private _render: RenderCompiler, private _protoViewFactory: ProtoViewFactory, - appUrl: AppRootUrl) { - this._defaultPipes = _defaultPipes; - this._appUrl = appUrl.value; - } + constructor(private _protoViewFactory: ProtoViewFactory) {} - private _bindDirective(directiveTypeOrBinding): DirectiveBinding { - if (directiveTypeOrBinding instanceof DirectiveBinding) { - return directiveTypeOrBinding; - } else if (directiveTypeOrBinding instanceof Binding) { - let annotation = this._directiveResolver.resolve(directiveTypeOrBinding.token); - return DirectiveBinding.createFromBinding(directiveTypeOrBinding, annotation); - } else { - let annotation = this._directiveResolver.resolve(directiveTypeOrBinding); - return DirectiveBinding.createFromType(directiveTypeOrBinding, annotation); - } - } - - private _bindPipe(typeOrBinding): PipeBinding { - let meta = this._pipeResolver.resolve(typeOrBinding); - return PipeBinding.createFromType(typeOrBinding, meta); - } - - /** - * Compiles a {@link Component} and returns a promise for this component's {@link ProtoViewRef}. - * - * Returns `ProtoViewRef` that can be later used to instantiate a component via - * {@link ViewContainerRef#createHostView} or {@link AppViewManager#createHostViewInContainer}. - */ compileInHost(componentType: Type): Promise { - var r = wtfStartTimeRange('Compiler#compile()', stringify(componentType)); - - var hostAppProtoView = this._compilerCache.getHost(componentType); - var hostPvPromise; - if (isPresent(hostAppProtoView)) { - hostPvPromise = PromiseWrapper.resolve(hostAppProtoView); - } else { - var componentBinding: DirectiveBinding = this._bindDirective(componentType); - Compiler._assertTypeIsComponent(componentBinding); - - var directiveMetadata = componentBinding.metadata; - hostPvPromise = this._render.compileHost(directiveMetadata) - .then((hostRenderPv) => { - var protoViews = this._protoViewFactory.createAppProtoViews( - componentBinding, hostRenderPv, [componentBinding], []); - return this._compileNestedProtoViews(protoViews, componentType, - new Map()); - }) - .then((appProtoView) => { - this._compilerCache.setHost(componentType, appProtoView); - return appProtoView; - }); - } - return hostPvPromise.then((hostAppProtoView) => { - wtfEndTimeRange(r); - return hostAppProtoView.ref; - }); - } - - private _compile(componentBinding: DirectiveBinding, - componentPath: Map): Promise| - AppProtoView { - var component = componentBinding.key.token; - var protoView = this._compilerCache.get(component); - if (isPresent(protoView)) { - // The component has already been compiled into an AppProtoView, - // returns a plain AppProtoView, not wrapped inside of a Promise, for performance reasons. - return protoView; - } - var resultPromise = this._compiling.get(component); - if (isPresent(resultPromise)) { - // The component is already being compiled, attach to the existing Promise - // instead of re-compiling the component. - // It happens when a template references a component multiple times. - return resultPromise; - } - var view = this._viewResolver.resolve(component); - - var directives = this._flattenDirectives(view); - - for (var i = 0; i < directives.length; i++) { - if (!Compiler._isValidDirective(directives[i])) { - throw new BaseException( - `Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`); + var metadatas = reflector.annotations(componentType); + var compiledHostTemplate = null; + for (var i = 0; i < metadatas.length; i++) { + var metadata = metadatas[i]; + if (metadata instanceof CompiledHostTemplate) { + compiledHostTemplate = metadata; + break; } } - - var boundDirectives = this._removeDuplicatedDirectives( - directives.map(directive => this._bindDirective(directive))); - - var pipes = this._flattenPipes(view); - var boundPipes = pipes.map(pipe => this._bindPipe(pipe)); - - var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives); - resultPromise = - this._render.compile(renderTemplate) - .then((renderPv) => { - var protoViews = this._protoViewFactory.createAppProtoViews( - componentBinding, renderPv, boundDirectives, boundPipes); - return this._compileNestedProtoViews(protoViews, component, componentPath); - }) - .then((appProtoView) => { - this._compilerCache.set(component, appProtoView); - MapWrapper.delete(this._compiling, component); - return appProtoView; - }); - this._compiling.set(component, resultPromise); - return resultPromise; - } - - private _removeDuplicatedDirectives(directives: DirectiveBinding[]): DirectiveBinding[] { - var directivesMap = new Map(); - directives.forEach((dirBinding) => { directivesMap.set(dirBinding.key.id, dirBinding); }); - return MapWrapper.values(directivesMap); - } - - private _compileNestedProtoViews(appProtoViews: AppProtoView[], componentType: Type, - componentPath: Map): Promise { - var nestedPVPromises = []; - componentPath = MapWrapper.clone(componentPath); - if (appProtoViews[0].type === ViewType.COMPONENT) { - componentPath.set(componentType, appProtoViews[0]); - } - appProtoViews.forEach(appProtoView => { - this._collectComponentElementBinders(appProtoView) - .forEach((elementBinder: ElementBinder) => { - var nestedComponent = elementBinder.componentDirective; - var nestedComponentType = nestedComponent.key.token; - var elementBinderDone = - (nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; }; - if (componentPath.has(nestedComponentType)) { - // cycle... - if (appProtoView.isEmbeddedFragment) { - throw new BaseException( - ` is used within the recursive path of ${stringify(nestedComponentType)}`); - } else if (appProtoView.type === ViewType.COMPONENT) { - throw new BaseException( - `Unconditional component cycle in ${stringify(nestedComponentType)}`); - } else { - elementBinderDone(componentPath.get(nestedComponentType)); - } - } else { - var nestedCall = this._compile(nestedComponent, componentPath); - if (isPromise(nestedCall)) { - nestedPVPromises.push((>nestedCall).then(elementBinderDone)); - } else { - elementBinderDone(nestedCall); - } - } - }); - }); - return PromiseWrapper.all(nestedPVPromises) - .then(_ => PromiseWrapper.all( - appProtoViews.map(appProtoView => this._mergeProtoView(appProtoView)))) - .then(_ => appProtoViews[0]); - } - - private _mergeProtoView(appProtoView: AppProtoView): Promise { - if (appProtoView.type !== ViewType.HOST && appProtoView.type !== ViewType.EMBEDDED) { - return null; - } - return this._render.mergeProtoViewsRecursively(this._collectMergeRenderProtoViews(appProtoView)) - .then((mergeResult: RenderProtoViewMergeMapping) => { - appProtoView.mergeMapping = new AppProtoViewMergeMapping(mergeResult); - }); - } - - private _collectMergeRenderProtoViews(appProtoView: - AppProtoView): Array { - var result = [appProtoView.render]; - for (var i = 0; i < appProtoView.elementBinders.length; i++) { - var binder = appProtoView.elementBinders[i]; - if (isPresent(binder.nestedProtoView)) { - if (binder.hasStaticComponent() || - (binder.hasEmbeddedProtoView() && binder.nestedProtoView.isEmbeddedFragment)) { - result.push(this._collectMergeRenderProtoViews(binder.nestedProtoView)); - } else { - result.push(null); - } - } - } - return result; - } - - private _collectComponentElementBinders(appProtoView: AppProtoView): ElementBinder[] { - var componentElementBinders = []; - appProtoView.elementBinders.forEach((elementBinder) => { - if (isPresent(elementBinder.componentDirective)) { - componentElementBinders.push(elementBinder); - } - }); - return componentElementBinders; - } - - private _buildRenderTemplate(component, view, directives): ViewDefinition { - var componentUrl = - this._urlResolver.resolve(this._appUrl, this._componentUrlMapper.getUrl(component)); - var templateAbsUrl = null; - var styleAbsUrls = null; - if (isPresent(view.templateUrl) && view.templateUrl.trim().length > 0) { - templateAbsUrl = this._urlResolver.resolve(componentUrl, view.templateUrl); - } else if (isPresent(view.template)) { - // Note: If we have an inline template, we also need to send - // the url for the component to the render so that it - // is able to resolve urls in stylesheets. - templateAbsUrl = componentUrl; - } - if (isPresent(view.styleUrls)) { - styleAbsUrls = - ListWrapper.map(view.styleUrls, url => this._urlResolver.resolve(componentUrl, url)); - } - return new ViewDefinition({ - componentId: stringify(component), - templateAbsUrl: templateAbsUrl, template: view.template, - styleAbsUrls: styleAbsUrls, - styles: view.styles, - directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata), - encapsulation: view.encapsulation - }); - } - - private _flattenPipes(view: ViewMetadata): any[] { - if (isBlank(view.pipes)) return this._defaultPipes; - var pipes = ListWrapper.clone(this._defaultPipes); - this._flattenList(view.pipes, pipes); - return pipes; - } - - private _flattenDirectives(view: ViewMetadata): Type[] { - if (isBlank(view.directives)) return []; - var directives = []; - this._flattenList(view.directives, directives); - return directives; - } - - private _flattenList(tree: any[], out: Array): void { - for (var i = 0; i < tree.length; i++) { - var item = resolveForwardRef(tree[i]); - if (isArray(item)) { - this._flattenList(item, out); - } else { - out.push(item); - } + if (isBlank(compiledHostTemplate)) { + throw new BaseException( + `No precompiled template for component ${stringify(componentType)} found`); } + return PromiseWrapper.resolve(this._createProtoView(compiledHostTemplate)); } - private static _isValidDirective(value: Type | Binding): boolean { - return isPresent(value) && (value instanceof Type || value instanceof Binding); + private _createProtoView(compiledHostTemplate: CompiledHostTemplate): ProtoViewRef { + return this._protoViewFactory.createHost(compiledHostTemplate).ref; } - private static _assertTypeIsComponent(directiveBinding: DirectiveBinding): void { - if (directiveBinding.metadata.type !== RenderDirectiveMetadata.COMPONENT_TYPE) { - throw new BaseException( - `Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`); - } - } + clearCache() { this._protoViewFactory.clearCache(); } } + +export function internalCreateProtoView(compiler: Compiler, + compiledHostTemplate: CompiledHostTemplate): ProtoViewRef { + return (compiler)._createProtoView(compiledHostTemplate); +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/directive_resolver.ts b/modules/angular2/src/core/compiler/directive_resolver.ts index 1b384408ca0a4..f9344bf71509a 100644 --- a/modules/angular2/src/core/compiler/directive_resolver.ts +++ b/modules/angular2/src/core/compiler/directive_resolver.ts @@ -119,7 +119,6 @@ export class DirectiveResolver { bindings: dm.bindings, exportAs: dm.exportAs, moduleId: dm.moduleId, - compileChildren: dm.compileChildren, queries: mergedQueries, changeDetection: dm.changeDetection, viewBindings: dm.viewBindings @@ -134,7 +133,6 @@ export class DirectiveResolver { bindings: dm.bindings, exportAs: dm.exportAs, moduleId: dm.moduleId, - compileChildren: dm.compileChildren, queries: mergedQueries }); } diff --git a/modules/angular2/src/core/compiler/element_binder.ts b/modules/angular2/src/core/compiler/element_binder.ts index 235516cef4ac3..4dbc1799cd428 100644 --- a/modules/angular2/src/core/compiler/element_binder.ts +++ b/modules/angular2/src/core/compiler/element_binder.ts @@ -1,26 +1,16 @@ -import {isBlank, isPresent} from 'angular2/src/core/facade/lang'; +import {isBlank} from 'angular2/src/core/facade/lang'; import {BaseException} from 'angular2/src/core/facade/exceptions'; import * as eiModule from './element_injector'; import {DirectiveBinding} from './element_injector'; import * as viewModule from './view'; export class ElementBinder { - // updated later, so we are able to resolve cycles - nestedProtoView: viewModule.AppProtoView = null; - constructor(public index: number, public parent: ElementBinder, public distanceToParent: number, public protoElementInjector: eiModule.ProtoElementInjector, - public componentDirective: DirectiveBinding) { + public componentDirective: DirectiveBinding, + public nestedProtoView: viewModule.AppProtoView) { if (isBlank(index)) { throw new BaseException('null index not allowed.'); } } - - hasStaticComponent(): boolean { - return isPresent(this.componentDirective) && isPresent(this.nestedProtoView); - } - - hasEmbeddedProtoView(): boolean { - return !isPresent(this.componentDirective) && isPresent(this.nestedProtoView); - } } diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index fbed7f7cbecd5..22faef99b8e98 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -170,7 +170,7 @@ export class DirectiveBinding extends ResolvedBinding { type: meta instanceof ComponentMetadata ? RenderDirectiveMetadata.COMPONENT_TYPE : RenderDirectiveMetadata.DIRECTIVE_TYPE, selector: meta.selector, - compileChildren: meta.compileChildren, + compileChildren: true, outputs: meta.outputs, host: isPresent(meta.host) ? MapWrapper.createFromStringMap(meta.host) : null, inputs: meta.inputs, @@ -214,6 +214,7 @@ export class DirectiveBinding extends ResolvedBinding { // TODO(rado): benchmark and consider rolling in as ElementInjector fields. export class PreBuiltObjects { + nestedView: viewModule.AppView = null; constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView, public elementRef: ElementRef, public templateRef: TemplateRef) {} } @@ -474,6 +475,8 @@ export class ElementInjector extends TreeNode implements Depend return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef()); } + getNestedView(): viewModule.AppView { return this._preBuiltObjects.nestedView; } + getView(): viewModule.AppView { return this._preBuiltObjects.view; } directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; } diff --git a/modules/angular2/src/core/compiler/element_ref.ts b/modules/angular2/src/core/compiler/element_ref.ts index 2899f11428dbf..43cfd8be3f246 100644 --- a/modules/angular2/src/core/compiler/element_ref.ts +++ b/modules/angular2/src/core/compiler/element_ref.ts @@ -33,6 +33,7 @@ export class ElementRef implements RenderElementRef { /** * @private * + * TODO(tbosch): remove this when the new compiler lands * Index of the element inside the `RenderViewRef`. * * This is used internally by the Angular framework to locate elements. @@ -42,11 +43,10 @@ export class ElementRef implements RenderElementRef { /** * @private */ - constructor(parentView: ViewRef, boundElementIndex: number, renderBoundElementIndex: number, - private _renderer: Renderer) { + constructor(parentView: ViewRef, boundElementIndex: number, private _renderer: Renderer) { this.parentView = parentView; this.boundElementIndex = boundElementIndex; - this.renderBoundElementIndex = renderBoundElementIndex; + this.renderBoundElementIndex = boundElementIndex; } /** diff --git a/modules/angular2/src/core/compiler/proto_view_factory.ts b/modules/angular2/src/core/compiler/proto_view_factory.ts index 9f00e5fae167d..9435043349d80 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.ts +++ b/modules/angular2/src/core/compiler/proto_view_factory.ts @@ -1,8 +1,5 @@ -import {Injectable} from 'angular2/src/core/di'; - import {ListWrapper, MapWrapper} from 'angular2/src/core/facade/collection'; -import {StringWrapper, isPresent, isBlank, assertionsEnabled} from 'angular2/src/core/facade/lang'; -import {BaseException} from 'angular2/src/core/facade/exceptions'; +import {isPresent, isBlank, Type, isArray, isNumber} from 'angular2/src/core/facade/lang'; import {reflector} from 'angular2/src/core/reflection/reflection'; import { @@ -17,23 +14,340 @@ import { ASTWithSource } from 'angular2/src/core/change_detection/change_detection'; -import {PipeBinding} from 'angular2/src/core/pipes/pipe_binding'; -import {ProtoPipes} from 'angular2/src/core/pipes/pipes'; - import { RenderDirectiveMetadata, RenderElementBinder, PropertyBindingType, DirectiveBinder, ProtoViewDto, - ViewType + ViewType, + RenderProtoViewRef } from 'angular2/src/core/render/api'; -import {AppProtoView} from './view'; + +import {Injectable, Binding, resolveForwardRef, Inject} from 'angular2/src/core/di'; + +import {PipeBinding} from '../pipes/pipe_binding'; +import {ProtoPipes} from '../pipes/pipes'; + +import {AppProtoView, AppProtoViewMergeInfo} from './view'; import {ElementBinder} from './element_binder'; import {ProtoElementInjector, DirectiveBinding} from './element_injector'; +import {DirectiveResolver} from './directive_resolver'; +import {ViewResolver} from './view_resolver'; +import {PipeResolver} from './pipe_resolver'; +import {ViewMetadata} from '../metadata/view'; +import {DEFAULT_PIPES_TOKEN} from 'angular2/src/core/pipes'; + +import { + visitAllCommands, + CompiledTemplate, + CompiledHostTemplate, + TemplateCmd, + CommandVisitor, + EmbeddedTemplateCmd, + BeginComponentCmd, + BeginElementCmd, + IBeginElementCmd, + TextCmd, + NgContentCmd +} from './template_commands'; + +import {Renderer} from 'angular2/render'; +import {APP_ID} from 'angular2/src/core/render/dom/dom_tokens'; + + +@Injectable() +export class ProtoViewFactory { + private _cache: Map = new Map(); + private _defaultPipes: Type[]; + private _appId: string; + + constructor(private _renderer: Renderer, @Inject(DEFAULT_PIPES_TOKEN) defaultPipes: Type[], + private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver, + private _pipeResolver: PipeResolver, @Inject(APP_ID) appId: string) { + this._defaultPipes = defaultPipes; + this._appId = appId; + } + + clearCache() { this._cache.clear(); } + + createHost(compiledHostTemplate: CompiledHostTemplate): AppProtoView { + var compiledTemplate = compiledHostTemplate.getTemplate(); + var result = this._cache.get(compiledTemplate.id); + if (isBlank(result)) { + var templateData = compiledTemplate.getData(this._appId); + result = + new AppProtoView(templateData.commands, ViewType.HOST, true, + templateData.changeDetectorFactory, null, new ProtoPipes(new Map())); + this._cache.set(compiledTemplate.id, result); + } + return result; + } + + private _createComponent(cmd: BeginComponentCmd): AppProtoView { + var nestedProtoView = this._cache.get(cmd.templateId); + if (isBlank(nestedProtoView)) { + var component = cmd.directives[0]; + var view = this._viewResolver.resolve(component); + var compiledTemplateData = cmd.template.getData(this._appId); + + this._renderer.registerComponentTemplate(cmd.templateId, compiledTemplateData.commands, + compiledTemplateData.styles); + var boundPipes = this._flattenPipes(view).map(pipe => this._bindPipe(pipe)); + + nestedProtoView = new AppProtoView(compiledTemplateData.commands, ViewType.COMPONENT, true, + compiledTemplateData.changeDetectorFactory, null, + ProtoPipes.fromBindings(boundPipes)); + // Note: The cache is updated before recursing + // to be able to resolve cycles + this._cache.set(cmd.template.id, nestedProtoView); + this._initializeProtoView(nestedProtoView, null); + } + return nestedProtoView; + } + + private _createEmbeddedTemplate(cmd: EmbeddedTemplateCmd, parent: AppProtoView): AppProtoView { + var nestedProtoView = new AppProtoView( + cmd.children, ViewType.EMBEDDED, cmd.isMerged, cmd.changeDetectorFactory, + arrayToMap(cmd.variableNameAndValues, true), new ProtoPipes(parent.pipes.config)); + if (cmd.isMerged) { + this.initializeProtoViewIfNeeded(nestedProtoView); + } + return nestedProtoView; + } + + initializeProtoViewIfNeeded(protoView: AppProtoView) { + if (!protoView.isInitialized()) { + var render = this._renderer.createProtoView(protoView.templateCmds); + this._initializeProtoView(protoView, render); + } + } + + private _initializeProtoView(protoView: AppProtoView, render: RenderProtoViewRef) { + var initializer = new _ProtoViewInitializer(protoView, this._directiveResolver, this); + visitAllCommands(initializer, protoView.templateCmds); + var mergeInfo = + new AppProtoViewMergeInfo(initializer.mergeEmbeddedViewCount, initializer.mergeElementCount, + initializer.mergeViewCount); + protoView.init(render, initializer.elementBinders, initializer.boundTextCount, mergeInfo, + initializer.variableLocations); + } + + private _bindPipe(typeOrBinding): PipeBinding { + let meta = this._pipeResolver.resolve(typeOrBinding); + return PipeBinding.createFromType(typeOrBinding, meta); + } + + private _flattenPipes(view: ViewMetadata): any[] { + if (isBlank(view.pipes)) return this._defaultPipes; + var pipes = ListWrapper.clone(this._defaultPipes); + _flattenList(view.pipes, pipes); + return pipes; + } +} + + +function createComponent(protoViewFactory: ProtoViewFactory, cmd: BeginComponentCmd): AppProtoView { + return (protoViewFactory)._createComponent(cmd); +} + +function createEmbeddedTemplate(protoViewFactory: ProtoViewFactory, cmd: EmbeddedTemplateCmd, + parent: AppProtoView): AppProtoView { + return (protoViewFactory)._createEmbeddedTemplate(cmd, parent); +} + +class _ProtoViewInitializer implements CommandVisitor { + variableLocations: Map = new Map(); + boundTextCount: number = 0; + boundElementIndex: number = 0; + elementBinderStack: ElementBinder[] = []; + distanceToParentElementBinder: number = 0; + distanceToParentProtoElementInjector: number = 0; + elementBinders: ElementBinder[] = []; + mergeEmbeddedViewCount: number = 0; + mergeElementCount: number = 0; + mergeViewCount: number = 1; + + constructor(private _protoView: AppProtoView, private _directiveResolver: DirectiveResolver, + private _protoViewFactory: ProtoViewFactory) {} + + visitText(cmd: TextCmd, context: any): any { + if (cmd.isBound) { + this.boundTextCount++; + } + return null; + } + visitNgContent(cmd: NgContentCmd, context: any): any { return null; } + visitBeginElement(cmd: BeginElementCmd, context: any): any { + if (cmd.isBound) { + this._visitBeginBoundElement(cmd, null); + } else { + this._visitBeginElement(cmd, null, null); + } + return null; + } + visitEndElement(context: any): any { return this._visitEndElement(); } + visitBeginComponent(cmd: BeginComponentCmd, context: any): any { + var nestedProtoView = createComponent(this._protoViewFactory, cmd); + return this._visitBeginBoundElement(cmd, nestedProtoView); + } + visitEndComponent(context: any): any { return this._visitEndElement(); } + visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any { + var nestedProtoView = createEmbeddedTemplate(this._protoViewFactory, cmd, this._protoView); + if (cmd.isMerged) { + this.mergeEmbeddedViewCount++; + } + this._visitBeginBoundElement(cmd, nestedProtoView); + return this._visitEndElement(); + } + + private _visitBeginBoundElement(cmd: IBeginElementCmd, nestedProtoView: AppProtoView): any { + if (isPresent(nestedProtoView) && nestedProtoView.isMergable) { + this.mergeElementCount += nestedProtoView.mergeInfo.elementCount; + this.mergeViewCount += nestedProtoView.mergeInfo.viewCount; + this.mergeEmbeddedViewCount += nestedProtoView.mergeInfo.embeddedViewCount; + } + var elementBinder = _createElementBinder( + this._directiveResolver, nestedProtoView, this.elementBinderStack, this.boundElementIndex, + this.distanceToParentElementBinder, this.distanceToParentProtoElementInjector, cmd); + this.elementBinders.push(elementBinder); + var protoElementInjector = elementBinder.protoElementInjector; + for (var i = 0; i < cmd.variableNameAndValues.length; i += 2) { + this.variableLocations.set(cmd.variableNameAndValues[i], this.boundElementIndex); + } + this.boundElementIndex++; + this.mergeElementCount++; + return this._visitBeginElement(cmd, elementBinder, protoElementInjector); + } + + private _visitBeginElement(cmd: IBeginElementCmd, elementBinder: ElementBinder, + protoElementInjector: ProtoElementInjector): any { + this.distanceToParentElementBinder = + isPresent(elementBinder) ? 1 : this.distanceToParentElementBinder + 1; + this.distanceToParentProtoElementInjector = + isPresent(protoElementInjector) ? 1 : this.distanceToParentProtoElementInjector + 1; + this.elementBinderStack.push(elementBinder); + return null; + } + + private _visitEndElement(): any { + var parentElementBinder = this.elementBinderStack.pop(); + var parentProtoElementInjector = + isPresent(parentElementBinder) ? parentElementBinder.protoElementInjector : null; + this.distanceToParentElementBinder = isPresent(parentElementBinder) ? + parentElementBinder.distanceToParent : + this.distanceToParentElementBinder - 1; + this.distanceToParentProtoElementInjector = isPresent(parentProtoElementInjector) ? + parentProtoElementInjector.distanceToParent : + this.distanceToParentProtoElementInjector - 1; + return null; + } +} + + +function _createElementBinder(directiveResolver: DirectiveResolver, nestedProtoView: AppProtoView, + elementBinderStack: ElementBinder[], boundElementIndex: number, + distanceToParentBinder: number, distanceToParentPei: number, + beginElementCmd: IBeginElementCmd): ElementBinder { + var parentElementBinder: ElementBinder = null; + var parentProtoElementInjector: ProtoElementInjector = null; + if (distanceToParentBinder > 0) { + parentElementBinder = elementBinderStack[elementBinderStack.length - distanceToParentBinder]; + } + if (isBlank(parentElementBinder)) { + distanceToParentBinder = -1; + } + if (distanceToParentPei > 0) { + var peiBinder = elementBinderStack[elementBinderStack.length - distanceToParentPei]; + if (isPresent(peiBinder)) { + parentProtoElementInjector = peiBinder.protoElementInjector; + } + } + if (isBlank(parentProtoElementInjector)) { + distanceToParentPei = -1; + } + var componentDirectiveBinding: DirectiveBinding = null; + var isEmbeddedTemplate = false; + var directiveBindings: DirectiveBinding[] = + beginElementCmd.directives.map(type => bindDirective(directiveResolver, type)); + if (beginElementCmd instanceof BeginComponentCmd) { + componentDirectiveBinding = directiveBindings[0]; + } else if (beginElementCmd instanceof EmbeddedTemplateCmd) { + isEmbeddedTemplate = true; + } + + var protoElementInjector = null; + // Create a protoElementInjector for any element that either has bindings *or* has one + // or more var- defined *or* for