diff --git a/broccoli/packages.js b/broccoli/packages.js index 2a43e290edd..983f569ef9d 100644 --- a/broccoli/packages.js +++ b/broccoli/packages.js @@ -86,7 +86,41 @@ module.exports.emberGlimmerES = function _emberGlimmerES() { } }); - return debugTree(funneled, 'ember-glimmer:output'); + let rollup = new Rollup(funneled, { + annotation: 'ember-glimmer', + rollup: { + external: [ + '@glimmer/reference', + '@glimmer/runtime', + '@glimmer/node', + '@glimmer/opcode-compiler', + '@glimmer/program', + '@glimmer/wire-format', + '@glimmer/util', + 'ember-console', + 'ember-debug', + 'ember-env-flags', + 'ember/features', + 'ember-environment', + 'ember-utils', + 'ember-metal', + 'ember-runtime', + 'ember-views', + 'ember-routing', + 'node-module', + 'rsvp', + 'container' + ], + input: 'ember-glimmer/index.js', + output: { + file: 'ember-glimmer.js', + format: 'es', + exports: 'named' + }, + }, + }); + + return debugTree(rollup, 'ember-glimmer:output'); }; module.exports.handlebarsES = function _handlebars() { diff --git a/broccoli/to-es5.js b/broccoli/to-es5.js index 913f084ef53..418c30151f4 100644 --- a/broccoli/to-es5.js +++ b/broccoli/to-es5.js @@ -36,6 +36,7 @@ module.exports = function toES5(tree, _options) { } }], ['transform-es2015-template-literals', {loose: true}], + ['transform-es2015-literals'], ['transform-es2015-arrow-functions'], ['transform-es2015-destructuring', {loose: true}], ['transform-es2015-spread', {loose: true}], diff --git a/ember-cli-build.js b/ember-cli-build.js index 9ead2974efa..e5a5b4e7010 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -35,12 +35,12 @@ const { rollupEmberMetal } = require('./broccoli/packages'); const SHOULD_ROLLUP = true; +const ENV = process.env.EMBER_ENV || 'development'; module.exports = function() { let loader = internalLoader(); let license = emberLicense(); let nodeModule = nodeModuleUtils(); - let ENV = process.env.EMBER_ENV || 'development'; let debugFeatures = toES5(emberFeaturesES()); let version = toES5(emberVersionES()); let emberTesting = emberPkgES('ember-testing'); @@ -49,7 +49,6 @@ module.exports = function() { let emberDebugES5 = toES5(emberDebug, { annotation: 'ember-debug' }); let emberTemplateCompiler = emberPkgES('ember-template-compiler'); let emberTemplateCompilerES5 = toES5(emberTemplateCompiler, { annotation: 'ember-template-compiler' }); - let babelDebugHelpersES5 = toES5(babelHelpers('debug'), { annotation: 'babel helpers debug' }); let inlineParser = toES5(handlebarsES(), { annotation: 'handlebars' }); let tokenizer = toES5(simpleHTMLTokenizerES(), { annotation: 'tokenizer' }); @@ -309,11 +308,23 @@ module.exports = function() { }; function dependenciesES6() { + let glimmerEntries = ['@glimmer/node', '@glimmer/runtime']; + if (ENV === 'development') { + let hasGlimmerDebug = true; + try { + require('@glimmer/debug'); + } catch (e) { + hasGlimmerDebug = false; + } + if (hasGlimmerDebug) { + glimmerEntries.push('@glimmer/debug', '@glimmer/local-debug-flags'); + } + } return [ dagES(), routerES(), routeRecognizerES(), - ...glimmerTrees(['@glimmer/node', '@glimmer/runtime']), + ...glimmerTrees(glimmerEntries), ]; } diff --git a/package.json b/package.json index f88eaf9e98a..12c183a62e6 100644 --- a/package.json +++ b/package.json @@ -59,10 +59,13 @@ "resolve": "^1.5.0" }, "devDependencies": { - "@glimmer/compiler": "^0.25.6", - "@glimmer/node": "^0.25.6", - "@glimmer/reference": "^0.25.6", - "@glimmer/runtime": "^0.25.6", + "@glimmer/compiler": "^0.30.5", + "@glimmer/runtime": "^0.30.5", + "@glimmer/node": "^0.30.5", + "@glimmer/interfaces": "^0.30.5", + "@glimmer/reference": "^0.30.5", + "@glimmer/opcode-compiler": "^0.30.5", + "@glimmer/program": "^0.30.5", "@types/rsvp": "^4.0.1", "auto-dist-tag": "^0.1.5", "aws-sdk": "^2.46.0", @@ -74,6 +77,7 @@ "babel-plugin-transform-es2015-classes": "^6.24.1", "babel-plugin-transform-es2015-computed-properties": "^6.24.1", "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", "babel-plugin-transform-es2015-modules-amd": "^6.24.1", "babel-plugin-transform-es2015-parameters": "^6.24.1", "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", diff --git a/packages/container/lib/index.d.ts b/packages/container/lib/index.d.ts index fe97d1abc0f..5e81cd19343 100644 --- a/packages/container/lib/index.d.ts +++ b/packages/container/lib/index.d.ts @@ -1,6 +1,7 @@ +import { Owner } from 'ember-utils'; interface Container { registry: any; - owner: any | null; + owner: Owner | null; cache: any | null; factoryManagerCache: any | null; isDestroyed: boolean; @@ -14,4 +15,5 @@ interface Container { } export const privatize: any; export const Registry: any; -export const Container: (registry: any, options?: any) => Container; \ No newline at end of file +export const Container: (registry: any, options?: any) => Container; +export const FACTORY_FOR: WeakMap; diff --git a/packages/ember-application/lib/system/engine-instance.js b/packages/ember-application/lib/system/engine-instance.js index 4afbbf14c47..7a6ae545e87 100644 --- a/packages/ember-application/lib/system/engine-instance.js +++ b/packages/ember-application/lib/system/engine-instance.js @@ -179,6 +179,7 @@ const EngineInstance = EmberObject.extend(RegistryProxyMixin, ContainerProxyMixi '-view-registry:main', `renderer:-${env.isInteractive ? 'dom' : 'inert'}`, 'service:-document', + P`template-options:main`, ]; if (env.isInteractive) { diff --git a/packages/ember-application/tests/system/application_test.js b/packages/ember-application/tests/system/application_test.js index a92e767454e..624b95619a5 100644 --- a/packages/ember-application/tests/system/application_test.js +++ b/packages/ember-application/tests/system/application_test.js @@ -164,7 +164,7 @@ moduleFor('Ember.Application', class extends ApplicationTestCase { verifyRegistration(assert, application, P`template:components/-default`); verifyRegistration(assert, application, 'template:-outlet'); verifyInjection(assert, application, 'view:-outlet', 'template', 'template:-outlet'); - verifyInjection(assert, application, 'template', 'env', 'service:-glimmer-environment'); + verifyInjection(assert, application, 'template', 'options', P`template-options:main`); assert.deepEqual(application.registeredOptionsForType('helper'), { instantiate: false }, `optionsForType 'helper'`); } diff --git a/packages/ember-application/tests/system/engine_test.js b/packages/ember-application/tests/system/engine_test.js index 41cf7a9c5c6..8b592037443 100644 --- a/packages/ember-application/tests/system/engine_test.js +++ b/packages/ember-application/tests/system/engine_test.js @@ -77,7 +77,7 @@ moduleFor('Ember.Engine', class extends TestCase { verifyRegistration(assert, engine, P`template:components/-default`); verifyRegistration(assert, engine, 'template:-outlet'); verifyInjection(assert, engine, 'view:-outlet', 'template', 'template:-outlet'); - verifyInjection(assert, engine, 'template', 'env', 'service:-glimmer-environment'); + verifyInjection(assert, engine, 'template', 'options', P`template-options:main`); assert.deepEqual(engine.registeredOptionsForType('helper'), { instantiate: false }, `optionsForType 'helper'`); } }); diff --git a/packages/ember-glimmer/lib/compile-time-lookup.ts b/packages/ember-glimmer/lib/compile-time-lookup.ts new file mode 100644 index 00000000000..dbf759c670f --- /dev/null +++ b/packages/ember-glimmer/lib/compile-time-lookup.ts @@ -0,0 +1,51 @@ +import { ComponentCapabilities, Option, ProgramSymbolTable } from '@glimmer/interfaces'; +import { CompileTimeLookup as ICompileTimeLookup } from '@glimmer/opcode-compiler'; +import { CompilableTemplate, ComponentDefinition, ComponentManager, WithStaticLayout } from '@glimmer/runtime'; +import { OwnedTemplateMeta } from 'ember-views'; +import RuntimeResolver from './resolver'; + +interface StaticComponentManager extends WithStaticLayout, + ComponentManager { +} + +export default class CompileTimeLookup implements ICompileTimeLookup { + constructor(private resolver: RuntimeResolver) {} + + getCapabilities(handle: number): ComponentCapabilities { + let definition = this.resolver.resolve>(handle); + let { manager, state } = definition!; + return manager.getCapabilities(state); + } + + getLayout(handle: number): Option> { + const { manager, state } = this.resolver.resolve>>(handle); + const capabilities = manager.getCapabilities(state); + + if (capabilities.dynamicLayout) { + return null; + } + + const invocation = manager.getLayout(state, this.resolver); + return { + // TODO: this seems weird, it already is compiled + compile() { return invocation.handle; }, + symbolTable: invocation.symbolTable + }; + } + + lookupHelper(name: string, referrer: OwnedTemplateMeta): Option { + return this.resolver.lookupHelper(name, referrer); + } + + lookupModifier(name: string, referrer: OwnedTemplateMeta): Option { + return this.resolver.lookupModifier(name, referrer); + } + + lookupComponentDefinition(name: string, referrer: OwnedTemplateMeta): Option { + return this.resolver.lookupComponentDefinition(name, referrer); + } + + lookupPartial(name: string, referrer: OwnedTemplateMeta): Option { + return this.resolver.lookupPartial(name, referrer); + } +} diff --git a/packages/ember-glimmer/lib/component-managers/abstract.ts b/packages/ember-glimmer/lib/component-managers/abstract.ts index d77be67a0ab..8c67a571a47 100644 --- a/packages/ember-glimmer/lib/component-managers/abstract.ts +++ b/packages/ember-glimmer/lib/component-managers/abstract.ts @@ -1,16 +1,17 @@ -import { ProgramSymbolTable } from '@glimmer/interfaces'; +import { + ComponentCapabilities, + Simple, +} from '@glimmer/interfaces'; import { Tag, VersionedPathReference } from '@glimmer/reference'; import { + Arguments, Bounds, - CompiledDynamicTemplate, - ComponentDefinition, ComponentManager, DynamicScope, ElementOperations, Environment, PreparedArguments, } from '@glimmer/runtime'; -import { IArguments } from '@glimmer/runtime/dist/types/lib/vm/arguments'; import { Destroyable, Opaque, @@ -23,7 +24,7 @@ import DebugStack from '../utils/debug-stack'; // tslint:disable-next-line:max-line-length // https://github.com/glimmerjs/glimmer-vm/blob/v0.24.0-beta.4/packages/%40glimmer/runtime/lib/component/interfaces.ts#L21 -export default abstract class AbstractManager implements ComponentManager { +export default abstract class AbstractManager implements ComponentManager { public debugStack: typeof DebugStack; public _pushToDebugStack: (name: string, environment: any) => void; public _pushEngineToDebugStack: (name: string, environment: any) => void; @@ -32,7 +33,7 @@ export default abstract class AbstractManager implements ComponentManager this.debugStack = undefined; } - prepareArgs(_definition: ComponentDefinition, _args: IArguments): Option { + prepareArgs(_state: U, _args: Arguments): Option { return null; } @@ -42,18 +43,16 @@ export default abstract class AbstractManager implements ComponentManager abstract create( env: Environment, - definition: ComponentDefinition, - args: IArguments, + definition: U, + args: Arguments, dynamicScope: DynamicScope, caller: VersionedPathReference, hasDefaultBlock: boolean): T; - abstract layoutFor( - definition: ComponentDefinition, - component: T, - env: Environment): CompiledDynamicTemplate; + abstract getSelf(component: T): VersionedPathReference; + abstract getCapabilities(state: U): ComponentCapabilities; - didCreateElement(_component: T, _element: Element, _operations: ElementOperations): void { + didCreateElement(_component: T, _element: Simple.Element, _operations: ElementOperations): void { // noop } @@ -68,7 +67,7 @@ export default abstract class AbstractManager implements ComponentManager // noop } - getTag(_bucket: T): Option { return null; } + abstract getTag(_bucket: T): Tag; // inheritors should also call `this._pushToDebugStack` // to ensure the rerendering assertion messages are diff --git a/packages/ember-glimmer/lib/component-managers/curly.ts b/packages/ember-glimmer/lib/component-managers/curly.ts index ff205775f09..8e2bebec5fe 100644 --- a/packages/ember-glimmer/lib/component-managers/curly.ts +++ b/packages/ember-glimmer/lib/component-managers/curly.ts @@ -1,37 +1,44 @@ -import { Option } from '@glimmer/interfaces'; import { - combineTagged, + ComponentCapabilities, + Option, + ProgramSymbolTable, + Simple, + VMHandle +} from '@glimmer/interfaces'; +import { + combine, Tag, VersionedPathReference, } from '@glimmer/reference'; import { Arguments, Bounds, - CompiledDynamicProgram, - ComponentClass, ComponentDefinition, - ComponentManager, ElementOperations, + Invocation, PreparedArguments, PrimitiveReference, - Simple, - VM, + WithDynamicLayout, + WithDynamicTagName, + WithStaticLayout, } from '@glimmer/runtime'; -import { Destroyable, Opaque } from '@glimmer/util'; +import { Destroyable, EMPTY_ARRAY, Opaque } from '@glimmer/util'; import { privatize as P } from 'container'; import { assert, + deprecate, } from 'ember-debug'; import { DEBUG } from 'ember-env-flags'; import { - _instrumentStart, - get, + _instrumentStart, get, } from 'ember-metal'; +import { String as StringUtils } from 'ember-runtime'; import { assign, - OWNER, + getOwner, + guidFor, } from 'ember-utils'; -import { setViewElement } from 'ember-views'; +import { OwnedTemplateMeta, setViewElement } from 'ember-views'; import { BOUNDS, DIRTY_TAG, @@ -41,18 +48,22 @@ import { } from '../component'; import Environment from '../environment'; import { DynamicScope } from '../renderer'; -import { OwnedTemplate, WrappedTemplateFactory } from '../template'; +import RuntimeResolver from '../resolver'; +import { + Factory as TemplateFactory, + OwnedTemplate +} from '../template'; import { AttributeBinding, ClassNameBinding, + ColonClassNameBindingReference, IsVisibleBinding, + referenceForKey } from '../utils/bindings'; import ComponentStateBucket, { Component } from '../utils/curly-component-state-bucket'; import { processComponentArgs } from '../utils/process-args'; -import { PropertyReference } from '../utils/references'; import AbstractManager from './abstract'; - -const DEFAULT_LAYOUT = P`template:components/-default`; +import DefinitionState from './definition-state'; function aliasIdToElementId(args: Arguments, props: any) { if (args.named.has('id')) { @@ -62,10 +73,14 @@ function aliasIdToElementId(args: Arguments, props: any) { } } +function isTemplateFactory(template: OwnedTemplate | TemplateFactory): template is TemplateFactory { + return typeof (template as TemplateFactory).create === 'function'; +} + // We must traverse the attributeBindings in reverse keeping track of // what has already been applied. This is essentially refining the concatenated // properties applying right to left. -function applyAttributeBindings(element: Simple.Element, attributeBindings: any, component: Component, operations: ElementOperations) { +function applyAttributeBindings(element: Simple.Element, attributeBindings: Array, component: Component, operations: ElementOperations) { let seen: string[] = []; let i = attributeBindings.length - 1; @@ -83,7 +98,7 @@ function applyAttributeBindings(element: Simple.Element, attributeBindings: any, } if (seen.indexOf('id') === -1) { - operations.addStaticAttribute(element, 'id', component.elementId); + operations.setAttribute('id', PrimitiveReference.create(component.elementId), true, null); } if (seen.indexOf('style') === -1) { @@ -91,112 +106,105 @@ function applyAttributeBindings(element: Simple.Element, attributeBindings: any, } } -function tagName(vm: VM) { - let dynamicScope: DynamicScope = vm.dynamicScope() as DynamicScope; - // tslint:disable-next-line:no-shadowed-variable - let { tagName } = dynamicScope.view!; - return PrimitiveReference.create(tagName === '' ? null : tagName || 'div'); -} - -function ariaRole(vm: VM) { - return vm.getSelf().get('ariaRole'); -} - -class CurlyComponentLayoutCompiler { - static id: string; - public template: WrappedTemplateFactory; +const DEFAULT_LAYOUT = P`template:components/-default`; - constructor(template: WrappedTemplateFactory) { - this.template = template; +export default class CurlyComponentManager extends AbstractManager + implements WithStaticLayout, + WithDynamicTagName, + WithDynamicLayout { + + getLayout(state: DefinitionState, _resolver: RuntimeResolver): Invocation { + return { + // TODO fix + handle: state.handle as any as number, + symbolTable: state.symbolTable! + }; } - compile(builder: any) { - builder.wrapLayout(this.template); - builder.tag.dynamic(tagName); - builder.attrs.dynamic('role', ariaRole); - builder.attrs.static('class', 'ember-view'); + templateFor(component: Component, resolver: RuntimeResolver): OwnedTemplate { + let layout = get(component, 'layout') as TemplateFactory | OwnedTemplate | undefined; + if (layout !== undefined) { + // This needs to be cached by template.id + if (isTemplateFactory(layout)) { + return resolver.createTemplate(layout, getOwner(component)); + } else { + // we were provided an instance already + return layout; + } + } + let owner = getOwner(component); + let layoutName = get(component, 'layoutName'); + if (layoutName) { + let template = owner.lookup('template:' + layoutName); + if (template) { + return template; + } + } + return owner.lookup(DEFAULT_LAYOUT); } -} - -CurlyComponentLayoutCompiler.id = 'curly'; - -export class PositionalArgumentReference { - public tag: any; - private _references: any; - constructor(references: any) { - this.tag = combineTagged(references); - this._references = references; + getDynamicLayout({ component }: ComponentStateBucket, resolver: RuntimeResolver): Invocation { + const template = this.templateFor(component, resolver); + const layout = resolver.getWrappedLayout(template, CURLY_CAPABILITIES); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable, + }; } - value() { - return this._references.map((reference: any) => reference.value()); + getTagName(state: ComponentStateBucket): Option { + const { component } = state; + if (component.tagName === '') { + return null; + } + return (component && component.tagName) || 'div'; } - get(key: string) { - return PropertyReference.create(this, key); + getCapabilities(state: DefinitionState) { + return state.capabilities; } -} - -export default class CurlyComponentManager extends AbstractManager { - prepareArgs(definition: CurlyComponentDefinition, args: Arguments): Option { - let componentPositionalParamsDefinition = definition.ComponentClass.class.positionalParams; - - if (DEBUG && componentPositionalParamsDefinition) { - validatePositionalParameters(args.named, args.positional, componentPositionalParamsDefinition); - } - let componentHasRestStylePositionalParams = typeof componentPositionalParamsDefinition === 'string'; - let componentHasPositionalParams = componentHasRestStylePositionalParams || - componentPositionalParamsDefinition.length > 0; - let needsPositionalParamMunging = componentHasPositionalParams && args.positional.length !== 0; - let isClosureComponent = definition.args; + prepareArgs(state: DefinitionState, args: Arguments): Option { + const { positionalParams } = state.ComponentClass.class; - if (!needsPositionalParamMunging && !isClosureComponent) { + // early exits + if (positionalParams === undefined || positionalParams === null || args.positional.length === 0) { return null; } - let capturedArgs = args.capture(); - // grab raw positional references array - let positional = capturedArgs.positional.references; - - // handle prep for closure component with positional params - let curriedNamed; - if (definition.args) { - let remainingDefinitionPositionals = definition.args.positional.slice(positional.length); - positional = positional.concat(remainingDefinitionPositionals); - curriedNamed = definition.args.named; - } - - // handle positionalParams - let positionalParamsToNamed; - if (componentHasRestStylePositionalParams) { - positionalParamsToNamed = { - [componentPositionalParamsDefinition]: new PositionalArgumentReference(positional), - }; - positional = []; - } else if (componentHasPositionalParams) { - positionalParamsToNamed = {}; - let length = Math.min(positional.length, componentPositionalParamsDefinition.length); - for (let i = 0; i < length; i++) { - let name = componentPositionalParamsDefinition[i]; - positionalParamsToNamed[name] = positional[i]; + let named: PreparedArguments['named']; + + if (typeof positionalParams === 'string') { + assert(`You cannot specify positional parameters and the hash argument \`${positionalParams}\`.`, !args.named.has(positionalParams)); + named = { [positionalParams]: args.positional.capture() }; + assign(named, args.named.capture().map); + } else if (Array.isArray(positionalParams) && positionalParams.length > 0) { + const count = Math.min(positionalParams.length, args.positional.length); + named = {}; + assign(named, args.named.capture().map); + for (let i=0; i, hasBlock: boolean): ComponentStateBucket { + create(environment: Environment, state: DefinitionState, args: Arguments, dynamicScope: DynamicScope, callerSelfRef: VersionedPathReference, hasBlock: boolean): ComponentStateBucket { if (DEBUG) { - this._pushToDebugStack(`component:${definition.name}`, environment); + this._pushToDebugStack(`component:${state.name}`, environment); } let parentView = dynamicScope.view; - let factory = definition.ComponentClass; + let factory = state.ComponentClass; let capturedArgs = args.named.capture(); let props = processComponentArgs(capturedArgs); @@ -208,6 +216,11 @@ export default class CurlyComponentManager extends AbstractManager { return component[ROOT_REF]; } - didCreateElement({ component, classRef, environment }: ComponentStateBucket, element: Element, operations: ElementOperations): void { + didCreateElement({ component, classRef, environment }: ComponentStateBucket, element: HTMLElement, operations: ElementOperations): void { setViewElement(component, element); let { attributeBindings, classNames, classNameBindings } = component; + operations.setAttribute('id', PrimitiveReference.create(guidFor(component)), false, null); + if (attributeBindings && attributeBindings.length) { applyAttributeBindings(element, attributeBindings, component, operations); } else { - operations.addStaticAttribute(element, 'id', component.elementId); + if (component.elementId) { + operations.setAttribute('id', PrimitiveReference.create(component.elementId), false, null); + } IsVisibleBinding.install(element, component, operations); } - if (classRef) { - // TODO should make addDynamicAttribute accept an opaque - operations.addDynamicAttribute(element, 'class', classRef as any, false); + if (classRef && classRef.value()) { + const ref = classRef.value() === true ? + new ColonClassNameBindingReference(classRef, StringUtils.dasherize(classRef['_propertyKey']), null) : + classRef; + operations.setAttribute('class', ref, false, null); } if (classNames && classNames.length) { classNames.forEach((name: string) => { - operations.addStaticAttribute(element, 'class', name); + operations.setAttribute('class', PrimitiveReference.create(name), false, null); }); } if (classNameBindings && classNameBindings.length) { - classNameBindings.forEach((binding: any) => { + classNameBindings.forEach((binding: string) => { ClassNameBinding.install(element, component, binding, operations); }); } + operations.setAttribute('class', PrimitiveReference.create('ember-view'), false, null); + + const ariaRole = get(component, 'ariaRole'); + if (ariaRole) { + operations.setAttribute('role', referenceForKey(component, 'ariaRole'), false, null); + } component._transitionTo('hasElement'); @@ -321,8 +322,9 @@ export default class CurlyComponentManager extends AbstractManager { - return component[DIRTY_TAG]; + getTag({ args, component }: ComponentStateBucket): Tag { + return args ? combine([args.tag, component[DIRTY_TAG]]) : + component[DIRTY_TAG]; } didCreate({ component, environment }: ComponentStateBucket): void { @@ -342,10 +344,10 @@ export default class CurlyComponentManager extends AbstractManager { +export const CURLY_CAPABILITIES: ComponentCapabilities = { + dynamicLayout: true, + dynamicTag: true, + prepareArgs: true, + createArgs: true, + attributeHook: true, + elementHook: true +}; + +const CURLY_COMPONENT_MANAGER = new CurlyComponentManager(); +export class CurlyComponentDefinition implements ComponentDefinition { public template: OwnedTemplate; public args: CurriedArgs | undefined; + public state: DefinitionState; + public symbolTable: ProgramSymbolTable | undefined; // tslint:disable-next-line:no-shadowed-variable - constructor(name: string, ComponentClass: ComponentClass, template: OwnedTemplate, args: CurriedArgs | undefined, customManager?: ComponentManager) { - super(name, customManager || MANAGER, ComponentClass); + constructor(public name: string, public manager: CurlyComponentManager = CURLY_COMPONENT_MANAGER, public ComponentClass: any, public handle: Option, template: OwnedTemplate, args?: CurriedArgs) { + const layout = template && template.asLayout(); + const symbolTable = layout ? layout.symbolTable : undefined; + this.symbolTable = symbolTable; this.template = template; this.args = args; + this.state = { + name, + ComponentClass, + handle, + template, + capabilities: CURLY_CAPABILITIES, + symbolTable + }; } } diff --git a/packages/ember-glimmer/lib/component-managers/definition-state.ts b/packages/ember-glimmer/lib/component-managers/definition-state.ts new file mode 100644 index 00000000000..4b0f701703c --- /dev/null +++ b/packages/ember-glimmer/lib/component-managers/definition-state.ts @@ -0,0 +1,19 @@ +import { + ComponentCapabilities, + ProgramSymbolTable, + VMHandle +} from '@glimmer/interfaces'; +import { + Option +} from '@glimmer/util'; +import { Factory } from 'ember-utils'; +import { Component } from '../utils/curly-component-state-bucket'; + +export default interface DefinitionState { + capabilities: ComponentCapabilities; + name: string; + ComponentClass: Factory; + handle: Option; + symbolTable?: ProgramSymbolTable; + template?: any; +} diff --git a/packages/ember-glimmer/lib/component-managers/mount.ts b/packages/ember-glimmer/lib/component-managers/mount.ts index 32d74fbc2d2..836da32e4e5 100644 --- a/packages/ember-glimmer/lib/component-managers/mount.ts +++ b/packages/ember-glimmer/lib/component-managers/mount.ts @@ -1,80 +1,135 @@ import { - Arguments, + ComponentCapabilities, +} from '@glimmer/interfaces'; +import { + CONSTANT_TAG, + Tag, + VersionedPathReference, +} from '@glimmer/reference'; +import { ComponentDefinition, + Invocation, + WithDynamicLayout, } from '@glimmer/runtime'; import { Destroyable, Opaque, Option } from '@glimmer/util'; -import { - VersionedPathReference -} from '@glimmer/reference'; import { DEBUG } from 'ember-env-flags'; import { generateControllerFactory } from 'ember-routing'; +import { OwnedTemplateMeta } from 'ember-views'; import { EMBER_ENGINES_MOUNT_PARAMS } from 'ember/features'; -import { RootReference } from '../utils/references'; import Environment from '../environment'; +import RuntimeResolver from '../resolver'; +import { OwnedTemplate } from '../template'; +import { RootReference } from '../utils/references'; import AbstractManager from './abstract'; -import { OutletLayoutCompiler } from './outlet'; // TODO: remove these stubbed interfaces when better typing is in place -interface EngineType { +interface Engine { boot(): void; destroy(): void; lookup(name: string): any; factoryFor(name: string): any; } -interface EngineBucket { - engine: EngineType; - controller?: any; - modelReference?: any; - modelRevision?: any; +interface EngineState { + engine: Engine; + controller: any; + self: RootReference; + tag: Tag; } -class MountManager extends AbstractManager { - create(environment: Environment, { name }: ComponentDefinition, args: Arguments) { - if (DEBUG) { - this._pushEngineToDebugStack(`engine:${name}`, environment); - } +interface EngineWithModelState extends EngineState { + modelRef: VersionedPathReference; + modelRev: number; +} - let engine = environment.owner.buildChildEngineInstance(name); +interface EngineDefinitionState { + name: string; + modelRef: VersionedPathReference | undefined; +} - engine.boot(); +const CAPABILITIES = { + dynamicLayout: true, + dynamicTag: false, + prepareArgs: false, + createArgs: false, + attributeHook: false, + elementHook: false +}; + +class MountManager extends AbstractManager + implements WithDynamicLayout { + + getDynamicLayout(state: EngineState, _: RuntimeResolver): Invocation { + let template = state.engine.lookup('template:application') as OwnedTemplate; + let layout = template.asLayout(); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable + }; + } - let bucket: EngineBucket = { engine }; + getCapabilities(): ComponentCapabilities { + return CAPABILITIES; + } - if (EMBER_ENGINES_MOUNT_PARAMS) { - bucket.modelReference = args.named.get('model'); + create(environment: Environment, state: EngineDefinitionState) { + if (DEBUG) { + this._pushEngineToDebugStack(`engine:${state.name}`, environment); } - return bucket; - } + // TODO + // mount is a runtime helper, this shouldn't use dynamic layout + // we should resolve the engine app template in the helper + // it also should use the owner that looked up the mount helper. - layoutFor(_definition: ComponentDefinition, { engine }: EngineBucket, env: Environment) { - let template = engine.lookup(`template:application`); - return env.getCompiledBlock(OutletLayoutCompiler, template); - } + let engine = environment.owner.buildChildEngineInstance(state.name); - getSelf(bucket: EngineBucket): VersionedPathReference { - let { engine, modelReference } = bucket; + engine.boot(); let applicationFactory = engine.factoryFor(`controller:application`); let controllerFactory = applicationFactory || generateControllerFactory(engine, 'application'); - let controller = bucket.controller = controllerFactory.create(); - + let controller: any; + let self: RootReference; + let bucket: EngineState | EngineWithModelState; + let tag: Tag; if (EMBER_ENGINES_MOUNT_PARAMS) { - let model = modelReference.value(); - bucket.modelRevision = modelReference.tag.value(); - controller.set('model', model); + let modelRef = state.modelRef; + if (modelRef === undefined) { + controller = controllerFactory.create(); + self = new RootReference(controller); + tag = CONSTANT_TAG; + bucket = { engine, controller, self, tag }; + } else { + let model = modelRef.value(); + let modelRev = modelRef.tag.value(); + controller = controllerFactory.create({ model }); + self = new RootReference(controller); + tag = modelRef.tag; + bucket = { engine, controller, self, tag, modelRef, modelRev }; + } + } else { + controller = controllerFactory.create(); + self = new RootReference(controller); + tag = CONSTANT_TAG; + bucket = { engine, controller, self, tag }; } + return bucket; + } - return new RootReference(controller); + getSelf({ self }: EngineState): VersionedPathReference { + return self; } - getDestructor({ engine }: EngineBucket): Option { + getTag(state: EngineState | EngineWithModelState): Tag { + return state.tag; + } + + getDestructor({ engine }: EngineState): Option { return engine; } @@ -84,13 +139,12 @@ class MountManager extends AbstractManager { } } - update(bucket: EngineBucket): void { + update(bucket: EngineWithModelState): void { if (EMBER_ENGINES_MOUNT_PARAMS) { - let { controller, modelReference, modelRevision } = bucket; - - if (!modelReference.tag.validate(modelRevision)) { - let model = modelReference.value(); - bucket.modelRevision = modelReference.tag.value(); + let { controller, modelRef, modelRev } = bucket; + if (!modelRef.tag.validate(modelRev!)) { + let model = modelRef.value(); + bucket.modelRev = modelRef.tag.value(); controller.set('model', model); } } @@ -99,8 +153,11 @@ class MountManager extends AbstractManager { const MOUNT_MANAGER = new MountManager(); -export class MountDefinition extends ComponentDefinition { - constructor(name: string) { - super(name, MOUNT_MANAGER, null); +export class MountDefinition implements ComponentDefinition { + public state: EngineDefinitionState; + public manager = MOUNT_MANAGER; + + constructor(name: string, modelRef: VersionedPathReference | undefined) { + this.state = { name, modelRef }; } } diff --git a/packages/ember-glimmer/lib/component-managers/outlet.ts b/packages/ember-glimmer/lib/component-managers/outlet.ts index 4d8aa8311d1..77949935afb 100644 --- a/packages/ember-glimmer/lib/component-managers/outlet.ts +++ b/packages/ember-glimmer/lib/component-managers/outlet.ts @@ -1,155 +1,169 @@ -import { Option } from '@glimmer/interfaces'; +import { ComponentCapabilities, Option, Unique } from '@glimmer/interfaces'; +import { + CONSTANT_TAG, Tag, VersionedPathReference +} from '@glimmer/reference'; import { Arguments, ComponentDefinition, - DynamicScope, + ElementOperations, Environment, + Invocation, + UNDEFINED_REFERENCE, + WithDynamicTagName, + WithStaticLayout } from '@glimmer/runtime'; -import { Destroyable } from '@glimmer/util/dist/types'; -import { ENV } from 'ember-environment'; +import { Destroyable } from '@glimmer/util'; import { DEBUG } from 'ember-env-flags'; +import { ENV } from 'ember-environment'; import { _instrumentStart } from 'ember-metal'; -import { generateGuid, guidFor } from 'ember-utils'; -import EmberEnvironment from '../environment'; +import { assign, guidFor } from 'ember-utils'; +import { OwnedTemplateMeta } from 'ember-views'; +import { DynamicScope } from '../renderer'; +import RuntimeResolver from '../resolver'; import { OwnedTemplate, - WrappedTemplateFactory, } from '../template'; +import { OutletState } from '../utils/outlet'; import { RootReference } from '../utils/references'; +import OutletView from '../views/outlet'; import AbstractManager from './abstract'; -function instrumentationPayload({ render: { name, outlet } }: {render: {name: string, outlet: string}}) { - return { object: `${name}:${outlet}` }; +function instrumentationPayload(def: OutletDefinitionState) { + return { object: `${def.name}:${def.outlet}` }; } -function NOOP() {/**/} +interface OutletInstanceState { + self: VersionedPathReference; + finalize: () => void; +} -interface OutletDynamicScope extends DynamicScope { - outletState: any; +export interface OutletDefinitionState { + ref: VersionedPathReference; + name: string; + outlet: string; + template: OwnedTemplate; + controller: any | undefined; } -class StateBucket { - public outletState: any; - public finalizer: any; +const CAPABILITIES: ComponentCapabilities = { + dynamicLayout: false, + dynamicTag: false, + prepareArgs: false, + createArgs: false, + attributeHook: false, + elementHook: false +}; - constructor(outletState: any) { - this.outletState = outletState; - this.instrument(); - } +class OutletComponentManager extends AbstractManager + implements WithStaticLayout { - instrument() { - this.finalizer = _instrumentStart('render.outlet', instrumentationPayload, this.outletState); - } - - finalize() { - let { finalizer } = this; - finalizer(); - this.finalizer = NOOP; - } -} - -class OutletComponentManager extends AbstractManager { create(environment: Environment, - definition: OutletComponentDefinition, + definition: OutletDefinitionState, _args: Arguments, - dynamicScope: OutletDynamicScope) { + dynamicScope: DynamicScope) { if (DEBUG) { - this._pushToDebugStack(`template:${definition.template.meta.moduleName}`, environment); + this._pushToDebugStack(`template:${definition.template.referrer.moduleName}`, environment); } + dynamicScope.outletState = definition.ref; - let outletStateReference = dynamicScope.outletState = - dynamicScope.outletState.get('outlets').get(definition.outletName); - let outletState = outletStateReference.value(); - return new StateBucket(outletState); - } + // this is only used for render helper which is legacy + if (dynamicScope.rootOutletState === undefined) { + dynamicScope.rootOutletState = dynamicScope.outletState; + } - layoutFor(definition: OutletComponentDefinition, _bucket: StateBucket, env: Environment) { - return (env as EmberEnvironment).getCompiledBlock(OutletLayoutCompiler, definition.template); + let controller = definition.controller; + let self = controller === undefined ? UNDEFINED_REFERENCE : new RootReference(controller); + return { + self, + finalize: _instrumentStart('render.outlet', instrumentationPayload, definition), + }; } - getSelf({ outletState }: StateBucket) { - return new RootReference(outletState.render.controller); + layoutFor(_state: OutletDefinitionState, _component: OutletInstanceState, _env: Environment): Unique<'Handle'> { + throw new Error('Method not implemented.'); } - didRenderLayout(bucket: StateBucket) { - bucket.finalize(); - - if (DEBUG) { - this.debugStack.pop(); - } + getLayout({ template }: OutletDefinitionState, _resolver: RuntimeResolver): Invocation { + // The router has already resolved the template + const layout = template.asLayout(); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable + }; } - getDestructor(): Option { - return null; + getCapabilities(): ComponentCapabilities { + return CAPABILITIES; } -} - -const MANAGER = new OutletComponentManager(); -class WrappedTopLevelOutletLayoutCompiler { - static id = 'wrapped-top-level-outlet'; - - constructor(public template: WrappedTemplateFactory) { + getSelf({ self }: OutletInstanceState) { + return self; } - compile(builder: any) { - builder.wrapLayout(this.template); - builder.tag.static('div'); - builder.attrs.static('id', guidFor(this)); - builder.attrs.static('class', 'ember-view'); + getTag(): Tag { + // an outlet has no hooks + return CONSTANT_TAG; } -} -class TopLevelOutletComponentManager extends OutletComponentManager { - create(environment: Environment, definition: OutletComponentDefinition, _args: Arguments, dynamicScope: OutletDynamicScope) { - if (DEBUG) { - this._pushToDebugStack(`template:${definition.template.meta.moduleName}`, environment); - } - return new StateBucket(dynamicScope.outletState.value()); - } + didRenderLayout(state: OutletInstanceState) { + state.finalize(); - layoutFor(definition: OutletComponentDefinition, bucket: StateBucket, env: Environment) { - if (ENV._APPLICATION_TEMPLATE_WRAPPER) { - return (env as EmberEnvironment).getCompiledBlock(WrappedTopLevelOutletLayoutCompiler, definition.template); - } else { - return super.layoutFor(definition, bucket, env); + if (DEBUG) { + this.debugStack.pop(); } } -} -const TOP_LEVEL_MANAGER = new TopLevelOutletComponentManager(); - -export class TopLevelOutletComponentDefinition extends ComponentDefinition { - public template: WrappedTemplateFactory; - constructor(instance: any) { - super('outlet', TOP_LEVEL_MANAGER, instance); - this.template = instance.template; - generateGuid(this); + getDestructor(): Option { + return null; } } -export class OutletComponentDefinition extends ComponentDefinition { - public outletName: string; - public template: OwnedTemplate; +const OUTLET_MANAGER = new OutletComponentManager(); - constructor(outletName: string, template: OwnedTemplate) { - super('outlet', MANAGER, null); - this.outletName = outletName; - this.template = template; - generateGuid(this); +export class OutletComponentDefinition implements ComponentDefinition { + constructor(public state: OutletDefinitionState, public manager: OutletComponentManager = OUTLET_MANAGER) { } } -export class OutletLayoutCompiler { - static id: string; - public template: WrappedTemplateFactory; - constructor(template: WrappedTemplateFactory) { - this.template = template; - } - - compile(builder: any) { - builder.wrapLayout(this.template); +export function createRootOutlet(outletView: OutletView): OutletComponentDefinition { + if (ENV._APPLICATION_TEMPLATE_WRAPPER) { + const WRAPPED_CAPABILITIES = assign({}, CAPABILITIES, { + dynamicTag: true, + elementHook: true, + }); + + const WrappedOutletComponentManager = class extends OutletComponentManager + implements WithDynamicTagName { + + getTagName(_component: OutletInstanceState) { + return 'div'; + } + + getLayout(state: OutletDefinitionState, resolver: RuntimeResolver): Invocation { + // The router has already resolved the template + const template = state.template; + const layout = resolver.getWrappedLayout(template, WRAPPED_CAPABILITIES); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable + }; + } + + getCapabilities(): ComponentCapabilities { + return WRAPPED_CAPABILITIES; + } + + didCreateElement(component: OutletInstanceState, element: Element, _operations: ElementOperations): void { + // to add GUID id and class + element.setAttribute('class', 'ember-view'); + element.setAttribute('id', guidFor(component)); + } + }; + + const WRAPPED_OUTLET_MANAGER = new WrappedOutletComponentManager(); + + return new OutletComponentDefinition(outletView.state, WRAPPED_OUTLET_MANAGER); + } else { + return new OutletComponentDefinition(outletView.state); } } - -OutletLayoutCompiler.id = 'outlet'; diff --git a/packages/ember-glimmer/lib/component-managers/render.ts b/packages/ember-glimmer/lib/component-managers/render.ts index 352dde6b538..774781c3b6e 100644 --- a/packages/ember-glimmer/lib/component-managers/render.ts +++ b/packages/ember-glimmer/lib/component-managers/render.ts @@ -1,27 +1,63 @@ import { + ComponentCapabilities, +} from '@glimmer/interfaces'; +import { + CONSTANT_TAG, Tag, VersionedPathReference +} from '@glimmer/reference'; +import { + Arguments, ComponentDefinition, - ComponentManager, + Invocation, + WithStaticLayout } from '@glimmer/runtime'; -import { IArguments } from '@glimmer/runtime/dist/types/lib/vm/arguments'; -import { assert } from 'ember-debug'; import { DEBUG } from 'ember-env-flags'; import { generateController, generateControllerFactory } from 'ember-routing'; +import { Owner } from 'ember-utils'; +import { OwnedTemplateMeta } from 'ember-views'; import Environment from '../environment'; import { DynamicScope } from '../renderer'; import { OwnedTemplate } from '../template'; +import { OrphanedOutletReference } from '../utils/outlet'; import { RootReference } from '../utils/references'; import AbstractManager from './abstract'; -import { OutletLayoutCompiler } from './outlet'; -export abstract class AbstractRenderManager extends AbstractManager { - layoutFor(definition: RenderDefinition, _bucket: RenderState, env: Environment) { - // only curly components can have lazy layout - assert('definition is missing a template', !!definition.template); - return env.getCompiledBlock(OutletLayoutCompiler, definition.template!); +export interface RenderDefinitionState { + name: string; + template: OwnedTemplate; +} + +export abstract class AbstractRenderManager extends AbstractManager + implements WithStaticLayout { + + create(env: Environment, + definition: RenderDefinitionState, + args: Arguments, + dynamicScope: DynamicScope): T { + let { name } = definition; + + if (DEBUG) { + this._pushToDebugStack(`controller:${name} (with the render helper)`, env); + } + + if (dynamicScope.rootOutletState) { + dynamicScope.outletState = new OrphanedOutletReference(dynamicScope.rootOutletState, name); + } + + return this.createRenderState(args, env.owner, name); } - getSelf({ controller }: RenderState) { + abstract createRenderState(args: Arguments, owner: Owner, name: string): T; + + getLayout({ template }: RenderDefinitionState): Invocation { + const layout = template!.asLayout(); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable + }; + } + + getSelf({ controller }: T) { return new RootReference(controller); } } @@ -34,26 +70,34 @@ if (DEBUG) { export interface RenderState { controller: any; - model: any; } -class SingletonRenderManager extends AbstractRenderManager { - create(env: Environment, - definition: ComponentDefinition, - _args: IArguments, - dynamicScope: DynamicScope) { - let { name } = definition; - let controller = env.owner.lookup(`controller:${name}`) || generateController(env.owner, name); +export interface RenderStateWithModel extends RenderState { + model: VersionedPathReference; +} - if (DEBUG) { - this._pushToDebugStack(`controller:${name} (with the render helper)`, env); - } +const CAPABILITIES = { + dynamicLayout: false, + dynamicTag: false, + prepareArgs: false, + createArgs: false, + attributeHook: false, + elementHook: false +}; + +class SingletonRenderManager extends AbstractRenderManager { + createRenderState(_args: Arguments, owner: Owner, name: string) { + let controller = owner.lookup(`controller:${name}`) || generateController(owner, name); + return { controller }; + } - if (dynamicScope.rootOutletState) { - dynamicScope.outletState = dynamicScope.rootOutletState.getOrphan(name); - } + getCapabilities(_: RenderDefinitionState): ComponentCapabilities { + return CAPABILITIES; + } - return { controller } as RenderState; + getTag(): Tag { + // todo this should be the tag of the state args + return CONSTANT_TAG; } getDestructor() { @@ -63,47 +107,50 @@ class SingletonRenderManager extends AbstractRenderManager { export const SINGLETON_RENDER_MANAGER = new SingletonRenderManager(); -class NonSingletonRenderManager extends AbstractRenderManager { - create(environment: Environment, definition: RenderDefinition, args: IArguments, dynamicScope: DynamicScope) { - let { name, env } = definition; - let modelRef = args.positional.at(0); - let controllerFactory = env.owner.factoryFor(`controller:${name}`); - - let factory: any = controllerFactory || generateControllerFactory(env.owner, name); - let controller = factory.create({ model: modelRef.value() }); - - if (DEBUG) { - this._pushToDebugStack(`controller:${name} (with the render helper)`, environment); - } +const NONSINGLETON_CAPABILITIES = { + dynamicLayout: false, + dynamicTag: false, + prepareArgs: false, + createArgs: true, + attributeHook: false, + elementHook: false +}; + +class NonSingletonRenderManager extends AbstractRenderManager { + createRenderState(args: Arguments, owner: Owner, name: string) { + let model = args.positional.at(1); + let factory = owner.factoryFor(`controller:${name}`) || generateControllerFactory(owner, `controller:${name}`); + let controller = factory.create({ model: model.value() }); + return { controller, model }; + } - if (dynamicScope.rootOutletState) { - dynamicScope.outletState = dynamicScope.rootOutletState.getOrphan(name); - } + update({ controller, model }: RenderStateWithModel) { + controller.set('model', model.value()); + } - return { controller, model: modelRef }; + getCapabilities(_: RenderDefinitionState): ComponentCapabilities { + return NONSINGLETON_CAPABILITIES; } - update({ controller, model }: RenderState) { - controller.set('model', model.value()); + getTag({ model }: RenderStateWithModel): Tag { + return model.tag; } - getDestructor({ controller }: RenderState) { + getDestructor({ controller }: RenderStateWithModel) { return controller; } } export const NON_SINGLETON_RENDER_MANAGER = new NonSingletonRenderManager(); -export class RenderDefinition extends ComponentDefinition { - public name: string; - public template: OwnedTemplate | undefined; - public env: Environment; +export class RenderDefinition implements ComponentDefinition { - constructor(name: string, template: OwnedTemplate | undefined, env: Environment, manager: ComponentManager) { - super('render', manager, null); + public state: RenderDefinitionState; - this.name = name; - this.template = template; - this.env = env; + constructor(name: string, template: OwnedTemplate, public manager: SingletonRenderManager | NonSingletonRenderManager) { + this.state = { + name, + template, + }; } } diff --git a/packages/ember-glimmer/lib/component-managers/root.ts b/packages/ember-glimmer/lib/component-managers/root.ts index ef1b6167b14..94c3147f057 100644 --- a/packages/ember-glimmer/lib/component-managers/root.ts +++ b/packages/ember-glimmer/lib/component-managers/root.ts @@ -1,26 +1,47 @@ +import { ComponentCapabilities } from '@glimmer/interfaces'; import { Arguments, ComponentDefinition } from '@glimmer/runtime'; +import { FACTORY_FOR } from 'container'; import { DEBUG } from 'ember-env-flags'; -import { - _instrumentStart, -} from 'ember-metal'; -import ComponentStateBucket from '../utils/curly-component-state-bucket'; +import { _instrumentStart } from 'ember-metal'; +import { DIRTY_TAG } from '../component'; +import Environment from '../environment'; +import { DynamicScope } from '../renderer'; +import RuntimeResolver from '../resolver'; +import ComponentStateBucket, { Component } from '../utils/curly-component-state-bucket'; import CurlyComponentManager, { initialRenderInstrumentDetails, processComponentInitializationAssertions, - CurlyComponentDefinition } from './curly'; -import { DynamicScope } from '../renderer'; -import Environment from '../environment'; +import DefinitionState from './definition-state'; class RootComponentManager extends CurlyComponentManager { - create(environment: Environment, definition: CurlyComponentDefinition, args: Arguments, dynamicScope: DynamicScope) { - let component = definition.ComponentClass.create(); + component: Component; + + constructor(component: Component) { + super(); + this.component = component; + } + + getLayout(_state: DefinitionState, resolver: RuntimeResolver) { + const template = this.templateFor(this.component, resolver); + const layout = resolver.getWrappedLayout(template, ROOT_CAPABILITIES); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable, + }; + } + + create(environment: Environment, + _state: DefinitionState, + _args: Arguments | null, + dynamicScope: DynamicScope) { + let component = this.component; if (DEBUG) { - this._pushToDebugStack(component._debugContainerKey, environment); + this._pushToDebugStack((component as any)._debugContainerKey, environment); } let finalizer = _instrumentStart('render.component', initialRenderInstrumentDetails, component); @@ -44,23 +65,38 @@ class RootComponentManager extends CurlyComponentManager { processComponentInitializationAssertions(component, {}); } - return new ComponentStateBucket(environment, component, args.named.capture(), finalizer); + return new ComponentStateBucket(environment, component, null, finalizer); } } -const ROOT_MANAGER = new RootComponentManager(); - -export class RootComponentDefinition extends ComponentDefinition { - public template: any; - public args: any; - constructor(instance: ComponentStateBucket) { - super('-root', ROOT_MANAGER, { - class: instance.constructor, - create() { - return instance; - }, - }); - this.template = undefined; - this.args = undefined; +// ROOT is the top-level template it has nothing but one yield. +// it is supposed to have a dummy element +export const ROOT_CAPABILITIES: ComponentCapabilities = { + dynamicLayout: false, + dynamicTag: true, + prepareArgs: false, + createArgs: false, + attributeHook: true, + elementHook: true +}; + +export class RootComponentDefinition implements ComponentDefinition { + state: DefinitionState; + manager: RootComponentManager; + + constructor(public component: Component) { + let manager = new RootComponentManager(component); + this.manager = manager; + let factory = FACTORY_FOR.get(component); + this.state = { + name: factory.fullName.slice(10), + capabilities: ROOT_CAPABILITIES, + ComponentClass: factory, + handle: null, + }; + } + + getTag({ component }: ComponentStateBucket) { + return component[DIRTY_TAG]; } } diff --git a/packages/ember-glimmer/lib/component-managers/template-only.ts b/packages/ember-glimmer/lib/component-managers/template-only.ts index f889fe49fb2..ecfb0df68f8 100644 --- a/packages/ember-glimmer/lib/component-managers/template-only.ts +++ b/packages/ember-glimmer/lib/component-managers/template-only.ts @@ -1,33 +1,43 @@ -import { VersionedPathReference } from '@glimmer/reference'; -import { CompiledDynamicProgram, ComponentDefinition, NULL_REFERENCE } from '@glimmer/runtime'; -import { Opaque } from '@glimmer/util'; -import Environment from '../environment'; -import { OwnedTemplate, WrappedTemplateFactory } from '../template'; +import { ComponentCapabilities } from '@glimmer/interfaces'; +import { CONSTANT_TAG } from '@glimmer/reference'; +import { ComponentDefinition, Invocation, NULL_REFERENCE, WithStaticLayout } from '@glimmer/runtime'; +import { OwnedTemplateMeta } from 'ember-views'; +import RuntimeResolver from '../resolver'; +import { OwnedTemplate } from '../template'; import AbstractManager from './abstract'; -class TemplateOnlyComponentLayoutCompiler { - static id = 'template-only'; - - constructor(public template: WrappedTemplateFactory) { +const CAPABILITIES: ComponentCapabilities = { + dynamicLayout: false, + dynamicTag: false, + prepareArgs: false, + createArgs: false, + attributeHook: false, + elementHook: false +}; + +export default class TemplateOnlyComponentManager extends AbstractManager implements WithStaticLayout { + getLayout(template: OwnedTemplate): Invocation { + const layout = template.asLayout(); + return { + handle: layout.compile(), + symbolTable: layout.symbolTable + }; } - compile(builder: any) { - // TODO: use fromLayout - builder.wrapLayout(this.template); + getCapabilities(): ComponentCapabilities { + return CAPABILITIES; } -} -export default class TemplateOnlyComponentManager extends AbstractManager { create(): null { return null; } - layoutFor({ template }: TemplateOnlyComponentDefinition, _: null, env: Environment): CompiledDynamicProgram { - return env.getCompiledBlock(TemplateOnlyComponentLayoutCompiler, template); + getSelf() { + return NULL_REFERENCE; } - getSelf(): VersionedPathReference { - return NULL_REFERENCE; + getTag() { + return CONSTANT_TAG; } getDestructor() { @@ -37,8 +47,8 @@ export default class TemplateOnlyComponentManager extends AbstractManager const MANAGER = new TemplateOnlyComponentManager(); -export class TemplateOnlyComponentDefinition extends ComponentDefinition { - constructor(name: string, public template: OwnedTemplate) { - super(name, MANAGER, null); +export class TemplateOnlyComponentDefinition implements ComponentDefinition { + manager = MANAGER; + constructor(public state: OwnedTemplate) { } } diff --git a/packages/ember-glimmer/lib/component.ts b/packages/ember-glimmer/lib/component.ts index 33e21d12ece..12e74b445cf 100644 --- a/packages/ember-glimmer/lib/component.ts +++ b/packages/ember-glimmer/lib/component.ts @@ -1,5 +1,8 @@ import { DirtyableTag } from '@glimmer/reference'; -import { readDOMAttr } from '@glimmer/runtime'; +import { + normalizeProperty, + SVG_NAMESPACE +} from '@glimmer/runtime'; import { assert } from 'ember-debug'; import { get, @@ -564,7 +567,7 @@ const Component = CoreView.extend( init() { this._super(...arguments); this[IS_DISPATCHING_ATTRS] = false; - this[DIRTY_TAG] = new DirtyableTag(); + this[DIRTY_TAG] = DirtyableTag.create(); this[ROOT_REF] = new RootReference(this); this[BOUNDS] = null; @@ -573,7 +576,7 @@ const Component = CoreView.extend( // tslint:disable-next-line:max-line-length `You can not define a function that handles DOM events in the \`${this}\` tagless component since it doesn't have any DOM element.`, this.tagName !== '' || !this.renderer._destinedForDOM || !(() => { - let eventDispatcher = getOwner(this).lookup('event_dispatcher:main'); + let eventDispatcher = getOwner(this).lookup('event_dispatcher:main'); let events = (eventDispatcher && eventDispatcher._finalEvents) || {}; // tslint:disable-next-line:forin @@ -590,7 +593,7 @@ const Component = CoreView.extend( }, rerender() { - this[DIRTY_TAG].dirty(); + this[DIRTY_TAG].inner.dirty(); this._super(); }, @@ -651,8 +654,19 @@ const Component = CoreView.extend( @public */ readDOMAttr(name: string) { - let element = getViewElement(this); - return readDOMAttr(element, name); + // TODO revisit this + let element = getViewElement(this) as HTMLElement; + let isSVG = element.namespaceURI === SVG_NAMESPACE; + let { type, normalized } = normalizeProperty(element, name); + + if (isSVG) { + return element.getAttribute(normalized); + } + + if (type === 'attr') { + return element.getAttribute(normalized); + } + return element[normalized]; }, /** diff --git a/packages/ember-glimmer/lib/components/link-to.ts b/packages/ember-glimmer/lib/components/link-to.ts index 2b5c671e7fb..90b0269e0a1 100644 --- a/packages/ember-glimmer/lib/components/link-to.ts +++ b/packages/ember-glimmer/lib/components/link-to.ts @@ -296,8 +296,8 @@ @public */ -import { DEBUG } from 'ember-env-flags'; import { assert, warn } from 'ember-debug'; +import { DEBUG } from 'ember-env-flags'; import { computed, flaggedInstrument, diff --git a/packages/ember-glimmer/lib/environment.ts b/packages/ember-glimmer/lib/environment.ts index e1c07f255d9..78abd10c54d 100644 --- a/packages/ember-glimmer/lib/environment.ts +++ b/packages/ember-glimmer/lib/environment.ts @@ -1,97 +1,42 @@ import { Reference, - PathReference, } from '@glimmer/reference'; import { - AttributeManager, - CapturedArguments, - CompilableLayout, - CompiledDynamicProgram, - compileLayout, - ComponentDefinition, + ElementBuilder, Environment as GlimmerEnvironment, - getDynamicVar, - Helper, - isSafeString, - ModifierManager, PrimitiveReference, - PartialDefinition, - Simple, - VM + SimpleDynamicAttribute, } from '@glimmer/runtime'; import { Destroyable, Opaque, } from '@glimmer/util'; import { warn } from 'ember-debug'; -import { ENV } from 'ember-environment'; import { DEBUG } from 'ember-env-flags'; -import { _instrumentStart, Cache } from 'ember-metal'; -import { guidFor, OWNER } from 'ember-utils'; +import { OWNER, Owner } from 'ember-utils'; import { constructStyleDeprecationMessage, - hasPartial, lookupComponent, - lookupPartial, } from 'ember-views'; -import { - CurlyComponentDefinition, -} from './component-managers/curly'; -import { - TemplateOnlyComponentDefinition, -} from './component-managers/template-only'; -import { - populateMacros, -} from './syntax'; import DebugStack from './utils/debug-stack'; import createIterable from './utils/iterable'; import { - ClassBasedHelperReference, ConditionalReference, RootPropertyReference, - SimpleHelperReference, UpdatableReference, } from './utils/references'; +import { isHTMLSafe } from './utils/string'; -import { default as classHelper } from './helpers/-class'; -import { default as htmlSafeHelper } from './helpers/-html-safe'; -import { default as inputTypeHelper } from './helpers/-input-type'; -import { default as normalizeClassHelper } from './helpers/-normalize-class'; -import { default as action } from './helpers/action'; -import { default as componentHelper } from './helpers/component'; -import { default as concat } from './helpers/concat'; -import { default as eachIn } from './helpers/each-in'; -import { default as get } from './helpers/get'; -import { default as hash } from './helpers/hash'; -import { - inlineIf, - inlineUnless, -} from './helpers/if-unless'; -import { default as log } from './helpers/log'; -import { default as mut } from './helpers/mut'; -import { default as queryParams } from './helpers/query-param'; -import { default as readonly } from './helpers/readonly'; -import { default as unbound } from './helpers/unbound'; - -import { default as ActionModifierManager } from './modifiers/action'; import installPlatformSpecificProtocolForURL from './protocol-for-url'; import { EMBER_MODULE_UNIFICATION, - GLIMMER_CUSTOM_COMPONENT_MANAGER, + // GLIMMER_CUSTOM_COMPONENT_MANAGER, } from 'ember/features'; -import { Container, OwnedTemplate, WrappedTemplateFactory } from './template'; - -function instrumentationPayload(name: string) { - return { object: `component:${name}` }; -} - -function isTemplateFactory(template: OwnedTemplate | WrappedTemplateFactory): template is WrappedTemplateFactory { - return typeof (template as WrappedTemplateFactory).create === 'function'; -} +import { OwnedTemplate } from './template'; export interface CompilerFactory { id: string; - new(template: OwnedTemplate | undefined): CompilableLayout; + new (template: OwnedTemplate): any; } export default class Environment extends GlimmerEnvironment { @@ -99,27 +44,22 @@ export default class Environment extends GlimmerEnvironment { return new this(options); } - public owner: Container; + public owner: Owner; public isInteractive: boolean; public destroyedComponents: Destroyable[]; - public builtInModifiers: { - [name: string]: ModifierManager; - }; - public builtInHelpers: { - [name: string]: Helper; - }; + public debugStack: typeof DebugStack; public inTransaction: boolean; - private _definitionCache: Cache<{ - name: string; - source: string; - owner: Container; - }, CurlyComponentDefinition | TemplateOnlyComponentDefinition | undefined>; - private _templateCache: Cache<{ - Template: WrappedTemplateFactory | OwnedTemplate; - owner: Container; - }, OwnedTemplate>; - private _compilerCache: Cache>; + // private _definitionCache: Cache<{ + // name: string; + // source: string; + // owner: Container; + // }, CurlyComponentDefinition | TemplateOnlyComponentDefinition | undefined>; + // private _templateCache: Cache<{ + // Template: WrappedTemplateFactory | OwnedTemplate; + // owner: Container; + // }, OwnedTemplate>; + // private _compilerCache: Cache>; constructor(injections: any) { super(injections); @@ -131,73 +71,73 @@ export default class Environment extends GlimmerEnvironment { installPlatformSpecificProtocolForURL(this); - this._definitionCache = new Cache(2000, ({ name, source, owner }) => { - let { component: componentFactory, layout } = lookupComponent(owner, name, { source }); - let customManager: any; - if (ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS && layout && !componentFactory) { - return new TemplateOnlyComponentDefinition(name, layout); - } else if (componentFactory || layout) { - if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { - let managerId = layout && layout.meta.managerId; - - if (managerId) { - customManager = owner.factoryFor(`component-manager:${managerId}`).class; - } - } - return new CurlyComponentDefinition(name, componentFactory, layout, undefined, customManager); - } - return undefined; - }, ({ name, source, owner }) => { - let expandedName = source && this._resolveLocalLookupName(name, source, owner) || name; - - let ownerGuid = guidFor(owner); - - return ownerGuid + '|' + expandedName; - }); - - this._templateCache = new Cache(1000, ({ Template, owner }) => { - if (isTemplateFactory(Template)) { - // we received a factory - return Template.create({ env: this, [OWNER]: owner }); - } else { - // we were provided an instance already - return Template; - } - }, ({ Template, owner }) => guidFor(owner) + '|' + Template.id); - - this._compilerCache = new Cache(10, (Compiler) => { - return new Cache(2000, (template) => { - let compilable = new Compiler(template); - return compileLayout(compilable, this); - }, (template) => { - let owner = template.meta.owner; - return guidFor(owner) + '|' + template.id; - }); - }, (Compiler) => Compiler.id); - - this.builtInModifiers = { - action: new ActionModifierManager(), - }; - - this.builtInHelpers = { - 'if': inlineIf, - action, - concat, - get, - hash, - log, - mut, - 'query-params': queryParams, - readonly, - unbound, - 'unless': inlineUnless, - '-class': classHelper, - '-each-in': eachIn, - '-input-type': inputTypeHelper, - '-normalize-class': normalizeClassHelper, - '-html-safe': htmlSafeHelper, - '-get-dynamic-var': getDynamicVar, - }; + // this._definitionCache = new Cache(2000, ({ name, source, owner }) => { + // let { component: componentFactory, layout } = lookupComponent(owner, name, { source }); + // let customManager: any; + // if (ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS && layout && !componentFactory) { + // return new TemplateOnlyComponentDefinition(name, layout); + // } else if (componentFactory || layout) { + // if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { + // let managerId = layout && layout.meta.managerId; + + // if (managerId) { + // customManager = owner.factoryFor(`component-manager:${managerId}`).class; + // } + // } + // return new CurlyComponentDefinition(name, componentFactory, layout, undefined, customManager); + // } + // return undefined; + // }, ({ name, source, owner }) => { + // let expandedName = source && this._resolveLocalLookupName(name, source, owner) || name; + + // let ownerGuid = guidFor(owner); + + // return ownerGuid + '|' + expandedName; + // }); + + // this._templateCache = new Cache(1000, ({ Template, owner }) => { + // if (isTemplateFactory(Template)) { + // // we received a factory + // return Template.create({ env: this, [OWNER]: owner }); + // } else { + // // we were provided an instance already + // return Template; + // } + // }, ({ Template, owner }) => guidFor(owner) + '|' + Template.id); + + // this._compilerCache = new Cache(10, (Compiler) => { + // return new Cache(2000, (template) => { + // let compilable = new Compiler(template); + // return compileLayout(compilable, this); + // }, (template) => { + // let owner = template.meta.owner; + // return guidFor(owner) + '|' + template.id; + // }); + // }, (Compiler) => Compiler.id); + + // this.builtInModifiers = { + // action: new ActionModifierManager(), + // }; + + // this.builtInHelpers = { + // 'if': inlineIf, + // action, + // concat, + // get, + // hash, + // log, + // mut, + // 'query-params': queryParams, + // readonly, + // unbound, + // 'unless': inlineUnless, + // '-class': classHelper, + // '-each-in': eachIn, + // '-input-type': inputTypeHelper, + // '-normalize-class': normalizeClassHelper, + // '-html-safe': htmlSafeHelper, + // '-get-dynamic-var': getDynamicVar, + // }; if (DEBUG) { this.debugStack = new DebugStack(); @@ -215,109 +155,16 @@ export default class Environment extends GlimmerEnvironment { : owner._resolveLocalLookupName(name, source); } + /* macros() { let macros = super.macros(); populateMacros(macros.blocks, macros.inlines); return macros; } + */ - hasComponentDefinition() { - return false; - } - - getComponentDefinition(name: string, { owner, moduleName }: any): ComponentDefinition { - let finalizer = _instrumentStart('render.getComponentDefinition', instrumentationPayload, name); - let source = moduleName && `template:${moduleName}`; - let definition = this._definitionCache.get({ name, source, owner }); - finalizer(); - // TODO the glimmer-vm wants this to always have a def - // but internally we need it to sometimes be undefined - return definition!; - } - - // normally templates should be exported at the proper module name - // and cached in the container, but this cache supports templates - // that have been set directly on the component's layout property - getTemplate(Template: WrappedTemplateFactory, owner: Container): OwnedTemplate { - return this._templateCache.get({ Template, owner }); - } - - // a Compiler can wrap the template so it needs its own cache - getCompiledBlock(Compiler: any, template: OwnedTemplate) { - let compilerCache = this._compilerCache.get(Compiler); - return compilerCache.get(template); - } - - hasPartial(name: string, meta: any): boolean { - return hasPartial(name, meta.owner); - } - - lookupPartial(name: string, meta: any): PartialDefinition { - let partial = { - name, - template: lookupPartial(name, meta.owner), - }; - - if (partial.template) { - return partial; - } else { - throw new Error(`${name} is not a partial`); - } - } - - hasHelper(name: string, { owner, moduleName }: { owner: Container, moduleName: string }): boolean { - if (name === 'component' || this.builtInHelpers[name]) { - return true; - } - - let options = { source: `template:${moduleName}` }; - - return owner.hasRegistration(`helper:${name}`, options) || - owner.hasRegistration(`helper:${name}`); - } - - lookupHelper(name: string, meta: any): Helper { - if (name === 'component') { - return (vm, args) => componentHelper(vm, args, meta); - } - - let { owner, moduleName } = meta; - let helper = this.builtInHelpers[name]; - - if (helper) { - return helper; - } - - let options = moduleName && { source: `template:${moduleName}` } || {}; - let helperFactory = owner.factoryFor(`helper:${name}`, options) || owner.factoryFor(`helper:${name}`); - - // TODO: try to unify this into a consistent protocol to avoid wasteful closure allocations - let HelperReference: { - create: (HelperFactory: any, vm: VM, args: CapturedArguments) => PathReference; - }; - if (helperFactory.class.isSimpleHelperFactory) { - HelperReference = SimpleHelperReference; - } else if (helperFactory.class.isHelperFactory) { - HelperReference = ClassBasedHelperReference; - } else { - throw new Error(`${name} is not a helper`); - } - - return (vm, args) => HelperReference.create(helperFactory, vm, args.capture()); - } - - hasModifier(name: string) { - return !!this.builtInModifiers[name]; - } - - lookupModifier(name: string) { - let modifier = this.builtInModifiers[name]; - - if (modifier) { - return modifier; - } else { - throw new Error(`${name} is not a modifier`); - } + lookupComponent(name: string, meta: any) { + return lookupComponent(meta.owner, name, meta); } toConditionalReference(reference: UpdatableReference): ConditionalReference | RootPropertyReference | PrimitiveReference { @@ -367,35 +214,32 @@ export default class Environment extends GlimmerEnvironment { } if (DEBUG) { - class StyleAttributeManager extends AttributeManager { - setAttribute(dom: Environment, element: Simple.Element, value: Opaque) { + class StyleAttributeManager extends SimpleDynamicAttribute { + set(dom: ElementBuilder, value: Opaque, env: GlimmerEnvironment): void { warn(constructStyleDeprecationMessage(value), (() => { - if (value === null || value === undefined || isSafeString(value)) { + if (value === null || value === undefined || isHTMLSafe(value)) { return true; } return false; })(), { id: 'ember-htmlbars.style-xss-warning' }); - super.setAttribute(dom, element, value); + super.set(dom, value, env); } - - updateAttribute(dom: Environment, element: Element, value: Opaque) { + update(value: Opaque, env: GlimmerEnvironment): void { warn(constructStyleDeprecationMessage(value), (() => { - if (value === null || value === undefined || isSafeString(value)) { + if (value === null || value === undefined || isHTMLSafe(value)) { return true; } return false; })(), { id: 'ember-htmlbars.style-xss-warning' }); - super.updateAttribute(dom, element, value); + super.update(value, env); } } - let STYLE_ATTRIBUTE_MANANGER = new StyleAttributeManager('style'); - - Environment.prototype.attributeFor = function (element, attribute, isTrusting) { + Environment.prototype.attributeFor = function (element, attribute: string, isTrusting: boolean, _namespace?) { if (attribute === 'style' && !isTrusting) { - return STYLE_ATTRIBUTE_MANANGER; + return StyleAttributeManager; } - return GlimmerEnvironment.prototype.attributeFor.call(this, element, attribute, isTrusting); + return GlimmerEnvironment.prototype.attributeFor.call(this, element, attribute, isTrusting, _namespace); }; } diff --git a/packages/ember-glimmer/lib/helper.ts b/packages/ember-glimmer/lib/helper.ts index 731ec973adc..3121eebfe89 100644 --- a/packages/ember-glimmer/lib/helper.ts +++ b/packages/ember-glimmer/lib/helper.ts @@ -2,13 +2,50 @@ @module @ember/component */ -import { Dict, Opaque } from '@glimmer/util'; +import { Dict, Opaque } from '@glimmer/interfaces'; import { DirtyableTag } from '@glimmer/reference'; import { FrameworkObject } from 'ember-runtime'; -import { symbol } from 'ember-utils'; +import { Factory, symbol } from 'ember-utils'; export const RECOMPUTE_TAG = symbol('RECOMPUTE_TAG'); +export type HelperFunction = (positional: Opaque[], named: Dict) => Opaque; + +export type SimpleHelperFactory = Factory; +export type ClassHelperFactory = Factory; + +export type HelperFactory = SimpleHelperFactory | ClassHelperFactory; + +export interface SimpleHelper { + isHelperFactory: true; + isSimpleHelper: true; + + create(): SimpleHelper; + compute: HelperFunction; +} + +export interface HelperStatic { + isHelperFactory: true; + isSimpleHelper: false; + + create(): HelperInstance; +} + +export interface HelperInstance { + compute: HelperFunction; + destroy(): void; +} + +export function isHelperFactory(helper: any | undefined | null): helper is HelperFactory { + return typeof helper === 'object' && + helper !== null && + helper.class && helper.class.isHelperFactory; +} + +export function isSimpleHelper(helper: HelperFactory): helper is SimpleHelperFactory { + return helper.class.isSimpleHelper; +} + /** Ember Helpers are functions that can compute values, and are used in templates. For example, this code calls a helper named `format-currency`: @@ -52,11 +89,9 @@ export const RECOMPUTE_TAG = symbol('RECOMPUTE_TAG'); @since 1.13.0 */ let Helper = FrameworkObject.extend({ - isHelperInstance: true, - init() { this._super(...arguments); - this[RECOMPUTE_TAG] = new DirtyableTag(); + this[RECOMPUTE_TAG] = DirtyableTag.create(); }, /** @@ -87,7 +122,7 @@ let Helper = FrameworkObject.extend({ @since 1.13.0 */ recompute() { - this[RECOMPUTE_TAG].dirty(); + this[RECOMPUTE_TAG].inner.dirty(); }, /** @@ -103,14 +138,14 @@ let Helper = FrameworkObject.extend({ Helper.reopenClass({ isHelperFactory: true, + isSimpleHelper: false, }); -export class SimpleHelper { - isHelperFactory = true; - isHelperInstance = true; - isSimpleHelperFactory = true; +class Wrapper implements SimpleHelper { + isHelperFactory: true = true; + isSimpleHelper: true = true; - constructor(public compute: (positional: any[], named: Dict) => any) { } + constructor(public compute: HelperFunction) {} create() { return this; @@ -139,8 +174,8 @@ export class SimpleHelper { @public @since 1.13.0 */ -export function helper(helperFn: (params: any[], hash?: any) => string) { - return new SimpleHelper(helperFn); +export function helper(helperFn: HelperFunction): SimpleHelper { + return new Wrapper(helperFn); } -export default Helper; +export default Helper as HelperStatic; diff --git a/packages/ember-glimmer/lib/helpers/action.ts b/packages/ember-glimmer/lib/helpers/action.ts index 4b4e079418a..cc366f45572 100644 --- a/packages/ember-glimmer/lib/helpers/action.ts +++ b/packages/ember-glimmer/lib/helpers/action.ts @@ -272,7 +272,7 @@ export const ACTION = symbol('ACTION'); @for Ember.Templates.helpers @public */ -export default function(_vm: VM, args: Arguments): UnboundReference { +export default function(_vm: VM, args: Arguments): UnboundReference { let { named, positional } = args; let capturedArgs = positional.capture(); diff --git a/packages/ember-glimmer/lib/helpers/component.ts b/packages/ember-glimmer/lib/helpers/component.ts deleted file mode 100644 index 72d67530710..00000000000 --- a/packages/ember-glimmer/lib/helpers/component.ts +++ /dev/null @@ -1,281 +0,0 @@ -/** - @module ember -*/ -import { - Arguments, - CapturedArguments, - Environment, - isComponentDefinition, - VM -} from '@glimmer/runtime'; -import { assert } from 'ember-debug'; -import { assign } from 'ember-utils'; -import { - CurlyComponentDefinition, - validatePositionalParameters, -} from '../component-managers/curly'; -import { CachedReference } from '../utils/references'; - -/** - The `{{component}}` helper lets you add instances of `Component` to a - template. See [Component](/api/classes/Ember.Component.html) for - additional information on how a `Component` functions. - `{{component}}`'s primary use is for cases where you want to dynamically - change which type of component is rendered as the state of your application - changes. This helper has three modes: inline, block, and nested. - - ### Inline Form - - Given the following template: - - ```app/application.hbs - {{component infographicComponentName}} - ``` - - And the following application code: - - ```app/controllers/application.js - import Controller from '@ember/controller'; - import { computed } from '@ember/object'; - - export default Controller.extend({ - infographicComponentName: computed('isMarketOpen', { - get() { - if (this.get('isMarketOpen')) { - return 'live-updating-chart'; - } else { - return 'market-close-summary'; - } - } - }) - }); - ``` - - The `live-updating-chart` component will be appended when `isMarketOpen` is - `true`, and the `market-close-summary` component will be appended when - `isMarketOpen` is `false`. If the value changes while the app is running, - the component will be automatically swapped out accordingly. - Note: You should not use this helper when you are consistently rendering the same - component. In that case, use standard component syntax, for example: - - ```app/templates/application.hbs - {{live-updating-chart}} - ``` - - ### Block Form - - Using the block form of this helper is similar to using the block form - of a component. Given the following application template: - - ```app/templates/application.hbs - {{#component infographicComponentName}} - Last update: {{lastUpdateTimestamp}} - {{/component}} - ``` - - The following controller code: - - ```app/controllers/application.js - import Controller from '@ember/controller'; - import { computed } from '@ember/object'; - - export default Controller.extend({ - lastUpdateTimestamp: computed(function() { - return new Date(); - }), - - infographicComponentName: computed('isMarketOpen', { - get() { - if (this.get('isMarketOpen')) { - return 'live-updating-chart'; - } else { - return 'market-close-summary'; - } - } - }) - }); - ``` - - And the following component template: - - ```app/templates/components/live-updating-chart.hbs - {{! chart }} - {{yield}} - ``` - - The `Last Update: {{lastUpdateTimestamp}}` will be rendered in place of the `{{yield}}`. - - ### Nested Usage - - The `component` helper can be used to package a component path with initial attrs. - The included attrs can then be merged during the final invocation. - For example, given a `person-form` component with the following template: - - ```app/templates/components/person-form.hbs - {{yield (hash - nameInput=(component "my-input-component" value=model.name placeholder="First Name") - )}} - ``` - - When yielding the component via the `hash` helper, the component is invoked directly. - See the following snippet: - - ``` - {{#person-form as |form|}} - {{form.nameInput placeholder="Username"}} - {{/person-form}} - ``` - - Which outputs an input whose value is already bound to `model.name` and `placeholder` - is "Username". - - When yielding the component without the hash helper use the `component` helper. - For example, below is a `full-name` component template: - - ```handlebars - {{yield (component "my-input-component" value=model.name placeholder="Name")}} - ``` - - ``` - {{#full-name as |field|}} - {{component field placeholder="Full name"}} - {{/full-name}} - ``` - - @method component - @since 1.11.0 - @for Ember.Templates.helpers - @public -*/ -export class ClosureComponentReference extends CachedReference { - static create(args: CapturedArguments, meta: any, env: Environment) { - return new ClosureComponentReference(args, meta, env); - } - - public defRef: any; - public tag: any; - public args: CapturedArguments; - public meta: any; - public env: Environment; - public lastDefinition: any; - public lastName: string | undefined; - - constructor(args: CapturedArguments, meta: any, env: Environment) { - super(); - - let firstArg = args.positional.at(0); - this.defRef = firstArg; - this.tag = firstArg.tag; - this.args = args; - this.meta = meta; - this.env = env; - this.lastDefinition = undefined; - this.lastName = undefined; - } - - compute() { - // TODO: Figure out how to extract this because it's nearly identical to - // DynamicComponentReference::compute(). The only differences besides - // currying are in the assertion messages. - let { args, defRef, env, meta, lastDefinition, lastName } = this; - let nameOrDef = defRef.value(); - let definition: CurlyComponentDefinition; - - if (nameOrDef && nameOrDef === lastName) { - return lastDefinition; - } - - this.lastName = nameOrDef; - - if (typeof nameOrDef === 'string') { - // tslint:disable-next-line:max-line-length - assert('You cannot use the input helper as a contextual helper. Please extend TextField or Checkbox to use it as a contextual component.', nameOrDef !== 'input'); - // tslint:disable-next-line:max-line-length - assert('You cannot use the textarea helper as a contextual helper. Please extend TextArea to use it as a contextual component.', nameOrDef !== 'textarea'); - definition = env.getComponentDefinition(nameOrDef, meta); - // tslint:disable-next-line:max-line-length - assert(`The component helper cannot be used without a valid component name. You used "${nameOrDef}" via (component "${nameOrDef}")`, !!definition); - } else if (isComponentDefinition(nameOrDef)) { - definition = nameOrDef; - } else { - assert( - `You cannot create a component from ${nameOrDef} using the {{component}} helper`, - nameOrDef, - ); - return null; - } - - let newDef = createCurriedDefinition(definition, args); - - this.lastDefinition = newDef; - - return newDef; - } -} - -function createCurriedDefinition(definition: CurlyComponentDefinition, args: CapturedArguments) { - let curriedArgs = curryArgs(definition, args); - - return new CurlyComponentDefinition( - definition.name, - definition.ComponentClass, - definition.template, - curriedArgs, - ); -} - -function curryArgs(definition: CurlyComponentDefinition, newArgs: CapturedArguments): any { - let { args, ComponentClass } = definition; - let positionalParams = ComponentClass.class.positionalParams; - - // The args being passed in are from the (component ...) invocation, - // so the first positional argument is actually the name or component - // definition. It needs to be dropped in order for any actual positional - // args to coincide with the ComponentClass's positionParams. - - // For "normal" curly components this slicing is done at the syntax layer, - // but we don't have that luxury here. - - let [, ...slicedPositionalArgs] = newArgs.positional.references; - - if (positionalParams && slicedPositionalArgs.length) { - validatePositionalParameters(newArgs.named, slicedPositionalArgs, positionalParams); - } - - let isRest = typeof positionalParams === 'string'; - - // For non-rest position params, we need to perform the position -> name mapping - // at each layer to avoid a collision later when the args are used to construct - // the component instance (inside of processArgs(), inside of create()). - let positionalToNamedParams = {}; - - if (!isRest && positionalParams.length > 0) { - let limit = Math.min(positionalParams.length, slicedPositionalArgs.length); - - for (let i = 0; i < limit; i++) { - let name = positionalParams[i]; - positionalToNamedParams[name] = slicedPositionalArgs[i]; - } - - slicedPositionalArgs.length = 0; // Throw them away since you're merged in. - } - - // args (aka 'oldArgs') may be undefined or simply be empty args, so - // we need to fall back to an empty array or object. - let oldNamed = args && args.named || {}; - let oldPositional = args && args.positional || []; - - // Merge positional arrays - let positional = new Array(Math.max(oldPositional.length, slicedPositionalArgs.length)); - positional.splice(0, oldPositional.length, ...oldPositional); - positional.splice(0, slicedPositionalArgs.length, ...slicedPositionalArgs); - - // Merge named maps - let named = assign({}, oldNamed, positionalToNamedParams, newArgs.named.map); - - return { positional, named }; -} - -export default function(vm: VM, args: Arguments, meta: any) { - return ClosureComponentReference.create(args.capture(), meta, vm.env); -} diff --git a/packages/ember-glimmer/lib/helpers/concat.ts b/packages/ember-glimmer/lib/helpers/concat.ts index 52ae7760ece..e779e369082 100644 --- a/packages/ember-glimmer/lib/helpers/concat.ts +++ b/packages/ember-glimmer/lib/helpers/concat.ts @@ -1,11 +1,21 @@ import { Arguments, CapturedArguments, - normalizeTextValue, VM } from '@glimmer/runtime'; import { InternalHelperReference } from '../utils/references'; +const isEmpty = (value: any): boolean => { + return value === null || value === undefined || typeof value.toString !== 'function'; +}; + +const normalizeTextValue = (value: any): string => { + if (isEmpty(value)) { + return ''; + } + return String(value); +}; + /** @module ember */ diff --git a/packages/ember-glimmer/lib/helpers/get.ts b/packages/ember-glimmer/lib/helpers/get.ts index 5398f1c3042..ee24ef97cf1 100644 --- a/packages/ember-glimmer/lib/helpers/get.ts +++ b/packages/ember-glimmer/lib/helpers/get.ts @@ -1,11 +1,13 @@ +import { Opaque } from '@glimmer/interfaces'; import { combine, CONSTANT_TAG, isConst, PathReference, - referenceFromParts, + Tag, TagWrapper, UpdatableTag, + VersionedPathReference, } from '@glimmer/reference'; import { Arguments, @@ -13,7 +15,7 @@ import { VM } from '@glimmer/runtime'; import { set } from 'ember-metal'; -import { CachedReference, UPDATE } from '../utils/references'; +import { CachedReference, referenceFromParts, UPDATE } from '../utils/references'; /** @module ember @@ -68,14 +70,14 @@ export default function(_vm: VM, args: Arguments) { } class GetHelperReference extends CachedReference { - public sourceReference: any; - public pathReference: PathReference; - public lastPath: any; - public innerReference: any; + public sourceReference: VersionedPathReference; + public pathReference: PathReference; + public lastPath: string | null; + public innerReference: VersionedPathReference; public innerTag: TagWrapper; - public tag: any; + public tag: Tag; - static create(sourceReference: any, pathReference: PathReference) { + static create(sourceReference: VersionedPathReference, pathReference: PathReference) { if (isConst(pathReference)) { let parts = pathReference.value().split('.'); return referenceFromParts(sourceReference, parts); @@ -84,7 +86,7 @@ class GetHelperReference extends CachedReference { } } - constructor(sourceReference: any, pathReference: PathReference) { + constructor(sourceReference: VersionedPathReference, pathReference: PathReference) { super(); this.sourceReference = sourceReference; this.pathReference = pathReference; diff --git a/packages/ember-glimmer/lib/helpers/loc.ts b/packages/ember-glimmer/lib/helpers/loc.ts index 78b55d807de..270279078a5 100644 --- a/packages/ember-glimmer/lib/helpers/loc.ts +++ b/packages/ember-glimmer/lib/helpers/loc.ts @@ -3,8 +3,8 @@ @module ember */ -import { helper } from '../helper'; import { String as StringUtils } from 'ember-runtime'; +import { helper } from '../helper'; /** Calls [loc](/api/classes/Ember.String.html#method_loc) with the diff --git a/packages/ember-glimmer/lib/index.ts b/packages/ember-glimmer/lib/index.ts index b5cea5dd521..d7ae9537dab 100644 --- a/packages/ember-glimmer/lib/index.ts +++ b/packages/ember-glimmer/lib/index.ts @@ -149,7 +149,7 @@ ```app/templates/application.hbs {{#labeled-textfield value=someProperty validator=(action 'firstNameValidator') as |validationError|}} {{#if validationError}} -

{{ValidationError}}

+

{{validationError}}

{{/if}} First name: {{/labeled-textfield}} @@ -266,7 +266,7 @@ export { default as Checkbox } from './components/checkbox'; export { default as TextField } from './components/text_field'; export { default as TextArea } from './components/text_area'; export { default as LinkComponent } from './components/link-to'; -export { default as Component } from './component'; +export { default as Component, ROOT_REF } from './component'; export { default as Helper, helper } from './helper'; export { default as Environment } from './environment'; export { @@ -293,3 +293,14 @@ export { setupEngineRegistry, setupApplicationRegistry } from './setup-registry' export { DOMChanges, NodeDOMTreeConstruction, DOMTreeConstruction } from './dom'; export { registerMacros as _registerMacros, experimentalMacros as _experimentalMacros } from './syntax'; export { default as AbstractComponentManager } from './component-managers/abstract'; + +// needed for test +// TODO just test these through public API +// a lot of these are testing how a problem was solved +// rather than the problem was solved +// DebugStack should just test the assert message +// it supports for example +export { UpdatableReference } from './utils/references'; +export { default as iterableFor } from './utils/iterable'; +export { default as DebugStack } from './utils/debug-stack'; +export { default as OutletView } from './views/outlet'; diff --git a/packages/ember-glimmer/lib/modifiers/action.ts b/packages/ember-glimmer/lib/modifiers/action.ts index 77da19b42a2..845e98baa9e 100644 --- a/packages/ember-glimmer/lib/modifiers/action.ts +++ b/packages/ember-glimmer/lib/modifiers/action.ts @@ -1,11 +1,19 @@ +import { + Simple +} from '@glimmer/interfaces'; +import { + RevisionTag, TagWrapper +} from '@glimmer/reference'; import { Arguments, CapturedNamedArguments, CapturedPositionalArguments, DynamicScope, - Simple + ModifierManager, } from '@glimmer/runtime'; -import { Destroyable } from '@glimmer/util'; +import { + Destroyable +} from '@glimmer/util'; import { assert } from 'ember-debug'; import { flaggedInstrument, run } from 'ember-metal'; import { uuid } from 'ember-utils'; @@ -70,8 +78,9 @@ export class ActionState { public implicitTarget: any; public dom: any; public eventName: any; + public tag: TagWrapper; - constructor(element: Simple.Element, actionId: number, actionName: any, actionArgs: any[], namedArgs: CapturedNamedArguments, positionalArgs: CapturedPositionalArguments, implicitTarget: any, dom: any) { + constructor(element: Simple.Element, actionId: number, actionName: any, actionArgs: any[], namedArgs: CapturedNamedArguments, positionalArgs: CapturedPositionalArguments, implicitTarget: any, dom: any, tag: TagWrapper) { this.element = element; this.actionId = actionId; this.actionName = actionName; @@ -81,6 +90,7 @@ export class ActionState { this.implicitTarget = implicitTarget; this.dom = dom; this.eventName = this.getEventName(); + this.tag = tag; } getEventName() { @@ -174,9 +184,9 @@ export class ActionState { } // implements ModifierManager -export default class ActionModifierManager { +export default class ActionModifierManager implements ModifierManager { create(element: Simple.Element, args: Arguments, _dynamicScope: DynamicScope, dom: any) { - let { named, positional } = args.capture(); + let { named, positional, tag } = args.capture(); let implicitTarget; let actionName; let actionNameRef: any; @@ -217,6 +227,7 @@ export default class ActionModifierManager { positional, implicitTarget, dom, + tag, ); } @@ -240,6 +251,10 @@ export default class ActionModifierManager { actionState.eventName = actionState.getEventName(); } + getTag(actionState: ActionState) { + return actionState.tag; + } + getDestructor(modifier: Destroyable) { return modifier; } diff --git a/packages/ember-glimmer/lib/protocol-for-url.ts b/packages/ember-glimmer/lib/protocol-for-url.ts index 98cd8582d1a..16f2458e9b2 100644 --- a/packages/ember-glimmer/lib/protocol-for-url.ts +++ b/packages/ember-glimmer/lib/protocol-for-url.ts @@ -1,8 +1,8 @@ /* globals module, URL */ import { environment as emberEnvironment } from 'ember-environment'; -import Environment from './environment'; import { IS_NODE, require } from 'node-module'; +import Environment from './environment'; let nodeURL: any; let parsingNode: HTMLAnchorElement; diff --git a/packages/ember-glimmer/lib/renderer.ts b/packages/ember-glimmer/lib/renderer.ts index 16daecdb1f8..f8b65055a38 100644 --- a/packages/ember-glimmer/lib/renderer.ts +++ b/packages/ember-glimmer/lib/renderer.ts @@ -1,8 +1,13 @@ -import { Option, Simple } from '@glimmer/interfaces'; +import { Simple } from '@glimmer/interfaces'; import { CURRENT_TAG, VersionedPathReference } from '@glimmer/reference'; import { + clientBuilder, + CurriedComponentDefinition, + curry, DynamicScope as GlimmerDynamicScope, IteratorResult, + RenderResult, + UNDEFINED_REFERENCE, } from '@glimmer/runtime'; import { Opaque } from '@glimmer/util'; import { assert } from 'ember-debug'; @@ -17,50 +22,37 @@ import { getViewId, setViewElement, } from 'ember-views'; +import RSVP from 'rsvp'; import { BOUNDS } from './component'; -import { TopLevelOutletComponentDefinition } from './component-managers/outlet'; +import { createRootOutlet } from './component-managers/outlet'; import { RootComponentDefinition } from './component-managers/root'; import Environment from './environment'; import { OwnedTemplate } from './template'; -import ComponentStateBucket, { Component } from './utils/curly-component-state-bucket'; -import { RootReference } from './utils/references'; -import OutletView, { OutletState, RootOutletStateReference } from './views/outlet'; - -import { ComponentDefinition, NULL_REFERENCE, RenderResult } from '@glimmer/runtime'; -import RSVP from 'rsvp'; +import { Component } from './utils/curly-component-state-bucket'; +import { OutletState } from './utils/outlet'; +import { UnboundReference } from './utils/references'; +import OutletView from './views/outlet'; const { backburner } = run; -export interface View { - tagName: string | null; - appendChild(child: View): void; -} - export class DynamicScope implements GlimmerDynamicScope { - outletState: VersionedPathReference>; - rootOutletState: RootOutletStateReference | undefined; - constructor( - public view: View | null, - outletState: VersionedPathReference>, - rootOutletState?: RootOutletStateReference) { - this.outletState = outletState; - this.rootOutletState = rootOutletState; + public view: Component | null, + public outletState: VersionedPathReference, + public rootOutletState?: VersionedPathReference) { } child() { - return new DynamicScope( - this.view, this.outletState, this.rootOutletState, - ); + return new DynamicScope(this.view, this.outletState, this.rootOutletState); } - get(key: 'outletState'): VersionedPathReference> { + get(key: 'outletState'): VersionedPathReference { // tslint:disable-next-line:max-line-length assert(`Using \`-get-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`, key === 'outletState'); return this.outletState; } - set(key: 'outletState', value: VersionedPathReference>) { + set(key: 'outletState', value: VersionedPathReference) { // tslint:disable-next-line:max-line-length assert(`Using \`-with-dynamic-scope\` is only supported for \`outletState\` (you used \`${key}\`).`, key === 'outletState'); this.outletState = value; @@ -71,7 +63,7 @@ export class DynamicScope implements GlimmerDynamicScope { class RootState { public id: string; public env: Environment; - public root: Opaque; + public root: Component | OutletView; public result: RenderResult | undefined; public shouldReflush: boolean; public destroyed: boolean; @@ -81,7 +73,7 @@ class RootState { public render: () => void; constructor( - root: Opaque, + root: Component | OutletView, env: Environment, template: OwnedTemplate, self: VersionedPathReference, @@ -101,7 +93,12 @@ class RootState { }; this.render = () => { - let iterator = template.render(self, parentElement, dynamicScope); + let iterator = template.renderLayout({ + self, + env, + builder: clientBuilder(env, { element: parentElement, nextSibling: null}), + dynamicScope + }); let iteratorResult: IteratorResult; do { @@ -125,7 +122,7 @@ class RootState { this.destroyed = true; this.env = undefined as any; - this.root = null; + this.root = null as any; this.result = undefined; this.render = undefined as any; @@ -145,11 +142,12 @@ class RootState { if (needsTransaction) { env.begin(); } - - result.destroy(); - - if (needsTransaction) { - env.commit(); + try { + result.destroy(); + } finally { + if (needsTransaction) { + env.commit(); + } } } } @@ -264,27 +262,22 @@ export abstract class Renderer { // renderer HOOKS appendOutletView(view: OutletView, target: Simple.Element) { - let definition = new TopLevelOutletComponentDefinition(view); - let outletStateReference = view.toReference(); - - this._appendDefinition(view, definition, target, outletStateReference); + let definition = createRootOutlet(view); + this._appendDefinition(view, curry(definition), target); } - appendTo(view: ComponentStateBucket, target: Simple.Element) { - let rootDef = new RootComponentDefinition(view); - - this._appendDefinition(view, rootDef, target); + appendTo(view: Component, target: Simple.Element) { + let definition = new RootComponentDefinition(view); + this._appendDefinition(view, curry(definition), target); } _appendDefinition( - root: Opaque, - definition: ComponentDefinition, - target: Simple.Element, - outletStateReference?: RootOutletStateReference) { - let self = new RootReference(definition); - let dynamicScope = new DynamicScope(null, outletStateReference || NULL_REFERENCE, outletStateReference); + root: OutletView | Component, + definition: CurriedComponentDefinition, + target: Simple.Element) { + let self = new UnboundReference(definition); + let dynamicScope = new DynamicScope(null, UNDEFINED_REFERENCE); let rootState = new RootState(root, this._env, this._rootTemplate, self, target, dynamicScope); - this._renderRoot(rootState); } @@ -292,13 +285,13 @@ export abstract class Renderer { this._scheduleRevalidate(); } - register(view: Opaque) { + register(view: any) { let id = getViewId(view); assert('Attempted to register a view with an id already in use: ' + id, !this._viewRegistry[id]); this._viewRegistry[id] = view; } - unregister(view: Opaque) { + unregister(view: any) { delete this._viewRegistry[getViewId(view)]; } @@ -379,44 +372,45 @@ export abstract class Renderer { do { env.begin(); - - // ensure that for the first iteration of the loop - // each root is processed - initialRootsLength = roots.length; - globalShouldReflush = false; - - for (let i = 0; i < roots.length; i++) { - let root = roots[i]; - - if (root.destroyed) { - // add to the list of roots to be removed - // they will be removed from `this._roots` later - removedRoots.push(root); - - // skip over roots that have been marked as destroyed - continue; + try { + // ensure that for the first iteration of the loop + // each root is processed + initialRootsLength = roots.length; + globalShouldReflush = false; + + for (let i = 0; i < roots.length; i++) { + let root = roots[i]; + + if (root.destroyed) { + // add to the list of roots to be removed + // they will be removed from `this._roots` later + removedRoots.push(root); + + // skip over roots that have been marked as destroyed + continue; + } + + let { shouldReflush } = root; + + // when processing non-initial reflush loops, + // do not process more roots than needed + if (i >= initialRootsLength && !shouldReflush) { + continue; + } + + root.options.alwaysRevalidate = shouldReflush; + // track shouldReflush based on this roots render result + shouldReflush = root.shouldReflush = runInTransaction(root, 'render'); + + // globalShouldReflush should be `true` if *any* of + // the roots need to reflush + globalShouldReflush = globalShouldReflush || shouldReflush; } - let { shouldReflush } = root; - - // when processing non-initial reflush loops, - // do not process more roots than needed - if (i >= initialRootsLength && !shouldReflush) { - continue; - } - - root.options.alwaysRevalidate = shouldReflush; - // track shouldReflush based on this roots render result - shouldReflush = root.shouldReflush = runInTransaction(root, 'render'); - - // globalShouldReflush should be `true` if *any* of - // the roots need to reflush - globalShouldReflush = globalShouldReflush || shouldReflush; + this._lastRevision = CURRENT_TAG.value(); + } finally { + env.commit(); } - - this._lastRevision = CURRENT_TAG.value(); - - env.commit(); } while (globalShouldReflush || roots.length > initialRootsLength); // remove any roots that were destroyed during this transaction diff --git a/packages/ember-glimmer/lib/resolver.ts b/packages/ember-glimmer/lib/resolver.ts new file mode 100644 index 00000000000..bbcedb81da7 --- /dev/null +++ b/packages/ember-glimmer/lib/resolver.ts @@ -0,0 +1,317 @@ +import { + ComponentCapabilities, + ComponentDefinition, + Opaque, + Option, + RuntimeResolver as IRuntimeResolver +} from '@glimmer/interfaces'; +import { LazyOpcodeBuilder, Macros, OpcodeBuilderConstructor, ParsedLayout, TemplateOptions, WrappedBuilder } from '@glimmer/opcode-compiler'; +import { LazyConstants, Program } from '@glimmer/program'; +import { + getDynamicVar, + Helper, + ModifierManager, + PartialDefinition, + TopLevelSyntax, +} from '@glimmer/runtime'; +import { privatize as P } from 'container'; +import { assert } from 'ember-debug'; +import { ENV } from 'ember-environment'; +import { _instrumentStart } from 'ember-metal'; +import { assign, LookupOptions, Owner, setOwner } from 'ember-utils'; +import { + lookupComponent, + lookupPartial, + OwnedTemplateMeta, +} from 'ember-views'; +import CompileTimeLookup from './compile-time-lookup'; +import { CurlyComponentDefinition } from './component-managers/curly'; +import { TemplateOnlyComponentDefinition } from './component-managers/template-only'; +import { isHelperFactory, isSimpleHelper } from './helper'; +import { default as classHelper } from './helpers/-class'; +import { default as htmlSafeHelper } from './helpers/-html-safe'; +import { default as inputTypeHelper } from './helpers/-input-type'; +import { default as normalizeClassHelper } from './helpers/-normalize-class'; +import { default as action } from './helpers/action'; +import { default as concat } from './helpers/concat'; +import { default as eachIn } from './helpers/each-in'; +import { default as get } from './helpers/get'; +import { default as hash } from './helpers/hash'; +import { inlineIf, inlineUnless } from './helpers/if-unless'; +import { default as log } from './helpers/log'; +import { default as mut } from './helpers/mut'; +import { default as queryParams } from './helpers/query-param'; +import { default as readonly } from './helpers/readonly'; +import { default as unbound } from './helpers/unbound'; +import ActionModifierManager from './modifiers/action'; +import { populateMacros } from './syntax'; +import { mountHelper } from './syntax/mount'; +import { outletHelper } from './syntax/outlet'; +import { renderHelper } from './syntax/render'; +import { Factory as TemplateFactory, Injections, OwnedTemplate } from './template'; +import { ClassBasedHelperReference, SimpleHelperReference } from './utils/references'; + +function instrumentationPayload(name: string) { + return { object: `component:${name}` }; +} + +function makeOptions(moduleName: string) { + return moduleName !== undefined ? { source: `template:${moduleName}`} : undefined; +} + +const BUILTINS_HELPERS = { + 'if': inlineIf, + action, + concat, + get, + hash, + log, + mut, + 'query-params': queryParams, + readonly, + unbound, + 'unless': inlineUnless, + '-class': classHelper, + '-each-in': eachIn, + '-input-type': inputTypeHelper, + '-normalize-class': normalizeClassHelper, + '-html-safe': htmlSafeHelper, + '-get-dynamic-var': getDynamicVar, + '-mount': mountHelper, + '-outlet': outletHelper, + '-render': renderHelper, +}; + +const BUILTIN_MODIFIERS = { + action: new ActionModifierManager(), +}; + +export default class RuntimeResolver implements IRuntimeResolver { + public templateOptions: TemplateOptions = { + program: new Program(new LazyConstants(this)), + macros: new Macros(), + resolver: new CompileTimeLookup(this), + Builder: LazyOpcodeBuilder as OpcodeBuilderConstructor, + }; + + private handles: any[] = [ + undefined, // ensure no falsy handle + ]; + private objToHandle = new WeakMap(); + + private builtInHelpers: { + [name: string]: Helper | undefined; + } = BUILTINS_HELPERS; + + private builtInModifiers: { + [name: string]: ModifierManager; + } = BUILTIN_MODIFIERS; + + // supports directly imported late bound layouts on component.prototype.layout + private templateCache: WeakMap> = new WeakMap(); + + public templateCacheHits = 0; + public templateCacheMisses = 0; + + private wrapperCache: WeakMap> = new WeakMap(); + public wrapperCacheHits = 0; + public wrapperCacheMisses = 0; + + constructor() { + populateMacros(this.templateOptions.macros); + } + + /*** IRuntimeResolver ***/ + + /** + * Called while executing Append Op.PushDynamicComponentManager if string + */ + lookupComponent(name: string, meta: OwnedTemplateMeta): Option { + let handle = this.lookupComponentDefinition(name, meta); + if (handle === null) { + assert(`Could not find component named "${name}" (no component or template with that name was found)`); + return null; + } + return this.resolve(handle); + } + + /** + * Called by RuntimeConstants to lookup unresolved handles. + */ + resolve(handle: number): U { + return this.handles[handle]; + } + // End IRuntimeResolver + + /** + * Called by CompileTimeLookup compiling Unknown or Helper OpCode + */ + lookupHelper(name: string, meta: OwnedTemplateMeta): Option { + let handle = this._lookupHelper(name, meta); + if (handle !== null) { + return this.handle(handle); + } + return null; + } + + /** + * Called by CompileTimeLookup compiling the Component OpCode + */ + lookupComponentDefinition(name: string, meta: OwnedTemplateMeta): Option { + return this.handle(this._lookupComponentDefinition(name, meta)); + } + + /** + * Called by CompileTimeLookup compiling the + */ + lookupModifier(name: string, _meta: OwnedTemplateMeta): Option { + return this.handle(this._lookupModifier(name)); + } + + /** + * Called by CompileTimeLookup to lookup partial + */ + lookupPartial(name: string, meta: OwnedTemplateMeta): Option { + let partial = this._lookupPartial(name, meta); + return this.handle(partial); + } + + // end CompileTimeLookup + + /** + * Creates a template with injections from a directly imported template factory. + * @param templateFactory the directly imported template factory. + * @param owner the owner the template instance would belong to if resolved + */ + createTemplate(factory: TemplateFactory, owner: Owner): OwnedTemplate { + let cache = this.templateCache.get(owner); + if (cache === undefined) { + cache = new WeakMap(); + this.templateCache.set(owner, cache); + } + let template = cache.get(factory); + if (template === undefined) { + const injections: Injections = { options: this.templateOptions }; + setOwner(injections, owner); + template = factory.create(injections); + cache.set(factory, template); + this.templateCacheMisses++; + } else { + this.templateCacheHits++; + } + return template; + } + + /** + * Returns a wrapped layout for the specified layout. + * + * The template singletons are cached by DI but we need to create a wrapped layout + * (a layout that has instructions for creating a wrapping element and calling hooks + * on the manager to set it up). + * + * @param template the layout to wrap. + */ + getWrappedLayout(template: OwnedTemplate, capabilities: ComponentCapabilities) { + // TODO move wrapper compilation into glimmer + let cache = this.wrapperCache.get(template); + if (cache === undefined) { + cache = new Map(); + this.wrapperCache.set(template, cache); + } + let wrapper = cache.get(capabilities); + if (wrapper === undefined) { + const compileOptions = assign({}, this.templateOptions, { asPartial: false, referrer: template.referrer}); + // TODO fix this getting private + const parsed: ParsedLayout = (template as any).parsedLayout; + wrapper = new WrappedBuilder(compileOptions, parsed, capabilities); + cache.set(capabilities, wrapper); + this.wrapperCacheMisses++; + } else { + this.wrapperCacheHits++; + } + return wrapper; + } + + // needed for lazy compile time lookup + private handle(obj: any | null | undefined) { + if (obj === undefined || obj === null) { + return null; + } + let handle: number | undefined = this.objToHandle.get(obj); + if (handle === undefined) { + handle = this.handles.push(obj) - 1; + this.objToHandle.set(obj, handle); + } + return handle; + } + + private _lookupHelper(name: string, meta: OwnedTemplateMeta): Option { + const helper = this.builtInHelpers[name]; + if (helper !== undefined) { + return helper; + } + + const { owner, moduleName } = meta; + + const options: LookupOptions | undefined = makeOptions(moduleName); + + const factory = owner.factoryFor(`helper:${name}`, options) || owner.factoryFor(`helper:${name}`); + + if (!isHelperFactory(factory)) { + return null; + } + + if (isSimpleHelper(factory)) { + const helper = factory.create().compute; + return (_vm, args) => { + return SimpleHelperReference.create(helper, args.capture()); + }; + } + + return (vm, args) => { + const helper = factory.create(); + vm.newDestroyable(helper); + return ClassBasedHelperReference.create(helper, args.capture()); + }; + } + + private _lookupPartial(name: string, meta: OwnedTemplateMeta): PartialDefinition { + const template = lookupPartial(name, meta.owner); + const partial = new PartialDefinition( name, lookupPartial(name, meta.owner)); + + if (template) { + return partial; + } else { + throw new Error(`${name} is not a partial`); + } + } + + private _lookupModifier(name: string) { + let modifier = this.builtInModifiers[name]; + if (modifier !== undefined) { + return modifier; + } + return null; + } + + private _lookupComponentDefinition(name: string, meta: OwnedTemplateMeta): Option { + let { layout, component } = lookupComponent(meta.owner, name, makeOptions(meta.moduleName)); + + if (layout && !component && ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) { + return new TemplateOnlyComponentDefinition(layout); + } + + let finalizer = _instrumentStart('render.getComponentDefinition', instrumentationPayload, name); + let definition = (layout || component) ? + new CurlyComponentDefinition( + name, + undefined, + component || meta.owner.factoryFor(P`component:-default`), + null, + layout + ) : null; + + finalizer(); + return definition; + } +} diff --git a/packages/ember-glimmer/lib/setup-registry.ts b/packages/ember-glimmer/lib/setup-registry.ts index cb06f456695..02f46806cba 100644 --- a/packages/ember-glimmer/lib/setup-registry.ts +++ b/packages/ember-glimmer/lib/setup-registry.ts @@ -11,12 +11,13 @@ import { NodeDOMTreeConstruction, } from './dom'; import Environment from './environment'; +import loc from './helpers/loc'; import { InertRenderer, InteractiveRenderer } from './renderer'; +import TemplateOptions from './template-options'; import ComponentTemplate from './templates/component'; import OutletTemplate from './templates/outlet'; import RootTemplate from './templates/root'; import OutletView from './views/outlet'; -import loc from './helpers/loc'; interface Registry { injection(name: string, name2: string, name3: string): void; @@ -63,7 +64,10 @@ export function setupEngineRegistry(registry: Registry) { registry.register(P`template:components/-default`, ComponentTemplate); registry.register('service:-glimmer-environment', Environment); - registry.injection('template', 'env', 'service:-glimmer-environment'); + + registry.register(P`template-options:main`, TemplateOptions); + + registry.injection('template', 'options', P`template-options:main`); registry.optionsForType('helper', { instantiate: false }); diff --git a/packages/ember-glimmer/lib/simple-dom.d.ts b/packages/ember-glimmer/lib/simple-dom.d.ts index b75dcd91bff..c608bc8f162 100644 --- a/packages/ember-glimmer/lib/simple-dom.d.ts +++ b/packages/ember-glimmer/lib/simple-dom.d.ts @@ -1,4 +1,4 @@ declare module "simple-dom" { - import { Simple } from "@glimmer/runtime"; + import { Simple } from "@glimmer/interfaces"; export interface Document extends Simple.Document {} } diff --git a/packages/ember-glimmer/lib/syntax.ts b/packages/ember-glimmer/lib/syntax.ts index 91637c30136..47ad705881b 100644 --- a/packages/ember-glimmer/lib/syntax.ts +++ b/packages/ember-glimmer/lib/syntax.ts @@ -1,57 +1,68 @@ +import { CompilableBlock, Macros, OpcodeBuilder } from '@glimmer/opcode-compiler'; +import { Option } from '@glimmer/util'; +import { Core } from '@glimmer/wire-format'; import { assert } from 'ember-debug'; -import { ENV } from 'ember-environment'; +import { OwnedTemplateMeta } from 'ember-views'; +import { EMBER_TEMPLATE_BLOCK_LET_HELPER } from 'ember/features'; +import CompileTimeLookup from './compile-time-lookup'; import { textAreaMacro } from './syntax/-text-area'; -import { - blockComponentMacro, - inlineComponentMacro, -} from './syntax/dynamic-component'; import { inputMacro } from './syntax/input'; +import { blockLetMacro } from './syntax/let'; import { mountMacro } from './syntax/mount'; import { outletMacro } from './syntax/outlet'; import { renderMacro } from './syntax/render'; import { hashToArgs } from './syntax/utils'; -import { blockLetMacro } from './syntax/let'; import { wrapComponentClassAttribute } from './utils/bindings'; -import { EMBER_TEMPLATE_BLOCK_LET_HELPER } from 'ember/features'; -function refineInlineSyntax(name: string, params: any[], hash: any, builder: any) { - assert(`You attempted to overwrite the built-in helper "${name}" which is not allowed. Please rename the helper.`, !(builder.env.builtInHelpers[name] && builder.env.owner.hasRegistration(`helper:${name}`))); - - let definition; - if (name.indexOf('-') > -1) { - definition = builder.env.getComponentDefinition(name, builder.meta.templateMeta); +function refineInlineSyntax(name: string, params: Option, hash: Option, builder: OpcodeBuilder): boolean { + assert( + `You attempted to overwrite the built-in helper "${name}" which is not allowed. Please rename the helper.`, + !( + (builder.resolver as CompileTimeLookup)['resolver']['builtInHelpers'][name] && + builder.referrer.owner.hasRegistration(`helper:${name}`) + ) + ); + if (name.indexOf('-') === -1) { + return false; } - if (definition) { - wrapComponentClassAttribute(hash); - builder.component.static(definition, [params, hashToArgs(hash), null, null]); + let handle = builder.resolver.lookupComponentDefinition(name, builder.referrer); + + if (handle !== null) { + builder.component.static(handle, [params === null ? [] : params, hashToArgs(hash), null, null]); return true; } return false; } -function refineBlockSyntax(name: string, params: any[], hash: any, _default: any, inverse: any, builder: any) { +function refineBlockSyntax(name: string, params: Core.Params, hash: Core.Hash, template: Option, inverse: Option, builder: OpcodeBuilder) { if (name.indexOf('-') === -1) { return false; } - let meta = builder.meta.templateMeta; - - let definition; - if (name.indexOf('-') > -1) { - definition = builder.env.getComponentDefinition(name, meta); - } + let handle = builder.resolver.lookupComponentDefinition(name, builder.referrer); - if (definition) { + if (handle !== null) { wrapComponentClassAttribute(hash); - builder.component.static(definition, [params, hashToArgs(hash), _default, inverse]); + builder.component.static(handle, [params, hashToArgs(hash), template, inverse]); return true; } - assert(`A component or helper named "${name}" could not be found`, builder.env.hasHelper(name, meta)); - - assert(`Helpers may not be used in the block form, for example {{#${name}}}{{/${name}}}. Please use a component, or alternatively use the helper in combination with a built-in Ember helper, for example {{#if (${name})}}{{/if}}.`, !builder.env.hasHelper(name, meta)); + assert(`A component or helper named "${name}" could not be found`, builder.referrer.owner.hasRegistration(`helper:${name}`)); + + assert( + `Helpers may not be used in the block form, for example {{#${name}}}{{/${name}}}. Please use a component, or alternatively use the helper in combination with a built-in Ember helper, for example {{#if (${name})}}{{/if}}.`, + !(() => { + const resolver = (builder.resolver as CompileTimeLookup)['resolver']; + const { owner, moduleName } = builder.referrer; + if (name === 'component' || resolver['builtInHelpers'][name]) { + return true; + } + let options = { source: `template:${moduleName}` }; + return owner.hasRegistration(`helper:${name}`, options) || owner.hasRegistration(`helper:${name}`); + })() + ); return false; } @@ -65,19 +76,14 @@ export function registerMacros(macro: any) { experimentalMacros.push(macro); } -export function populateMacros(blocks: any, inlines: any) { +export function populateMacros(macros: Macros) { + let { inlines, blocks } = macros; inlines.add('outlet', outletMacro); - inlines.add('component', inlineComponentMacro); - - if (ENV._ENABLE_RENDER_SUPPORT === true) { - inlines.add('render', renderMacro); - } - + inlines.add('render', renderMacro); inlines.add('mount', mountMacro); inlines.add('input', inputMacro); inlines.add('textarea', textAreaMacro); inlines.addMissing(refineInlineSyntax); - blocks.add('component', blockComponentMacro); if (EMBER_TEMPLATE_BLOCK_LET_HELPER === true) { blocks.add('let', blockLetMacro); } diff --git a/packages/ember-glimmer/lib/syntax/-text-area.ts b/packages/ember-glimmer/lib/syntax/-text-area.ts index d1139df630f..b670b734610 100644 --- a/packages/ember-glimmer/lib/syntax/-text-area.ts +++ b/packages/ember-glimmer/lib/syntax/-text-area.ts @@ -1,9 +1,13 @@ +import { Option } from '@glimmer/interfaces'; +import { OpcodeBuilder } from '@glimmer/opcode-compiler'; +import * as WireFormat from '@glimmer/wire-format'; +import { OwnedTemplateMeta } from 'ember-views'; import { wrapComponentClassAttribute } from '../utils/bindings'; import { hashToArgs } from './utils'; -export function textAreaMacro(_name: string, params: any[], hash: any, builder: any) { - let definition = builder.env.getComponentDefinition('-text-area', builder.meta.templateMeta); +export function textAreaMacro(_name: string, params: Option, hash: Option, builder: OpcodeBuilder) { + let definition = builder.resolver.lookupComponentDefinition('-text-area', builder.referrer); wrapComponentClassAttribute(hash); - builder.component.static(definition, [params, hashToArgs(hash), null, null]); + builder.component.static(definition!, [params || [], hashToArgs(hash), null, null]); return true; } diff --git a/packages/ember-glimmer/lib/syntax/dynamic-component.ts b/packages/ember-glimmer/lib/syntax/dynamic-component.ts deleted file mode 100644 index ca5643759e3..00000000000 --- a/packages/ember-glimmer/lib/syntax/dynamic-component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - Arguments, - Environment, - isComponentDefinition, - VM -} from '@glimmer/runtime'; -import { assert } from 'ember-debug'; -import { hashToArgs } from './utils'; - -function dynamicComponentFor(vm: VM, args: Arguments, meta: any) { - let env = vm.env; - let nameRef = args.positional.at(0); - - return new DynamicComponentReference({ nameRef, env, meta, args: null }); -} - -export function dynamicComponentMacro(params: any[], hash: any, _default: any, _inverse: any, builder: any) { - let definitionArgs = [params.slice(0, 1), null, null, null]; - let args = [params.slice(1), hashToArgs(hash), null, null]; - builder.component.dynamic(definitionArgs, dynamicComponentFor, args); - return true; -} - -export function blockComponentMacro(params: any[], hash: any, _default: any, inverse: any, builder: any) { - let definitionArgs = [params.slice(0, 1), null, null, null]; - let args = [params.slice(1), hashToArgs(hash), _default, inverse]; - builder.component.dynamic(definitionArgs, dynamicComponentFor, args); - return true; -} - -export function inlineComponentMacro(_name: string, params: any[], hash: any, builder: any) { - let definitionArgs = [params.slice(0, 1), null, null, null]; - let args = [params.slice(1), hashToArgs(hash), null, null]; - builder.component.dynamic(definitionArgs, dynamicComponentFor, args); - return true; -} - -class DynamicComponentReference { - public tag: any; - public nameRef: any; - public env: Environment; - public meta: any; - public args: Arguments; - - constructor({ nameRef, env, meta, args }: any) { - this.tag = nameRef.tag; - this.nameRef = nameRef; - this.env = env; - this.meta = meta; - this.args = args; - } - - value() { - let { env, nameRef, meta } = this; - let nameOrDef = nameRef.value(); - - if (typeof nameOrDef === 'string') { - let definition = env.getComponentDefinition(nameOrDef, meta); - - // tslint:disable-next-line:max-line-length - assert(`Could not find component named "${nameOrDef}" (no component or template with that name was found)`, !!definition); - - return definition; - } else if (isComponentDefinition(nameOrDef)) { - return nameOrDef; - } else { - return null; - } - } - - get() { /* NOOP */ } -} diff --git a/packages/ember-glimmer/lib/syntax/input.ts b/packages/ember-glimmer/lib/syntax/input.ts index d1b7e519301..1ad85050fee 100644 --- a/packages/ember-glimmer/lib/syntax/input.ts +++ b/packages/ember-glimmer/lib/syntax/input.ts @@ -1,14 +1,17 @@ /** @module ember */ +import { Option } from '@glimmer/interfaces'; +import { OpcodeBuilder } from '@glimmer/opcode-compiler'; +import * as WireFormat from '@glimmer/wire-format'; import { assert } from 'ember-debug'; +import { OwnedTemplateMeta } from 'ember-views'; import { wrapComponentClassAttribute } from '../utils/bindings'; -import { dynamicComponentMacro } from './dynamic-component'; import { hashToArgs } from './utils'; -function buildSyntax(type: string, params: any[], hash: any, builder: any) { - let definition = builder.env.getComponentDefinition(type, builder.meta.templateMeta); - builder.component.static(definition, [params, hashToArgs(hash), null, null]); +function buildSyntax(type: string, params: any[], hash: any, builder: OpcodeBuilder) { + let definition = builder.resolver.lookupComponentDefinition(type, builder.referrer); + builder.component.static(definition!, [params, hashToArgs(hash), null, null]); return true; } @@ -148,35 +151,34 @@ function buildSyntax(type: string, params: any[], hash: any, builder: any) { @public */ -export function inputMacro(_name: string, params: any[], hash: any[], builder: any) { - let keys; - let values; - let typeIndex = -1; - let valueIndex = -1; - - if (hash) { - keys = hash[0]; - values = hash[1]; - typeIndex = keys.indexOf('type'); - valueIndex = keys.indexOf('value'); +export function inputMacro(_name: string, params: Option, hash: Option, builder: OpcodeBuilder) { + if (params === null) { + params = []; } - - if (!params) { params = []; } - - if (typeIndex > -1) { - let typeArg = values[typeIndex]; - if (Array.isArray(typeArg)) { - return dynamicComponentMacro(params, hash, null, null, builder); - } else if (typeArg === 'checkbox') { - assert( - '{{input type=\'checkbox\'}} does not support setting `value=someBooleanValue`; ' + - 'you must use `checked=someBooleanValue` instead.', - valueIndex === -1, - ); - wrapComponentClassAttribute(hash); - return buildSyntax('-checkbox', params, hash, builder); + if (hash !== null) { + let keys = hash[0]; + let values = hash[1]; + let typeIndex = keys.indexOf('type'); + + if (typeIndex > -1) { + let typeArg = values[typeIndex]; + if (Array.isArray(typeArg)) { + // there is an AST plugin that converts this to an expression + // it really should just compile in the component call too. + let inputTypeExpr = params.shift() as WireFormat.Expression; + builder.dynamicComponent(inputTypeExpr, params, hash, true, null, null); + return true; + } + if (typeArg === 'checkbox') { + assert( + '{{input type=\'checkbox\'}} does not support setting `value=someBooleanValue`; ' + + 'you must use `checked=someBooleanValue` instead.', + keys.indexOf('value') === -1, + ); + wrapComponentClassAttribute(hash); + return buildSyntax('-checkbox', params, hash, builder); + } } } - return buildSyntax('-text-field', params, hash, builder); } diff --git a/packages/ember-glimmer/lib/syntax/let.ts b/packages/ember-glimmer/lib/syntax/let.ts index e48daf9f961..399759b5421 100644 --- a/packages/ember-glimmer/lib/syntax/let.ts +++ b/packages/ember-glimmer/lib/syntax/let.ts @@ -1,8 +1,12 @@ +import { Option } from '@glimmer/interfaces'; +import { CompilableBlock, OpcodeBuilder } from '@glimmer/opcode-compiler'; +import * as WireFormat from '@glimmer/wire-format'; +import { OwnedTemplateMeta } from 'ember-views'; + /** @module ember */ -import { compileList } from "@glimmer/runtime"; /** The `let` helper receives one or more positional arguments and yields them out as block params. @@ -38,7 +42,14 @@ import { compileList } from "@glimmer/runtime"; @for Ember.Templates.helpers @public */ -export function blockLetMacro(params: any, _hash: any, _default: any, _inverse: any, builder: any) { - compileList(params, builder); - builder.invokeStatic(_default, params.length); +export function blockLetMacro(params: WireFormat.Core.Params, _hash: WireFormat.Core.Hash, template: Option, _inverse: Option, builder: OpcodeBuilder) { + if (template !== null) { + if (params !== null) { + builder.compileParams(params); + builder.invokeStaticBlock(template, params.length); + } else { + builder.invokeStatic(template); + } + } + return true; } diff --git a/packages/ember-glimmer/lib/syntax/mount.ts b/packages/ember-glimmer/lib/syntax/mount.ts index bc636b0c5bb..1d2d77af890 100644 --- a/packages/ember-glimmer/lib/syntax/mount.ts +++ b/packages/ember-glimmer/lib/syntax/mount.ts @@ -1,21 +1,28 @@ /** @module ember */ +import { Opaque, Option } from '@glimmer/interfaces'; +import { OpcodeBuilder } from '@glimmer/opcode-compiler'; +import { Tag, VersionedPathReference } from '@glimmer/reference'; import { Arguments, + CurriedComponentDefinition, + curry, + UNDEFINED_REFERENCE, VM } from '@glimmer/runtime'; +import * as WireFormat from '@glimmer/wire-format'; import { assert } from 'ember-debug'; +import { OwnedTemplateMeta } from 'ember-views'; import { EMBER_ENGINES_MOUNT_PARAMS } from 'ember/features'; -import Environment from '../environment'; import { MountDefinition } from '../component-managers/mount'; -import { hashToArgs } from './utils'; +import Environment from '../environment'; -function dynamicEngineFor(vm: VM, args: Arguments, meta: any) { - let env = vm.env; +export function mountHelper(vm: VM, args: Arguments): VersionedPathReference { + let env = vm.env as Environment; let nameRef = args.positional.at(0); - - return new DynamicEngineReference({ nameRef, env, meta }); + let modelRef = args.named.has('model') ? args.named.get('model') : undefined; + return new DynamicEngineReference(nameRef, env, modelRef); } /** @@ -59,70 +66,74 @@ function dynamicEngineFor(vm: VM, args: Arguments, meta: any) { @category ember-application-engines @public */ -export function mountMacro(_name: string, params: any[], hash: any, builder: any) { +export function mountMacro(_name: string, params: Option, hash: Option, builder: OpcodeBuilder) { if (EMBER_ENGINES_MOUNT_PARAMS) { assert( 'You can only pass a single positional argument to the {{mount}} helper, e.g. {{mount "chat-engine"}}.', - params.length === 1, + params!.length === 1, ); } else { assert( 'You can only pass a single argument to the {{mount}} helper, e.g. {{mount "chat-engine"}}.', - params.length === 1 && hash === null, + params!.length === 1 && hash === null, ); } - let definitionArgs = [params.slice(0, 1), null, null, null]; - let args = [null, hashToArgs(hash), null, null]; - builder.component.dynamic(definitionArgs, dynamicEngineFor, args); + let expr: WireFormat.Expressions.Helper = [WireFormat.Ops.Helper, '-mount', params || [], hash]; + builder.dynamicComponent(expr, [], null, false, null, null); return true; } class DynamicEngineReference { - public tag: any; - public nameRef: any; + public tag: Tag; + public nameRef: VersionedPathReference; + public modelRef: VersionedPathReference | undefined; public env: Environment; - public meta: any; - private _lastName: any; - private _lastDef: any; - constructor({ nameRef, env, meta }: any) { + private _lastName: string | null; + private _lastDef: CurriedComponentDefinition | null; + constructor(nameRef: VersionedPathReference, env: Environment, modelRef: VersionedPathReference | undefined) { this.tag = nameRef.tag; this.nameRef = nameRef; + this.modelRef = modelRef; this.env = env; - this.meta = meta; - this._lastName = undefined; - this._lastDef = undefined; + this._lastName = null; + this._lastDef = null; } value() { - let { env, nameRef /*meta*/ } = this; - let nameOrDef = nameRef.value(); + let { env, nameRef, modelRef } = this; + let name = nameRef.value(); - if (typeof nameOrDef === 'string') { - if (this._lastName === nameOrDef) { + if (typeof name === 'string') { + if (this._lastName === name) { return this._lastDef; } assert( - `You used \`{{mount '${nameOrDef}'}}\`, but the engine '${nameOrDef}' can not be found.`, - env.owner.hasRegistration(`engine:${nameOrDef}`), + `You used \`{{mount '${name}'}}\`, but the engine '${name}' can not be found.`, + env.owner.hasRegistration(`engine:${name}`), ); - if (!env.owner.hasRegistration(`engine:${nameOrDef}`)) { + if (!env.owner.hasRegistration(`engine:${name}`)) { return null; } - this._lastName = nameOrDef; - this._lastDef = new MountDefinition(nameOrDef); + this._lastName = name; + this._lastDef = curry(new MountDefinition(name, modelRef)); return this._lastDef; } else { assert( - `Invalid engine name '${nameOrDef}' specified, engine name must be either a string, null or undefined.`, - nameOrDef === null || nameOrDef === undefined, + `Invalid engine name '${name}' specified, engine name must be either a string, null or undefined.`, + name === null || name === undefined, ); - + this._lastDef = null; + this._lastName = null; return null; } } + + get() { + return UNDEFINED_REFERENCE; + } } diff --git a/packages/ember-glimmer/lib/syntax/outlet.ts b/packages/ember-glimmer/lib/syntax/outlet.ts index 1a4e190b024..149c60922de 100644 --- a/packages/ember-glimmer/lib/syntax/outlet.ts +++ b/packages/ember-glimmer/lib/syntax/outlet.ts @@ -1,87 +1,18 @@ -import { - combine, - ConstReference, - TagWrapper, - UpdatableTag, -} from '@glimmer/reference'; +import { Option } from '@glimmer/interfaces'; +import { OpcodeBuilder } from '@glimmer/opcode-compiler'; +import { ConstReference, Reference, Tag, VersionedPathReference } from '@glimmer/reference'; import { Arguments, + CurriedComponentDefinition, + curry, + UNDEFINED_REFERENCE, VM, } from '@glimmer/runtime'; -import { OutletComponentDefinition } from '../component-managers/outlet'; +import * as WireFormat from '@glimmer/wire-format'; +import { OwnedTemplateMeta } from 'ember-views'; +import { OutletComponentDefinition, OutletDefinitionState } from '../component-managers/outlet'; import { DynamicScope } from '../renderer'; - -class OutletComponentReference { - public outletNameRef: any; - public parentOutletStateRef: any; - public definition: any; - public lastState: any; - public outletStateTag: TagWrapper; - public tag: any; - - constructor(outletNameRef: any, parentOutletStateRef: any) { - this.outletNameRef = outletNameRef; - this.parentOutletStateRef = parentOutletStateRef; - this.definition = null; - this.lastState = null; - let outletStateTag = this.outletStateTag = UpdatableTag.create(parentOutletStateRef.tag); - this.tag = combine([outletStateTag.inner, outletNameRef.tag]); - } - - value() { - let { outletNameRef, parentOutletStateRef, definition, lastState } = this; - - let outletName = outletNameRef.value(); - let outletStateRef = parentOutletStateRef.get('outlets').get(outletName); - let newState = this.lastState = outletStateRef.value(); - - this.outletStateTag.inner.update(outletStateRef.tag); - - definition = revalidate(definition, lastState, newState); - - let hasTemplate = newState && newState.render.template; - - if (definition) { - return definition; - } else if (hasTemplate) { - return this.definition = new OutletComponentDefinition(outletName, newState.render.template); - } else { - return this.definition = null; - } - } -} - -function revalidate(definition: any, lastState: any, newState: any) { - if (!lastState && !newState) { - return definition; - } - - if (!lastState && newState || lastState && !newState) { - return null; - } - - if ( - newState.render.template === lastState.render.template && - newState.render.controller === lastState.render.controller - ) { - return definition; - } - - return null; -} - -function outletComponentFor(vm: VM, args: Arguments) { - let { outletState } = vm.dynamicScope() as DynamicScope; - - let outletNameRef; - if (args.positional.length === 0) { - outletNameRef = new ConstReference('main'); - } else { - outletNameRef = args.positional.at(0); - } - - return new OutletComponentReference(outletNameRef, outletState); -} +import { OutletReference, OutletState } from '../utils/outlet'; /** The `{{outlet}}` helper lets you specify where a child route will render in @@ -133,12 +64,76 @@ function outletComponentFor(vm: VM, args: Arguments) { @for Ember.Templates.helpers @public */ -export function outletMacro(_name: string, params: any[], _hash: any[], builder: any) { - if (!params) { - params = []; +export function outletHelper(vm: VM, args: Arguments) { + let scope = vm.dynamicScope() as DynamicScope; + let nameRef: Reference; + if (args.positional.length === 0) { + nameRef = new ConstReference('main'); + } else { + nameRef = args.positional.at>(0); } - let definitionArgs = [params.slice(0, 1), null, null, null]; - let emptyArgs = [[], null, null, null]; // FIXME - builder.component.dynamic(definitionArgs, outletComponentFor, emptyArgs); + return new OutletComponentReference(new OutletReference(scope.outletState, nameRef)); +} + +export function outletMacro(_name: string, params: Option, hash: Option, builder: OpcodeBuilder) { + let expr: WireFormat.Expressions.Helper = [WireFormat.Ops.Helper, '-outlet', params || [], hash]; + builder.dynamicComponent(expr, [], null, false, null, null); return true; } + +class OutletComponentReference implements VersionedPathReference { + public tag: Tag; + private definition: CurriedComponentDefinition | null; + private lastState: OutletDefinitionState | null; + + constructor(private outletRef: VersionedPathReference) { + this.definition = null; + this.lastState = null; + // The router always dirties the root state. + this.tag = outletRef.tag; + } + + value(): CurriedComponentDefinition | null { + let state = stateFor(this.outletRef); + if (validate(state, this.lastState)) { + return this.definition; + } + this.lastState = state; + let definition = null; + if (state !== null) { + definition = curry(new OutletComponentDefinition(state)); + } + return this.definition = definition; + } + + get(_key: string) { + return UNDEFINED_REFERENCE; + } +} + +function stateFor(ref: VersionedPathReference): OutletDefinitionState | null { + let outlet = ref.value(); + if (outlet === undefined) return null; + let render = outlet.render; + if (render === undefined) return null; + let template = render.template; + if (template === undefined) return null; + return { + ref, + name: render.name, + outlet: render.outlet, + template, + controller: render.controller, + }; +} + +function validate(state: OutletDefinitionState | null, lastState: OutletDefinitionState | null) { + if (state === null) { + return lastState === null; + } + if (lastState === null) { + return false; + } + return state.template === lastState.template && + state.controller === lastState.controller; +} diff --git a/packages/ember-glimmer/lib/syntax/render.ts b/packages/ember-glimmer/lib/syntax/render.ts index a7db291018a..1fb7ebdcb44 100644 --- a/packages/ember-glimmer/lib/syntax/render.ts +++ b/packages/ember-glimmer/lib/syntax/render.ts @@ -5,12 +5,19 @@ Remove after 3.4 once _ENABLE_RENDER_SUPPORT flag is no longer needed. */ -import { ConstReference, isConst } from '@glimmer/reference'; +import { Option } from '@glimmer/interfaces'; +import { OpcodeBuilder } from '@glimmer/opcode-compiler'; +import { isConst, VersionedPathReference } from '@glimmer/reference'; import { Arguments, + CurriedComponentDefinition, + curry, VM, } from '@glimmer/runtime'; +import * as WireFormat from '@glimmer/wire-format'; import { assert } from 'ember-debug'; +import { ENV } from 'ember-environment'; +import { OwnedTemplateMeta } from 'ember-views'; import { NON_SINGLETON_RENDER_MANAGER, RenderDefinition, @@ -18,9 +25,9 @@ import { } from '../component-managers/render'; import Environment from '../environment'; import { OwnedTemplate } from '../template'; -import { hashToArgs } from './utils'; +import { UnboundReference } from '../utils/references'; -function makeComponentDefinition(vm: VM, args: Arguments) { +export function renderHelper(vm: VM, args: Arguments): VersionedPathReference { let env = vm.env as Environment; let nameRef = args.positional.at(0); @@ -33,7 +40,7 @@ function makeComponentDefinition(vm: VM, args: Arguments) { // tslint:disable-next-line:max-line-length assert(`You used \`{{render '${templateName}'}}\`, but '${templateName}' can not be found as a template.`, env.owner.hasRegistration(`template:${templateName}`)); - let template = env.owner.lookup(`template:${templateName}`); + let template = env.owner.lookup(`template:${templateName}`); let controllerName: string; @@ -53,9 +60,12 @@ function makeComponentDefinition(vm: VM, args: Arguments) { } if (args.positional.length === 1) { - return new ConstReference(new RenderDefinition(controllerName, template, env, SINGLETON_RENDER_MANAGER)); + let def = new RenderDefinition(controllerName, template, SINGLETON_RENDER_MANAGER); + return UnboundReference.create(curry(def)); } else { - return new ConstReference(new RenderDefinition(controllerName, template, env, NON_SINGLETON_RENDER_MANAGER)); + let def = new RenderDefinition(controllerName, template, NON_SINGLETON_RENDER_MANAGER); + let captured = args.capture(); + return UnboundReference.create(curry(def, captured)); } } @@ -131,12 +141,14 @@ function makeComponentDefinition(vm: VM, args: Arguments) { @public @deprecated Use a component instead */ -export function renderMacro(_name: string, params: any[], hash: any[], builder: any) { - if (!params) { - params = []; +export function renderMacro(_name: string, params: Option, hash: Option, builder: OpcodeBuilder) { + if (ENV._ENABLE_RENDER_SUPPORT === true) { + // TODO needs makeComponentDefinition a helper that returns a curried definition + // TODO not sure all args are for definition or component + // likely the controller name should be a arg to create? + let expr: WireFormat.Expressions.Helper = [WireFormat.Ops.Helper, '-render', params || [], hash]; + builder.dynamicComponent(expr, null, null, false, null, null); + return true; } - let definitionArgs = [params.slice(0), hash, null, null]; - let args = [params.slice(1), hashToArgs(hash), null, null]; - builder.component.dynamic(definitionArgs, makeComponentDefinition, args); - return true; + return false; } diff --git a/packages/ember-glimmer/lib/syntax/utils.ts b/packages/ember-glimmer/lib/syntax/utils.ts index 69d357a106e..4cdf934920d 100644 --- a/packages/ember-glimmer/lib/syntax/utils.ts +++ b/packages/ember-glimmer/lib/syntax/utils.ts @@ -1,5 +1,8 @@ -export function hashToArgs(hash: any[]) { - if (hash === null) { return null; } - let names = hash[0].map((key: string) => `@${key}`); +import { Option } from '@glimmer/util'; +import { Core } from '@glimmer/wire-format'; + +export function hashToArgs(hash: Option): Option { + if (hash === null) return null; + let names = hash[0].map(key => `@${key}`); return [names, hash[1]]; } diff --git a/packages/ember-glimmer/lib/template-options.ts b/packages/ember-glimmer/lib/template-options.ts new file mode 100644 index 00000000000..15535f1c4d3 --- /dev/null +++ b/packages/ember-glimmer/lib/template-options.ts @@ -0,0 +1,10 @@ +import { TemplateOptions } from '@glimmer/opcode-compiler'; +import { OwnedTemplateMeta } from 'ember-views'; +import RuntimeResolver from './resolver'; + +// factory for DI +export default { + create(): TemplateOptions { + return new RuntimeResolver().templateOptions; + } +}; diff --git a/packages/ember-glimmer/lib/template.ts b/packages/ember-glimmer/lib/template.ts index 8131b7af028..e9cb47cd2e1 100644 --- a/packages/ember-glimmer/lib/template.ts +++ b/packages/ember-glimmer/lib/template.ts @@ -1,48 +1,42 @@ +import { TemplateOptions } from '@glimmer/opcode-compiler'; import { Template, templateFactory, TemplateFactory, } from '@glimmer/runtime'; -import { OWNER } from 'ember-utils'; +import { SerializedTemplateWithLazyBlock } from '@glimmer/wire-format'; +import { getOwner } from 'ember-utils'; +import { OwnedTemplateMeta, StaticTemplateMeta } from 'ember-views'; -export interface Container { - lookup(name: string): T; - factoryFor(name: string): T; - buildChildEngineInstance(name: string): T; - hasRegistration(name: string, options?: any): boolean; +export type StaticTemplate = SerializedTemplateWithLazyBlock; +export type OwnedTemplate = Template; + +export default function template(json: StaticTemplate): Factory { + return new FactoryWrapper(templateFactory(json)); } -export type OwnedTemplate = Template<{ - moduleName: string; - owner: Container; -}>; +export interface Injections { + options: TemplateOptions; + [key: string]: any; +} -export class WrappedTemplateFactory { +export interface Factory { id: string; - meta: { - moduleName: string; - }; + meta: StaticTemplateMeta; + create(injections: Injections): OwnedTemplate; +} - constructor(public factory: TemplateFactory<{ - moduleName: string; - }, { - owner: Container; - }>) { +class FactoryWrapper implements Factory { + public id: string; + public meta: StaticTemplateMeta; + + constructor(public factory: TemplateFactory) { this.id = factory.id; this.meta = factory.meta; } - create(props: any): OwnedTemplate { - let owner = props[OWNER]; - return this.factory.create(props.env, { owner }); + create(injections: Injections): OwnedTemplate { + const owner = getOwner(injections); + return this.factory.create(injections.options, { owner }); } } - -export default function template(json: any) { - const factory = templateFactory<{ - moduleName: string; - }, { - owner: Container; - }>(json); - return new WrappedTemplateFactory(factory); -} diff --git a/packages/ember-glimmer/lib/template_registry.ts b/packages/ember-glimmer/lib/template_registry.ts index 2a8896dc3fd..68901bc9787 100644 --- a/packages/ember-glimmer/lib/template_registry.ts +++ b/packages/ember-glimmer/lib/template_registry.ts @@ -1,9 +1,9 @@ -import { WrappedTemplateFactory } from './template'; +import { Factory } from './template'; // STATE within a module is frowned upon, this exists // to support Ember.TEMPLATES but shield ember internals from this legacy // global API. interface TemplatesRegistry { - [name: string]: WrappedTemplateFactory; + [name: string]: Factory; } let TEMPLATES: TemplatesRegistry = {}; @@ -15,7 +15,7 @@ export function getTemplates() { return TEMPLATES; } -export function getTemplate(name: string): WrappedTemplateFactory | void { +export function getTemplate(name: string): Factory | void { if (TEMPLATES.hasOwnProperty(name)) { return TEMPLATES[name]; } @@ -25,6 +25,6 @@ export function hasTemplate(name: string): boolean { return TEMPLATES.hasOwnProperty(name); } -export function setTemplate(name: string, template: WrappedTemplateFactory): WrappedTemplateFactory { +export function setTemplate(name: string, template: Factory): Factory { return TEMPLATES[name] = template; } diff --git a/packages/ember-glimmer/lib/templates/component.d.ts b/packages/ember-glimmer/lib/templates/component.d.ts index 01e88b1186d..786bb5e91a8 100644 --- a/packages/ember-glimmer/lib/templates/component.d.ts +++ b/packages/ember-glimmer/lib/templates/component.d.ts @@ -1,3 +1,3 @@ -import { WrappedTemplateFactory } from '../template'; -declare const TEMPLATE: WrappedTemplateFactory; +import { Factory } from '../template'; +declare const TEMPLATE: Factory; export default TEMPLATE; diff --git a/packages/ember-glimmer/lib/templates/empty.d.ts b/packages/ember-glimmer/lib/templates/empty.d.ts index 01e88b1186d..786bb5e91a8 100644 --- a/packages/ember-glimmer/lib/templates/empty.d.ts +++ b/packages/ember-glimmer/lib/templates/empty.d.ts @@ -1,3 +1,3 @@ -import { WrappedTemplateFactory } from '../template'; -declare const TEMPLATE: WrappedTemplateFactory; +import { Factory } from '../template'; +declare const TEMPLATE: Factory; export default TEMPLATE; diff --git a/packages/ember-glimmer/lib/templates/link-to.d.ts b/packages/ember-glimmer/lib/templates/link-to.d.ts index 01e88b1186d..786bb5e91a8 100644 --- a/packages/ember-glimmer/lib/templates/link-to.d.ts +++ b/packages/ember-glimmer/lib/templates/link-to.d.ts @@ -1,3 +1,3 @@ -import { WrappedTemplateFactory } from '../template'; -declare const TEMPLATE: WrappedTemplateFactory; +import { Factory } from '../template'; +declare const TEMPLATE: Factory; export default TEMPLATE; diff --git a/packages/ember-glimmer/lib/templates/outlet.d.ts b/packages/ember-glimmer/lib/templates/outlet.d.ts index 01e88b1186d..786bb5e91a8 100644 --- a/packages/ember-glimmer/lib/templates/outlet.d.ts +++ b/packages/ember-glimmer/lib/templates/outlet.d.ts @@ -1,3 +1,3 @@ -import { WrappedTemplateFactory } from '../template'; -declare const TEMPLATE: WrappedTemplateFactory; +import { Factory } from '../template'; +declare const TEMPLATE: Factory; export default TEMPLATE; diff --git a/packages/ember-glimmer/lib/templates/root.d.ts b/packages/ember-glimmer/lib/templates/root.d.ts index 01e88b1186d..786bb5e91a8 100644 --- a/packages/ember-glimmer/lib/templates/root.d.ts +++ b/packages/ember-glimmer/lib/templates/root.d.ts @@ -1,3 +1,3 @@ -import { WrappedTemplateFactory } from '../template'; -declare const TEMPLATE: WrappedTemplateFactory; +import { Factory } from '../template'; +declare const TEMPLATE: Factory; export default TEMPLATE; diff --git a/packages/ember-glimmer/lib/utils/bindings.ts b/packages/ember-glimmer/lib/utils/bindings.ts index 1c842f88914..15c64b4823d 100644 --- a/packages/ember-glimmer/lib/utils/bindings.ts +++ b/packages/ember-glimmer/lib/utils/bindings.ts @@ -1,31 +1,33 @@ -import { Opaque, Option } from '@glimmer/interfaces'; +import { + Opaque, + Option, + Simple +} from '@glimmer/interfaces'; import { CachedReference, combine, map, Reference, - referenceFromParts, - Tag, + Tag } from '@glimmer/reference'; import { ElementOperations, - Simple + PrimitiveReference } from '@glimmer/runtime'; -import { - Ops, -} from '@glimmer/wire-format'; +import { Core, Ops } from '@glimmer/wire-format'; import { assert } from 'ember-debug'; import { get } from 'ember-metal'; import { String as StringUtils } from 'ember-runtime'; -import { Component } from './curly-component-state-bucket'; import { ROOT_REF } from '../component'; +import { Component } from './curly-component-state-bucket'; +import { referenceFromParts } from './references'; import { htmlSafe, isHTMLSafe, SafeString } from './string'; -function referenceForKey(component: Component, key: string) { +export function referenceForKey(component: Component, key: string) { return component[ROOT_REF].get(key); } -function referenceForParts(component: Component, parts: string[]) { +function referenceForParts(component: Component, parts: string[]): Reference { let isAttrs = parts[0] === 'attrs'; // TODO deprecate this @@ -41,26 +43,28 @@ function referenceForParts(component: Component, parts: string[]) { } // TODO we should probably do this transform at build time -export function wrapComponentClassAttribute(hash: any[]) { - if (!hash) { - return hash; +export function wrapComponentClassAttribute(hash: Core.Hash) { + if (hash === null) { + return; } let [ keys, values ] = hash; - let index = keys.indexOf('class'); + let index = keys === null ? -1 : keys.indexOf('class'); if (index !== -1) { - let [ type ] = values[index]; + let value = values[index]; + if (!Array.isArray(value)) { + return; + } + + let [ type ] = value; if (type === Ops.Get || type === Ops.MaybeLocal) { - let getExp = values[index]; - let path = getExp[getExp.length - 1]; + let path = value[value.length - 1]; let propName = path[path.length - 1]; - hash[1][index] = [Ops.Helper, ['-class'], [getExp, propName]]; + values[index] = [Ops.Helper, '-class', [value, propName], null]; } } - - return hash; } export const AttributeBinding = { @@ -69,7 +73,7 @@ export const AttributeBinding = { if (colonIndex === -1) { assert('You cannot use class as an attributeBinding, use classNameBindings instead.', microsyntax !== 'class'); - return [microsyntax, microsyntax, true]; + return [microsyntax, microsyntax.toLowerCase(), true]; } else { let prop = microsyntax.substring(0, colonIndex); let attribute = microsyntax.substring(colonIndex + 1); @@ -80,7 +84,7 @@ export const AttributeBinding = { } }, - install(element: Simple.Element, component: Component, parsed: [string, string, boolean], operations: ElementOperations) { + install(_element: Simple.Element, component: Component, parsed: [string, string, boolean], operations: ElementOperations) { let [prop, attribute, isSimple] = parsed; if (attribute === 'id') { @@ -88,7 +92,9 @@ export const AttributeBinding = { if (elementId === undefined || elementId === null) { elementId = component.elementId; } - operations.addStaticAttribute(element, 'id', elementId); + elementId = PrimitiveReference.create(elementId); + operations.setAttribute('id', elementId, true, null); + // operations.addStaticAttribute(element, 'id', elementId); return; } @@ -101,7 +107,8 @@ export const AttributeBinding = { reference = new StyleBindingReference(reference, referenceForKey(component, 'isVisible')); } - operations.addDynamicAttribute(element, attribute, reference, false); + operations.setAttribute(attribute, reference, false, null); + // operations.addDynamicAttribute(element, attribute, reference, false); }, }; @@ -132,13 +139,17 @@ class StyleBindingReference extends CachedReference { } export const IsVisibleBinding = { - install(element: Simple.Element, component: Component, operations: ElementOperations) { - let ref = map(referenceForKey(component, 'isVisible'), this.mapStyleValue); - - // the upstream type for addDynamicAttribute's `value` argument - // appears to be incorrect. It is currently a Reference, I - // think it should be a Reference. - operations.addDynamicAttribute(element, 'style', ref as any as Reference, false); + install(_element: Simple.Element, component: Component, operations: ElementOperations) { + operations.setAttribute( + 'style', + map(referenceForKey(component, 'isVisible'), this.mapStyleValue), + false, + null + ); + // // the upstream type for addDynamicAttribute's `value` argument + // // appears to be incorrect. It is currently a Reference, I + // // think it should be a Reference. + // operations.addDynamicAttribute(element, 'style', ref as any as Reference, false); }, mapStyleValue(isVisible: boolean) { @@ -147,12 +158,12 @@ export const IsVisibleBinding = { }; export const ClassNameBinding = { - install(element: Simple.Element, component: Component, microsyntax: string, operations: ElementOperations) { + install(_element: Simple.Element, component: Component, microsyntax: string, operations: ElementOperations) { let [ prop, truthy, falsy ] = microsyntax.split(':'); let isStatic = prop === ''; if (isStatic) { - operations.addStaticAttribute(element, 'class', truthy); + operations.setAttribute('class', PrimitiveReference.create(truthy), true, null); } else { let isPath = prop.indexOf('.') > -1; let parts = isPath ? prop.split('.') : []; @@ -165,10 +176,11 @@ export const ClassNameBinding = { ref = new ColonClassNameBindingReference(value, truthy, falsy); } - // the upstream type for addDynamicAttribute's `value` argument - // appears to be incorrect. It is currently a Reference, I - // think it should be a Reference. - operations.addDynamicAttribute(element, 'class', ref as any as Reference, false); + operations.setAttribute('class', ref, false, null); + // // the upstream type for addDynamicAttribute's `value` argument + // // appears to be incorrect. It is currently a Reference, I + // // think it should be a Reference. + // operations.addDynamicAttribute(element, 'class', ref as any as Reference, false); } }, }; @@ -200,7 +212,7 @@ class SimpleClassNameBindingReference extends CachedReference> { } } -class ColonClassNameBindingReference extends CachedReference> { +export class ColonClassNameBindingReference extends CachedReference> { public tag: Tag; constructor(private inner: Reference, diff --git a/packages/ember-glimmer/lib/utils/curly-component-state-bucket.ts b/packages/ember-glimmer/lib/utils/curly-component-state-bucket.ts index d0681ce61b0..56ecb9949e6 100644 --- a/packages/ember-glimmer/lib/utils/curly-component-state-bucket.ts +++ b/packages/ember-glimmer/lib/utils/curly-component-state-bucket.ts @@ -1,17 +1,19 @@ import { Revision, VersionedReference } from '@glimmer/reference'; import { CapturedNamedArguments } from '@glimmer/runtime'; -import { Opaque } from '@glimmer/util/dist/types'; +import { Opaque } from '@glimmer/util'; import Environment from '../environment'; export interface Component { _debugContainerKey: string; _transitionTo(name: string): void; - attributeBindings: any; - classNames: any; - classNameBindings: any; + layoutName?: string; + attributeBindings: Array; + classNames: Array; + classNameBindings: Array; elementId: string; tagName: string; isDestroying: boolean; + appendChild(view: Component): void; trigger(event: string): void; destroy(): void; setProperties(props: { @@ -37,9 +39,9 @@ export default class ComponentStateBucket { public classRef: VersionedReference | null = null; public argsRevision: Revision; - constructor(public environment: Environment, public component: Component, public args: CapturedNamedArguments, public finalizer: Finalizer) { + constructor(public environment: Environment, public component: Component, public args: CapturedNamedArguments | null, public finalizer: Finalizer) { this.classRef = null; - this.argsRevision = args.tag.value(); + this.argsRevision = args === null ? 0 : args.tag.value(); } destroy() { diff --git a/packages/ember-glimmer/lib/utils/iterable.ts b/packages/ember-glimmer/lib/utils/iterable.ts index 090113c8ca3..a0a60928179 100644 --- a/packages/ember-glimmer/lib/utils/iterable.ts +++ b/packages/ember-glimmer/lib/utils/iterable.ts @@ -8,6 +8,7 @@ import { import { Opaque } from '@glimmer/util'; import { get, isProxy, tagFor, tagForProperty } from 'ember-metal'; import { + _contentFor, isEmberArray, objectAt, } from 'ember-runtime'; @@ -204,13 +205,16 @@ class EachInIterable { let { ref, keyFor, valueTag } = this; let iterable = ref.value(); - - valueTag.inner.update(tagFor(iterable)); + let tag = tagFor(iterable); if (isProxy(iterable)) { - iterable = get(iterable, 'content'); + // this is because the each-in doesn't actually get(proxy, 'key') but bypasses it + // and the proxy's tag is lazy updated on access + iterable = _contentFor(iterable); } + valueTag.inner.update(tag); + let typeofIterable = typeof iterable; if (iterable !== null && (typeofIterable === 'object' || typeofIterable === 'function')) { diff --git a/packages/ember-glimmer/lib/utils/outlet.ts b/packages/ember-glimmer/lib/utils/outlet.ts new file mode 100644 index 00000000000..c859c55e336 --- /dev/null +++ b/packages/ember-glimmer/lib/utils/outlet.ts @@ -0,0 +1,170 @@ +import { Opaque } from '@glimmer/interfaces'; +import { combine, DirtyableTag, Reference, Tag, VersionedPathReference } from '@glimmer/reference'; +import { Owner } from 'ember-utils'; +import { OwnedTemplate } from '../template'; + +export interface RenderState { + /** + * Not sure why this is here, we use the owner of the template for lookups. + * + * Maybe this is for the render helper? + */ + owner: Owner; + + /** + * The name of the parent outlet state. + */ + into: string | undefined; + + /* + * The outlet name in the parent outlet state's outlets. + */ + outlet: string; + + /** + * The name of the route/template + */ + name: string; + + /** + * The controller (the self of the outlet component) + */ + controller: any | undefined; + + /** + * template (the layout of the outlet component) + */ + template: OwnedTemplate | undefined; +} + +export interface Outlets { + [name: string]: OutletState | undefined; +} + +export interface OutletState { + /** + * Nested outlet connections. + */ + outlets: Outlets; + + /** + * Represents what was rendered into this outlet. + */ + render: RenderState | undefined; + + /** + * Has to do with render helper and orphan outlets. + * Whether outlet state was rendered. + */ + wasUsed?: boolean; +} + +/** + * Represents the root outlet. + */ +export class RootOutletReference implements VersionedPathReference { + tag = DirtyableTag.create(); + + constructor(public outletState: OutletState) { + } + + get(key: string): VersionedPathReference { + return new PathReference(this, key); + } + + value(): OutletState { + return this.outletState; + } + + update(state: OutletState) { + this.outletState.outlets.main = state; + this.tag.inner.dirty(); + } +} + +/** + * Represents the connected outlet. + */ +export class OutletReference implements VersionedPathReference { + tag: Tag; + + constructor(public parentStateRef: VersionedPathReference, + public outletNameRef: Reference) { + this.tag = combine([parentStateRef.tag, outletNameRef.tag]); + } + + value(): OutletState | undefined { + let outletState = this.parentStateRef.value(); + let outlets = outletState === undefined ? undefined : outletState.outlets; + return outlets === undefined ? undefined : outlets[this.outletNameRef.value()]; + } + + get(key: string): VersionedPathReference { + return new PathReference(this, key); + } +} + +/** + * Outlet state is dirtied from root. + * This just using the parent tag for dirtiness. + */ +class PathReference implements VersionedPathReference { + public parent: VersionedPathReference; + public key: string; + public tag: Tag; + + constructor(parent: VersionedPathReference, key: string) { + this.parent = parent; + this.key = key; + this.tag = parent.tag; + } + + get(key: string): VersionedPathReference { + return new PathReference(this, key); + } + + value(): Opaque { + let parent = this.parent.value(); + return parent && parent[this.key]; + } +} + +/** + * So this is a relic of the past that SHOULD go away + * in 3.0. Preferably it is deprecated in the release that + * follows the Glimmer release. + */ +export class OrphanedOutletReference implements VersionedPathReference { + public tag: Tag; + + constructor(public root: VersionedPathReference, public name: string) { + this.tag = root.tag; + } + + value(): OutletState | undefined { + let rootState = this.root.value(); + let outletState = rootState && rootState.outlets.main; + let outlets = outletState && outletState.outlets; + outletState = outlets && outlets.__ember_orphans__; + outlets = outletState && outletState.outlets; + + if (outlets === undefined) { + return; + } + + let matched = outlets[this.name]; + + if (matched === undefined || matched.render === undefined) { + return; + } + + let state = Object.create(null); + state[matched.render.outlet] = matched; + matched.wasUsed = true; + return { outlets: state, render: undefined }; + } + + get(key: string): VersionedPathReference { + return new PathReference(this, key); + } +} diff --git a/packages/ember-glimmer/lib/utils/process-args.ts b/packages/ember-glimmer/lib/utils/process-args.ts index 7a875b86249..65fbd455622 100644 --- a/packages/ember-glimmer/lib/utils/process-args.ts +++ b/packages/ember-glimmer/lib/utils/process-args.ts @@ -1,6 +1,6 @@ +import { CapturedNamedArguments } from '@glimmer/runtime'; import { symbol } from 'ember-utils'; import { MUTABLE_CELL } from 'ember-views'; -import { CapturedNamedArguments } from '@glimmer/runtime'; import { ARGS } from '../component'; import { ACTION } from '../helpers/action'; import { UPDATE } from './references'; diff --git a/packages/ember-glimmer/lib/utils/references.ts b/packages/ember-glimmer/lib/utils/references.ts index fa696bd99f1..fec0bab3f32 100644 --- a/packages/ember-glimmer/lib/utils/references.ts +++ b/packages/ember-glimmer/lib/utils/references.ts @@ -1,17 +1,20 @@ +import { Opaque } from '@glimmer/interfaces'; import { combine, CONSTANT_TAG, ConstReference, DirtyableTag, isConst, + RevisionTag, + Tag, TagWrapper, UpdatableTag, + VersionedPathReference, } from '@glimmer/reference'; import { CapturedArguments, ConditionalReference as GlimmerConditionalReference, PrimitiveReference, - VM, } from '@glimmer/runtime'; import { DEBUG } from 'ember-env-flags'; import { @@ -31,8 +34,9 @@ import { MANDATORY_SETTER, } from 'ember/features'; import { + HelperFunction, + HelperInstance, RECOMPUTE_TAG, - SimpleHelper, } from '../helper'; import emberToBool from './to-bool'; @@ -56,20 +60,23 @@ if (DEBUG) { // @abstract // @implements PathReference -class EmberPathReference { +abstract class EmberPathReference implements VersionedPathReference { // @abstract get tag() // @abstract value() + public tag: Tag; get(key: string): any { return PropertyReference.create(this, key); } + + abstract value(): Opaque; } // @abstract export class CachedReference extends EmberPathReference { private _lastRevision: any; private _lastValue: any; - public tag: any; + public tag: Tag; constructor() { super(); @@ -113,16 +120,27 @@ export class RootReference extends ConstReference { } } -let TwoWayFlushDetectionTag: any; +interface TwoWayFlushDetectionTag extends RevisionTag { + didCompute(parent: Opaque): void; +} + +let TwoWayFlushDetectionTag: { + new (tag: Tag, key: string, ref: VersionedPathReference): TwoWayFlushDetectionTag; + create(tag: Tag, key: string, ref: VersionedPathReference): TagWrapper; +}; if (EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER) { TwoWayFlushDetectionTag = class { - public tag: any; - public parent: any; + public tag: Tag; + public parent: Opaque; public key: string; public ref: any; - constructor(tag: TagWrapper, key: string, ref: any) { + static create(tag: Tag, key: string, ref: VersionedPathReference): TagWrapper { + return new TagWrapper((tag as any).type, new TwoWayFlushDetectionTag(tag, key, ref)); + } + + constructor(tag: Tag, key: string, ref: any) { this.tag = tag; this.parent = null; this.key = key; @@ -153,7 +171,7 @@ if (EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER) { } export class PropertyReference extends CachedReference { - static create(parentReference: any, propertyKey: string) { + static create(parentReference: VersionedPathReference, propertyKey: string) { if (isConst(parentReference)) { return new RootPropertyReference(parentReference.value(), propertyKey); } else { @@ -161,12 +179,12 @@ export class PropertyReference extends CachedReference { } } - get(key: string) { + get(key: string): VersionedPathReference { return new NestedPropertyReference(this, key); } } -export class RootPropertyReference extends PropertyReference { +export class RootPropertyReference extends PropertyReference implements VersionedPathReference { private _parentValue: any; private _propertyKey: string; @@ -177,7 +195,7 @@ export class RootPropertyReference extends PropertyReference { this._propertyKey = propertyKey; if (EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER) { - this.tag = new TwoWayFlushDetectionTag(tagForProperty(parentValue, propertyKey), propertyKey, this); + this.tag = TwoWayFlushDetectionTag.create(tagForProperty(parentValue, propertyKey), propertyKey, this); } else { this.tag = tagForProperty(parentValue, propertyKey); } @@ -191,7 +209,7 @@ export class RootPropertyReference extends PropertyReference { let { _parentValue, _propertyKey } = this; if (EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER) { - this.tag.didCompute(_parentValue); + (this.tag.inner as TwoWayFlushDetectionTag).didCompute(_parentValue); } return get(_parentValue, _propertyKey); @@ -207,7 +225,7 @@ export class NestedPropertyReference extends PropertyReference { private _parentObjectTag: TagWrapper; private _propertyKey: string; - constructor(parentReference: any, propertyKey: string) { + constructor(parentReference: VersionedPathReference, propertyKey: string) { super(); let parentReferenceTag = parentReference.tag; @@ -219,7 +237,7 @@ export class NestedPropertyReference extends PropertyReference { if (EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER) { let tag = combine([parentReferenceTag, parentObjectTag]); - this.tag = new TwoWayFlushDetectionTag(tag, propertyKey, this); + this.tag = TwoWayFlushDetectionTag.create(tag, propertyKey, this); } else { this.tag = combine([parentReferenceTag, parentObjectTag]); } @@ -244,7 +262,7 @@ export class NestedPropertyReference extends PropertyReference { } if (EMBER_GLIMMER_DETECT_BACKTRACKING_RERENDER) { - this.tag.didCompute(parentValue); + (this.tag.inner as TwoWayFlushDetectionTag).didCompute(parentValue); } return get(parentValue, _propertyKey); @@ -322,12 +340,10 @@ export class ConditionalReference extends GlimmerConditionalReference { } export class SimpleHelperReference extends CachedReference { - public helper: (positionalValue: any, namedValue: any) => any; - public args: any; - - static create(Helper: SimpleHelper, _vm: VM, args: CapturedArguments) { - let helper = Helper.create(); + public helper: HelperFunction; + public args: CapturedArguments; + static create(helper: HelperFunction, args: CapturedArguments) { if (isConst(args)) { let { positional, named } = args; @@ -339,19 +355,14 @@ export class SimpleHelperReference extends CachedReference { maybeFreeze(namedValue); } - let result = helper.compute(positionalValue, namedValue); - - if (typeof result === 'object' && result !== null || typeof result === 'function') { - return new RootReference(result); - } else { - return PrimitiveReference.create(result); - } + let result = helper(positionalValue, namedValue); + return valueToRef(result); } else { - return new SimpleHelperReference(helper.compute, args); + return new SimpleHelperReference(helper, args); } } - constructor(helper: (positionalValue: any, namedValue: any) => any, args: CapturedArguments) { + constructor(helper: HelperFunction, args: CapturedArguments) { super(); this.tag = args.tag; @@ -375,16 +386,14 @@ export class SimpleHelperReference extends CachedReference { } export class ClassBasedHelperReference extends CachedReference { - public instance: any; - public args: any; + public instance: HelperInstance; + public args: CapturedArguments; - static create(helperClass: any, vm: VM, args: CapturedArguments) { - let instance = helperClass.create(); - vm.newDestroyable(instance); + static create(instance: HelperInstance, args: CapturedArguments) { return new ClassBasedHelperReference(instance, args); } - constructor(instance: any, args: CapturedArguments) { + constructor(instance: HelperInstance, args: CapturedArguments) { super(); this.tag = combine([instance[RECOMPUTE_TAG], args.tag]); @@ -426,16 +435,34 @@ export class InternalHelperReference extends CachedReference { } // @implements PathReference -export class UnboundReference extends ConstReference { - static create(value: any) { - if (typeof value === 'object' && value !== null) { - return new UnboundReference(value); - } else { - return PrimitiveReference.create(value); - } +export class UnboundReference extends ConstReference { + static create(value: T): VersionedPathReference { + return valueToRef(value, false); } get(key: string) { - return new UnboundReference(get(this.inner, key)); + return valueToRef(get(this.inner, key), false); + } +} + +export function referenceFromParts(root: VersionedPathReference, parts: string[]): VersionedPathReference { + let reference = root; + + for (let i=0; i { + if (value !== null && typeof value === 'object') { + // root of interop with ember objects + return bound ? new RootReference(value) : new UnboundReference(value); + } + // ember doesn't do observing with functions + if (typeof value === 'function') { + return new UnboundReference(value); } + return PrimitiveReference.create(value); } diff --git a/packages/ember-glimmer/lib/utils/string.ts b/packages/ember-glimmer/lib/utils/string.ts index 0f020ed778a..31e28cc0b4d 100644 --- a/packages/ember-glimmer/lib/utils/string.ts +++ b/packages/ember-glimmer/lib/utils/string.ts @@ -101,6 +101,6 @@ export function htmlSafe(str: string) { @return {Boolean} `true` if the string was decorated with `htmlSafe`, `false` otherwise. @public */ -export function isHTMLSafe(str: string | SafeString): str is SafeString { +export function isHTMLSafe(str: any | null | undefined): str is SafeString { return str !== null && typeof str === 'object' && typeof str.toHTML === 'function'; } diff --git a/packages/ember-glimmer/lib/views/outlet.ts b/packages/ember-glimmer/lib/views/outlet.ts index 238579daaa5..5e7d388435a 100644 --- a/packages/ember-glimmer/lib/views/outlet.ts +++ b/packages/ember-glimmer/lib/views/outlet.ts @@ -1,107 +1,11 @@ import { Simple } from '@glimmer/interfaces'; -import { DirtyableTag, Tag, TagWrapper, VersionedPathReference } from '@glimmer/reference'; -import { Opaque, Option } from '@glimmer/util'; import { environment } from 'ember-environment'; import { run } from 'ember-metal'; -import { assign, OWNER } from 'ember-utils'; +import { assign, OWNER, Owner } from 'ember-utils'; +import { OutletDefinitionState } from '../component-managers/outlet'; import { Renderer } from '../renderer'; -import { Container, OwnedTemplate } from '../template'; - -export class RootOutletStateReference implements VersionedPathReference> { - tag: Tag; - - constructor(public outletView: OutletView) { - this.tag = outletView._tag; - } - - get(key: string): VersionedPathReference { - return new ChildOutletStateReference(this, key); - } - - value(): Option { - return this.outletView.outletState; - } - - getOrphan(name: string): VersionedPathReference> { - return new OrphanedOutletStateReference(this, name); - } - - update(state: OutletState) { - this.outletView.setOutletState(state); - } -} - -// So this is a relic of the past that SHOULD go away -// in 3.0. Preferably it is deprecated in the release that -// follows the Glimmer release. -class OrphanedOutletStateReference extends RootOutletStateReference { - public root: any; - public name: string; - - constructor(root: RootOutletStateReference, name: string) { - super(root.outletView); - this.root = root; - this.name = name; - } - - value(): Option { - let rootState = this.root.value(); - - let orphans = rootState.outlets.main.outlets.__ember_orphans__; - - if (!orphans) { - return null; - } - - let matched = orphans.outlets[this.name]; - - if (!matched) { - return null; - } - - let state = Object.create(null); - state[matched.render.outlet] = matched; - matched.wasUsed = true; - return { outlets: state, render: undefined }; - } -} - -class ChildOutletStateReference implements VersionedPathReference { - public parent: VersionedPathReference; - public key: string; - public tag: Tag; - - constructor(parent: VersionedPathReference, key: string) { - this.parent = parent; - this.key = key; - this.tag = parent.tag; - } - - get(key: string): VersionedPathReference { - return new ChildOutletStateReference(this, key); - } - - value(): any { - let parent = this.parent.value(); - return parent && parent[this.key]; - } -} - -export interface RenderState { - owner: Container | undefined; - into: string | undefined; - outlet: string; - name: string; - controller: Opaque; - template: OwnedTemplate | undefined; -} - -export interface OutletState { - outlets: { - [name: string]: OutletState | undefined; - }; - render: RenderState | undefined; -} +import { OwnedTemplate } from '../template'; +import { OutletState, RootOutletReference } from '../utils/outlet'; export interface BootEnvironment { hasDOM: boolean; @@ -109,14 +13,10 @@ export interface BootEnvironment { options: any; } -export default class OutletView { - private _environment: BootEnvironment; - public renderer: Renderer; - public owner: Container; - public template: OwnedTemplate; - public outletState: Option; - public _tag: TagWrapper; +const TOP_LEVEL_NAME = '-top-level'; +const TOP_LEVEL_OUTLET = 'main'; +export default class OutletView { static extend(injections: any) { return class extends OutletView { static create(options: any) { @@ -139,13 +39,28 @@ export default class OutletView { return new OutletView(_environment, renderer, owner, template); } - constructor(_environment: BootEnvironment, renderer: Renderer, owner: Container, template: OwnedTemplate) { - this._environment = _environment; - this.renderer = renderer; - this.owner = owner; - this.template = template; - this.outletState = null; - this._tag = DirtyableTag.create(); + public ref: RootOutletReference; + public state: OutletDefinitionState; + + constructor(private _environment: BootEnvironment, public renderer: Renderer, public owner: Owner, public template: OwnedTemplate) { + let ref = this.ref = new RootOutletReference({ + outlets: { main: undefined }, + render: { + owner: owner, + into: undefined, + outlet: TOP_LEVEL_OUTLET, + name: TOP_LEVEL_NAME, + controller: undefined, + template, + }, + }); + this.state = { + ref, + name: TOP_LEVEL_NAME, + outlet: TOP_LEVEL_OUTLET, + template, + controller: undefined + }; } appendTo(selector: string | Simple.Element) { @@ -164,24 +79,7 @@ export default class OutletView { rerender() { /**/ } setOutletState(state: OutletState) { - this.outletState = { - outlets: { - main: state, - }, - render: { - owner: undefined, - into: undefined, - outlet: 'main', - name: '-top-level', - controller: undefined, - template: undefined, - }, - }; - this._tag.inner.dirty(); - } - - toReference() { - return new RootOutletStateReference(this); + this.ref.update(state); } destroy() { /**/ } diff --git a/packages/ember-glimmer/tests/integration/components/contextual-components-test.js b/packages/ember-glimmer/tests/integration/components/contextual-components-test.js index c32d45460a9..4fb35490a79 100644 --- a/packages/ember-glimmer/tests/integration/components/contextual-components-test.js +++ b/packages/ember-glimmer/tests/integration/components/contextual-components-test.js @@ -87,27 +87,27 @@ moduleFor('Components test: contextual components', class extends RenderingTest this.render('{{component (component "-looked-up" model.greeting model.name) model.name model.greeting}}', { model: { greeting: 'Gabon ', - name: 'Zack' + name: 'Zack ' } }); - this.assertText('ZackGabon '); + this.assertText('Gabon Zack Zack Gabon '); this.runTask(() => this.rerender()); - this.assertText('ZackGabon '); + this.assertText('Gabon Zack Zack Gabon '); this.runTask(() => this.context.set('model.greeting', 'Good morning ')); - this.assertText('ZackGood morning '); + this.assertText('Good morning Zack Zack Good morning '); - this.runTask(() => this.context.set('model.name', 'Matthew')); + this.runTask(() => this.context.set('model.name', 'Matthew ')); - this.assertText('MatthewGood morning '); + this.assertText('Good morning Matthew Matthew Good morning '); - this.runTask(() => this.context.set('model', { greeting: 'Gabon ', name: 'Zack' })); + this.runTask(() => this.context.set('model', { greeting: 'Gabon ', name: 'Zack ' })); - this.assertText('ZackGabon '); + this.assertText('Gabon Zack Zack Gabon '); } ['@test GH#13742 keeps nested rest positional parameters if nested and rendered with no positional parameters']() { @@ -155,27 +155,27 @@ moduleFor('Components test: contextual components', class extends RenderingTest this.render('{{component (component (component "-looked-up" model.greeting model.name) model.name model.greeting)}}', { model: { greeting: 'Gabon ', - name: 'Zack' + name: 'Zack ' } }); - this.assertText('ZackGabon '); + this.assertText('Gabon Zack Zack Gabon '); this.runTask(() => this.rerender()); - this.assertText('ZackGabon '); + this.assertText('Gabon Zack Zack Gabon '); this.runTask(() => this.context.set('model.greeting', 'Good morning ')); - this.assertText('ZackGood morning '); + this.assertText('Good morning Zack Zack Good morning '); - this.runTask(() => this.context.set('model.name', 'Matthew')); + this.runTask(() => this.context.set('model.name', 'Matthew ')); - this.assertText('MatthewGood morning '); + this.assertText('Good morning Matthew Matthew Good morning '); - this.runTask(() => this.context.set('model', { greeting: 'Gabon ', name: 'Zack' })); + this.runTask(() => this.context.set('model', { greeting: 'Gabon ', name: 'Zack ' })); - this.assertText('ZackGabon '); + this.assertText('Gabon Zack Zack Gabon '); } ['@test renders with component helper with curried params, hash']() { @@ -284,7 +284,7 @@ moduleFor('Components test: contextual components', class extends RenderingTest this.assertText('Hodi'); } - ['@test nested components overwrite named positional parameters']() { + ['@test nested components do not overwrite positional parameters']() { this.registerComponent('-looked-up', { ComponentClass: Component.extend().reopenClass({ positionalParams: ['name', 'age'] @@ -294,11 +294,150 @@ moduleFor('Components test: contextual components', class extends RenderingTest this.render('{{component (component (component "-looked-up" "Sergio" 29) "Marvin" 21) "Hodari"}}'); - this.assertText('Hodari 21'); + this.assertText('Sergio 29'); + + this.runTask(() => this.rerender()); + + this.assertText('Sergio 29'); + } + + ['@test positional parameters are combined not clobbered']() { + this.registerComponent('-looked-up', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['greeting', 'name', 'age'] + }), + template: '{{greeting}} {{name}} {{age}}' + }); + + this.render('{{component (component (component "-looked-up" "Hi") "Max") 9}}'); + + this.assertText('Hi Max 9'); + + this.runTask(() => this.rerender()); + + this.assertText('Hi Max 9'); + } + + ['@test nested components positional parameters override named parameters [DEPRECATED]']() { + this.registerComponent('-looked-up', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['name', 'age'] + }), + template: '{{name}} {{age}}' + }); + + expectDeprecation(() => { + this.render('{{component (component (component "-looked-up" "Sergio" 29) name="Marvin" age=21)}}'); + }, 'You cannot specify both a positional param (at position 1) and the hash argument `age`.'); + + this.assertText('Sergio 29'); this.runTask(() => this.rerender()); - this.assertText('Hodari 21'); + this.assertText('Sergio 29'); + } + + ['@test nested components with positional params at outer layer are override hash parameters [DEPRECATED]']() { + this.registerComponent('-looked-up', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['greeting', 'name', 'age'] + }), + template: '{{greeting}} {{name}} {{age}}' + }); + + expectDeprecation(() => { + this.render( + strip` + {{#with (component "-looked-up" "Hola" "Dolores" 33) as |first|}} + {{#with (component first greeting="Hej" name="Sigmundur") as |second|}} + {{component second greeting=model.greeting}} + {{/with}} + {{/with}}`, + { + model: { + greeting: 'Hodi' + } + } + ); + }, 'You cannot specify both a positional param (at position 1) and the hash argument `name`.'); + + this.assertText('Hola Dolores 33'); + + this.runTask(() => this.rerender()); + + this.assertText('Hola Dolores 33'); + } + + ['@test nested components with positional params at middle layer partially override hash parameters [DEPRECATED]']() { + this.registerComponent('-looked-up', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['greeting', 'name', 'age'] + }), + + template: '{{greeting}} {{name}} {{age}}' + }); + + expectDeprecation(() => { + this.render( + strip` + {{#with (component "-looked-up" greeting="Hola" name="Dolores" age=33) as |first|}} + {{#with (component first "Hej" "Sigmundur") as |second|}} + {{component second greeting=model.greeting}} + {{/with}} + {{/with}}`, + { + model: { + greeting: 'Hodi' + } + } + ); + }, 'You cannot specify both a positional param (at position 0) and the hash argument `greeting`.'); + + this.assertText('Hej Sigmundur 33'); + + this.runTask(() => this.rerender()); + + this.assertText('Hej Sigmundur 33'); + } + + ['@test nested components with positional params at invocation override earlier hash parameters [DEPRECATED]']() { + this.registerComponent('-looked-up', { + ComponentClass: Component.extend().reopenClass({ + positionalParams: ['greeting', 'name', 'age'] + }), + + template: '{{greeting}} {{name}} {{age}}' + }); + + expectDeprecation(() => { + this.render( + strip` + {{#with (component "-looked-up" greeting="Hola" name="Dolores" age=33) as |first|}} + {{#with (component first greeting="Hej" name="Sigmundur") as |second|}} + {{component second model.greeting}} + {{/with}} + {{/with}}`, + { + model: { + greeting: 'Hodi' + } + } + ); + }, 'You cannot specify both a positional param (at position 0) and the hash argument `greeting`.'); + + this.assertText('Hodi Sigmundur 33'); + + this.runTask(() => this.rerender()); + + this.assertText('Hodi Sigmundur 33'); + + this.runTask(() => this.context.set('model.greeting', 'Kaixo')); + + this.assertText('Kaixo Sigmundur 33'); + + this.runTask(() => this.context.set('model', { greeting: 'Hodi' })); + + this.assertText('Hodi Sigmundur 33'); } ['@test nested components overwrite hash parameters']() { @@ -354,19 +493,19 @@ moduleFor('Components test: contextual components', class extends RenderingTest } }); - this.assertText('Inner 28'); + this.assertText('Outer 28'); this.runTask(() => this.rerender()); - this.assertText('Inner 28'); + this.assertText('Outer 28'); this.runTask(() => this.context.set('model.outerAge', 29)); - this.assertText('Inner 29'); + this.assertText('Outer 29'); this.runTask(() => this.context.set('model.outerName', 'Not outer')); - this.assertText('Inner 29'); + this.assertText('Not outer 29'); this.runTask(() => { this.context.set('model', { @@ -375,7 +514,7 @@ moduleFor('Components test: contextual components', class extends RenderingTest }); }); - this.assertText('Inner 28'); + this.assertText('Outer 28'); } ['@test bound outer hash parameters get updated in the right scope']() { @@ -421,7 +560,7 @@ moduleFor('Components test: contextual components', class extends RenderingTest this.assertText('Inner 28'); } - ['@test conflicting positional and hash parameters raise and assertion if in the same component context']() { + ['@test conflicting positional and hash parameters trigger a deprecation if in the same component context [DEPRECATED]']() { this.registerComponent('-looked-up', { ComponentClass: Component.extend().reopenClass({ positionalParams: ['name'] @@ -429,7 +568,7 @@ moduleFor('Components test: contextual components', class extends RenderingTest template: '{{greeting}} {{name}}' }); - expectAssertion(() => { + expectDeprecation(() => { this.render('{{component (component "-looked-up" "Hodari" name="Sergio") "Hodari" greeting="Hodi"}}'); }, 'You cannot specify both a positional param (at position 0) and the hash argument `name`.'); } @@ -465,7 +604,7 @@ moduleFor('Components test: contextual components', class extends RenderingTest this.assertText('Hodi Hodari'); } - ['@test conflicting positional and hash parameters does not raise an assertion if in different component context']() { + ['@test conflicting positional and hash parameters trigger a deprecation [DEPRECATED]']() { this.registerComponent('-looked-up', { ComponentClass: Component.extend().reopenClass({ positionalParams: ['name'] @@ -473,33 +612,107 @@ moduleFor('Components test: contextual components', class extends RenderingTest template: '{{greeting}} {{name}}' }); - this.render('{{component (component "-looked-up" "Hodari") name="Sergio" greeting="Hodi"}}'); + expectDeprecation(() => { + this.render('{{component (component "-looked-up" "Hodari") name="Sergio" greeting="Hodi"}}'); + }, 'You cannot specify both a positional param (at position 0) and the hash argument `name`.'); - this.assertText('Hodi Sergio'); + this.assertText('Hodi Hodari'); this.runTask(() => this.rerender()); - this.assertText('Hodi Sergio'); + this.assertText('Hodi Hodari'); } - ['@test raises an asserton when component path is null']() { - expectAssertion(() => { - this.render('{{component (component lookupComponent)}}'); - }); + ['@test component with dynamic component name resolving to undefined, then an existing component']() { + this.registerComponent('foo-bar', { template: 'hello {{name}}' }); + + this.render('{{component (component componentName name=name)}}', { componentName: undefined, name: 'Alex' }); + + this.assertText(''); + + this.runTask(() => this.rerender()); + + this.assertText(''); + + this.runTask(() => this.context.set('componentName', 'foo-bar')); + + this.assertText('hello Alex'); + + this.runTask(() => this.context.set('componentName', undefined)); + + this.assertText(''); + } + + ['@test component with dynamic component name resolving to a component, then undefined']() { + this.registerComponent('foo-bar', { template: 'hello {{name}}' }); + + this.render('{{component (component componentName name=name)}}', { componentName: 'foo-bar', name: 'Alex' }); + + this.assertText('hello Alex'); + + this.runTask(() => this.rerender()); + + this.assertText('hello Alex'); + + this.runTask(() => this.context.set('componentName', undefined)); + + this.assertText(''); + + this.runTask(() => this.context.set('componentName', 'foo-bar')); + + this.assertText('hello Alex'); + } + + ['@test component with dynamic component name resolving to null, then an existing component']() { + this.registerComponent('foo-bar', { template: 'hello {{name}}' }); + + this.render('{{component (component componentName name=name)}}', { componentName: null, name: 'Alex' }); + + this.assertText(''); + + this.runTask(() => this.rerender()); + + this.assertText(''); + + this.runTask(() => this.context.set('componentName', 'foo-bar')); + + this.assertText('hello Alex'); + + this.runTask(() => this.context.set('componentName', null)); + + this.assertText(''); + } + + ['@test component with dynamic component name resolving to a component, then null']() { + this.registerComponent('foo-bar', { template: 'hello {{name}}' }); + + this.render('{{component (component componentName name=name)}}', { componentName: 'foo-bar', name: 'Alex' }); + + this.assertText('hello Alex'); + + this.runTask(() => this.rerender()); + + this.assertText('hello Alex'); + + this.runTask(() => this.context.set('componentName', null)); + + this.assertText(''); + + this.runTask(() => this.context.set('componentName', 'foo-bar')); + + this.assertText('hello Alex'); } ['@test raises an assertion when component path is not a component name (static)']() { expectAssertion(() => { this.render('{{component (component "not-a-component")}}'); - }, 'The component helper cannot be used without a valid component name. You used "not-a-component" via (component "not-a-component")'); + }, 'Could not find component named \"not-a-component\" (no component or template with that name was found)'); } ['@test raises an assertion when component path is not a component name (dynamic)']() { expectAssertion(() => { - this.render('{{component (component compName)}}', { - compName: 'not-a-component' - }); - }, /The component helper cannot be used without a valid component name. You used "not-a-component" via \(component .*\)/); + this.render('{{component (component compName)}}', { compName: "not-a-component" }); + }, 'Could not find component named \"not-a-component\" (no component or template with that name was found)'); } ['@test renders with dot path']() { @@ -1083,13 +1296,13 @@ moduleFor('Components test: contextual components', class extends RenderingTest ['@test GH#14632 give useful warning when calling contextual components with input as a name']() { expectAssertion(() => { this.render('{{component (component "input" type="text")}}'); - }, 'You cannot use the input helper as a contextual helper. Please extend TextField or Checkbox to use it as a contextual component.'); + }, 'You cannot use \'input\' as a component name. Component names must contain a hyphen.'); } ['@test GH#14632 give useful warning when calling contextual components with textarea as a name']() { expectAssertion(() => { this.render('{{component (component "textarea" type="text")}}'); - }, 'You cannot use the textarea helper as a contextual helper. Please extend TextArea to use it as a contextual component.'); + }, 'You cannot use \'textarea\' as a component name. Component names must contain a hyphen.'); } }); diff --git a/packages/ember-glimmer/tests/integration/components/curly-components-test.js b/packages/ember-glimmer/tests/integration/components/curly-components-test.js index 999d21e6d0e..5544fb17278 100644 --- a/packages/ember-glimmer/tests/integration/components/curly-components-test.js +++ b/packages/ember-glimmer/tests/integration/components/curly-components-test.js @@ -1691,7 +1691,7 @@ moduleFor('Components test: curly components', class extends RenderingTest { template: '{{name}}' }); - expectAssertion(() => { + expectDeprecation(() => { this.render('{{sample-component notMyName name=myName}}', { myName: 'Quint', notMyName: 'Sergio' diff --git a/packages/ember-glimmer/tests/integration/custom-component-manager-test.js b/packages/ember-glimmer/tests/integration/custom-component-manager-test.js index 9bdf23cbbde..e89506ab63d 100644 --- a/packages/ember-glimmer/tests/integration/custom-component-manager-test.js +++ b/packages/ember-glimmer/tests/integration/custom-component-manager-test.js @@ -1,6 +1,3 @@ -import { - PrimitiveReference -} from '@glimmer/runtime'; import { moduleFor, RenderingTest } from '../utils/test-case'; import { GLIMMER_CUSTOM_COMPONENT_MANAGER @@ -8,25 +5,6 @@ import { import { AbstractComponentManager } from 'ember-glimmer'; if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { - /* - Custom layout compiler. Exists mostly to inject - class and attributes for testing purposes. - */ - class TestLayoutCompiler { - constructor(template) { - this.template = template; - } - - compile(builder) { - builder.wrapLayout(this.template); - builder.tag.dynamic(() => { - return PrimitiveReference.create('p'); - }); - builder.attrs.static('class', 'hey-oh-lets-go'); - builder.attrs.static('manager-id', 'test'); - } - } - /* Implementation of custom component manager, `ComponentManager` interface */ @@ -35,10 +13,6 @@ if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { return definition.ComponentClass.create(); } - layoutFor(definition, bucket, env) { - return env.getCompiledBlock(TestLayoutCompiler, definition.template); - } - getDestructor(component) { return component; } @@ -49,7 +23,7 @@ if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { } moduleFor('Components test: curly components with custom manager', class extends RenderingTest { - ['@test it can render a basic component with custom component manager'](assert) { + ['@skip it can render a basic component with custom component manager'](assert) { let managerId = 'test'; this.owner.register(`component-manager:${managerId}`, new TestComponentManager()); this.registerComponent('foo-bar', { diff --git a/packages/ember-glimmer/tests/integration/syntax/experimental-syntax-test.js b/packages/ember-glimmer/tests/integration/syntax/experimental-syntax-test.js index 42c46e43c07..1cf132eb61c 100644 --- a/packages/ember-glimmer/tests/integration/syntax/experimental-syntax-test.js +++ b/packages/ember-glimmer/tests/integration/syntax/experimental-syntax-test.js @@ -1,7 +1,6 @@ import { moduleFor, RenderingTest } from '../../utils/test-case'; import { strip } from '../../utils/abstract-test-case'; import { _registerMacros, _experimentalMacros } from 'ember-glimmer'; -import { compileExpression } from '@glimmer/runtime'; moduleFor('registerMacros', class extends RenderingTest { constructor() { @@ -9,8 +8,8 @@ moduleFor('registerMacros', class extends RenderingTest { _registerMacros(blocks => { blocks.add('-let', (params, hash, _default, inverse, builder) => { - compileExpression(params[0], builder); - builder.invokeStatic(_default, 1); + builder.compileParams(params); + builder.invokeStaticBlock(_default, params.length); }); }); diff --git a/packages/ember-glimmer/tests/integration/syntax/in-element-test.js b/packages/ember-glimmer/tests/integration/syntax/in-element-test.js index 31e6235ac03..ffd0a6de077 100644 --- a/packages/ember-glimmer/tests/integration/syntax/in-element-test.js +++ b/packages/ember-glimmer/tests/integration/syntax/in-element-test.js @@ -1,17 +1,17 @@ import { moduleFor, RenderingTest } from '../../utils/test-case'; import { equalTokens } from '../../utils/test-helpers'; import { strip } from '../../utils/abstract-test-case'; -import Component from '../../../component'; +import { Component } from 'ember-glimmer'; import { set } from 'ember-metal'; -moduleFor('{{-in-element}}', class extends RenderingTest { +moduleFor('{{in-element}}', class extends RenderingTest { ['@test allows rendering into an external element']() { let someElement = document.createElement('div'); this.render(strip` - {{#-in-element someElement}} + {{#in-element someElement}} {{text}} - {{/-in-element}} + {{/in-element}} `, { someElement, text: 'Whoop!' @@ -54,9 +54,9 @@ moduleFor('{{-in-element}}', class extends RenderingTest { this.render(strip` {{#if showModal}} - {{#-in-element someElement}} + {{#in-element someElement}} {{modal-display text=text}} - {{/-in-element}} + {{/in-element}} {{/if}} `, { someElement, diff --git a/packages/ember-glimmer/tests/unit/layout-cache-test.js b/packages/ember-glimmer/tests/unit/layout-cache-test.js index 71e6bf8a5aa..7e4eecb4c28 100644 --- a/packages/ember-glimmer/tests/unit/layout-cache-test.js +++ b/packages/ember-glimmer/tests/unit/layout-cache-test.js @@ -1,140 +1,134 @@ import { RenderingTest, moduleFor } from '../utils/test-case'; -import { CompiledDynamicTemplate } from '@glimmer/runtime'; -import { OWNER } from 'ember-utils'; - -class Counter { - constructor() { - this.reset(); - } - - increment(key) { - this.total++; - return this.counts[key] = (this.counts[key] || 0) + 1; - } - - get(key) { - return this.counts[key] || 0; - } - - reset() { - this.total = 0; - this.counts = Object.create(null); - } -} - -const COUNTER = new Counter(); - -class BasicCompiler { - constructor(template) { - this.template = template; - } - - compile(builder) { - let { template } = this; - COUNTER.increment(`${this.constructor.id}+${template.id}`); - builder.wrapLayout(template); - } -} - -class TypeOneCompiler extends BasicCompiler {} -class TypeTwoCompiler extends BasicCompiler {} - -TypeOneCompiler.id = 'type-one'; -TypeTwoCompiler.id = 'type-two'; +import { Component } from '../utils/helpers'; +import { set } from 'ember-metal'; +import { runDestroy, runAppend } from 'internal-test-helpers'; moduleFor('Layout cache test', class extends RenderingTest { - constructor() { - super(); - COUNTER.reset(); - } - - templateFor(content) { - let Factory = this.compile(content); - return this.env.getTemplate(Factory, this.owner); - } - ['@test each template is only compiled once'](assert) { - let { env } = this; - - let template1 = this.templateFor('Hello world!'); - let template2 = this.templateFor('{{foo}} {{bar}}'); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.strictEqual(COUNTER.get(`type-one+${template1.id}`), 1); - assert.strictEqual(COUNTER.get(`type-one+${template2.id}`), 0); - assert.strictEqual(COUNTER.total, 1); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.strictEqual(COUNTER.get(`type-one+${template1.id}`), 1); - assert.strictEqual(COUNTER.get(`type-one+${template2.id}`), 0); - assert.strictEqual(COUNTER.total, 1); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template2) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.strictEqual(COUNTER.get(`type-one+${template1.id}`), 1); - assert.strictEqual(COUNTER.get(`type-one+${template2.id}`), 1); - assert.strictEqual(COUNTER.total, 2); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template2) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template2) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template2) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - - assert.strictEqual(COUNTER.get(`type-one+${template1.id}`), 1); - assert.strictEqual(COUNTER.get(`type-one+${template2.id}`), 1); - assert.strictEqual(COUNTER.total, 2); + // static layout + this.registerComponent('component-one', { template: 'One' }); + + // test directly import template factory onto late bound layout + let Two = Component.extend({ + layout: this.compile('Two'), + }); + this.registerComponent('component-two', { ComponentClass: Two }); + + // inject layout onto component, share layout with component-one + this.registerComponent('root-component', { ComponentClass: Component }); + this.owner.inject('component:root-component', 'layout', 'template:components/component-one'); + + // template instance shared between to template managers + // Root components doesn't have creation arguments + let rootFactory = this.owner.factoryFor('component:root-component'); + + // assert precondition + let state = this.getCacheCounters(); + assert.deepEqual(state, { + wrapperCacheHits: 0, + wrapperCacheMisses: 0, + templateCacheHits: 0, + templateCacheMisses: 0, + }, 'precondition'); + + // show component-one for the first time + this.render(` + {{~#if cond~}} + {{component-one}} + {{~else~}} + {{component-two}} + {{~/if}}`, { + cond: true, + }); + + this.assertText('One'); + state = this.expectCacheChanges({ + wrapperCacheMisses: 2, + }, state, 'test case component and component-one miss wrapper cache'); + + // show component-two for the first time + this.runTask(() => set(this.context, 'cond', false)); + + this.assertText('Two'); + state = this.expectCacheChanges({ + templateCacheMisses: 1, + wrapperCacheMisses: 1, + }, state, 'component-two first render misses both template cache and wrapper cache'); + + // show component-one again + this.runTask(() => set(this.context, 'cond', true)); + + this.assertText('One'); + state = this.expectCacheChanges({ + wrapperCacheHits: 1, + }, state, 'toggle back to component-one hits wrapper cache'); + + // show component-two again + this.runTask(() => set(this.context, 'cond', false)); + + this.assertText('Two'); + state = this.expectCacheChanges({ + wrapperCacheHits: 1, + templateCacheHits: 1, + }, state, 'toggle back to component-two hits wrapper cache and template cache'); + + // render new root append + let root = rootFactory.create(); + try { + runAppend(root); + this.assertText('TwoOne'); + // roots have different capabilities so this will hit + state = this.expectCacheChanges({ + wrapperCacheMisses: 1, + }, state, 'append root with component-one template misses because different capabilities'); + + // render new root append + let root2 = rootFactory.create(); + try { + runAppend(root2); + this.assertText('TwoOneOne'); + state = this.expectCacheChanges({ + wrapperCacheHits: 1, + }, state, 'append another root hits'); + } finally { + runDestroy(root2); + } + } finally { + runDestroy(root); + } } - ['@test each template/compiler pair is treated as unique'](assert) { - let { env } = this; - - let template = this.templateFor('Hello world!'); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.strictEqual(COUNTER.get(`type-one+${template.id}`), 1); - assert.strictEqual(COUNTER.get(`type-two+${template.id}`), 0); - assert.strictEqual(COUNTER.total, 1); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.strictEqual(COUNTER.get(`type-one+${template.id}`), 1); - assert.strictEqual(COUNTER.get(`type-two+${template.id}`), 0); - assert.strictEqual(COUNTER.total, 1); - - assert.ok(env.getCompiledBlock(TypeTwoCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.strictEqual(COUNTER.get(`type-one+${template.id}`), 1); - assert.strictEqual(COUNTER.get(`type-two+${template.id}`), 1); - assert.strictEqual(COUNTER.total, 2); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeTwoCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeTwoCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeTwoCompiler, template) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - - assert.strictEqual(COUNTER.get(`type-one+${template.id}`), 1); - assert.strictEqual(COUNTER.get(`type-two+${template.id}`), 1); - assert.strictEqual(COUNTER.total, 2); - } - - ['@test a template instance is returned (ensures templates can be injected into layout property)'](assert) { - let { owner, env } = this; - - let templateInstanceFor = (content) => { - let Factory = this.compile(content); - return Factory.create({ [OWNER]: owner, env }); + getCacheCounters() { + let { runtimeResolver: { + wrapperCacheHits, + wrapperCacheMisses, + templateCacheHits, + templateCacheMisses, + } } = this; + return { + wrapperCacheHits, + wrapperCacheMisses, + templateCacheHits, + templateCacheMisses, }; + } - let template1 = templateInstanceFor('Hello world!'); - let template2 = templateInstanceFor('{{foo}} {{bar}}'); - - assert.ok(env.getCompiledBlock(TypeOneCompiler, template1) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); - assert.ok(env.getCompiledBlock(TypeOneCompiler, template2) instanceof CompiledDynamicTemplate, 'should return a CompiledDynamicTemplate'); + expectCacheChanges(expected, lastState, message) { + let state = this.getCacheCounters(); + let actual = diff(state, lastState); + this.assert.deepEqual(actual, expected, message); + return state; } }); + +function diff(state, lastState) { + let res = {}; + Object.keys(state).forEach(key => { + let delta = state[key] - lastState[key]; + if (delta !== 0) { + res[key] = state[key] - lastState[key]; + } + }); + return res; +} diff --git a/packages/ember-glimmer/tests/unit/outlet-test.js b/packages/ember-glimmer/tests/unit/outlet-test.js index e9cc8d1a237..78f9493073f 100644 --- a/packages/ember-glimmer/tests/unit/outlet-test.js +++ b/packages/ember-glimmer/tests/unit/outlet-test.js @@ -1,4 +1,4 @@ -import OutletView from 'ember-glimmer/views/outlet'; +import { OutletView } from 'ember-glimmer'; import { run } from 'ember-metal'; QUnit.module('Glimmer OutletView'); diff --git a/packages/ember-glimmer/tests/unit/template-factory-test.js b/packages/ember-glimmer/tests/unit/template-factory-test.js index bd6775d0c0b..8320c085fa3 100644 --- a/packages/ember-glimmer/tests/unit/template-factory-test.js +++ b/packages/ember-glimmer/tests/unit/template-factory-test.js @@ -1,11 +1,12 @@ import { precompile, compile } from 'ember-template-compiler'; -import { template } from '../../index'; +import { template } from 'ember-glimmer'; import { RenderingTest, moduleFor } from '../utils/test-case'; import { Component } from '../utils/helpers'; moduleFor('Template factory test', class extends RenderingTest { ['@test the template factory returned from precompile is the same as compile'](assert) { - let { env } = this; + let { owner } = this; + let { runtimeResolver } = this; let templateStr = 'Hello {{name}}'; let options = { moduleName: 'some-module' }; @@ -25,18 +26,18 @@ moduleFor('Template factory test', class extends RenderingTest { assert.equal(typeof Compiled.create, 'function', 'compiled is a factory'); assert.ok(Compiled.id, 'compiled has id'); - assert.equal(env._templateCache.misses, 0, 'misses 0'); - assert.equal(env._templateCache.hits, 0, 'hits 0'); + assert.equal(runtimeResolver.templateCacheMisses, 0, 'misses 0'); + assert.equal(runtimeResolver.templateCacheHits, 0, 'hits 0'); - let precompiled = env.getTemplate(Precompiled, env.owner); + let precompiled = runtimeResolver.createTemplate(Precompiled, owner); - assert.equal(env._templateCache.misses, 1, 'misses 1'); - assert.equal(env._templateCache.hits, 0, 'hits 0'); + assert.equal(runtimeResolver.templateCacheMisses, 1, 'misses 1'); + assert.equal(runtimeResolver.templateCacheHits, 0, 'hits 0'); - let compiled = env.getTemplate(Compiled, env.owner); + let compiled = runtimeResolver.createTemplate(Compiled, owner); - assert.equal(env._templateCache.misses, 2, 'misses 2'); - assert.equal(env._templateCache.hits, 0, 'hits 0'); + assert.equal(runtimeResolver.templateCacheMisses, 2, 'misses 2'); + assert.equal(runtimeResolver.templateCacheHits, 0, 'hits 0'); assert.ok(typeof precompiled.spec !== 'string', 'Spec has been parsed'); assert.ok(typeof compiled.spec !== 'string', 'Spec has been parsed'); @@ -55,8 +56,8 @@ moduleFor('Template factory test', class extends RenderingTest { this.render('{{x-precompiled name="precompiled"}} {{x-compiled name="compiled"}}'); - assert.equal(env._templateCache.misses, 2, 'misses 2'); - assert.equal(env._templateCache.hits, 2, 'hits 2'); + assert.equal(runtimeResolver.templateCacheMisses, 2, 'misses 2'); + assert.equal(runtimeResolver.templateCacheHits, 2, 'hits 2'); this.assertText('Hello precompiled Hello compiled'); } diff --git a/packages/ember-glimmer/tests/unit/utils/debug-stack-test.js b/packages/ember-glimmer/tests/unit/utils/debug-stack-test.js index ce70d45d941..601555e555f 100644 --- a/packages/ember-glimmer/tests/unit/utils/debug-stack-test.js +++ b/packages/ember-glimmer/tests/unit/utils/debug-stack-test.js @@ -1,4 +1,4 @@ -import DebugStack from 'ember-glimmer/utils/debug-stack'; +import { DebugStack } from 'ember-glimmer'; import { DEBUG } from 'ember-env-flags'; if (DEBUG) { diff --git a/packages/ember-glimmer/tests/unit/utils/iterable-test.js b/packages/ember-glimmer/tests/unit/utils/iterable-test.js index 859a66c8b58..28797a895dd 100644 --- a/packages/ember-glimmer/tests/unit/utils/iterable-test.js +++ b/packages/ember-glimmer/tests/unit/utils/iterable-test.js @@ -1,7 +1,6 @@ import Ember from 'ember'; import { moduleFor, TestCase } from 'ember-glimmer/tests/utils/test-case'; -import iterableFor from 'ember-glimmer/utils/iterable'; -import { UpdatableReference } from 'ember-glimmer/utils/references'; +import { iterableFor, UpdatableReference } from 'ember-glimmer'; const ITERATOR_KEY_GUID = 'be277757-bbbe-4620-9fcb-213ef433cca2'; diff --git a/packages/ember-metal/lib/index.d.ts b/packages/ember-metal/lib/index.d.ts index 31b93a6dccd..88adb90d73e 100644 --- a/packages/ember-metal/lib/index.d.ts +++ b/packages/ember-metal/lib/index.d.ts @@ -1,3 +1,5 @@ +import { Tag } from '@glimmer/reference'; + interface IBackburner { join(...args: any[]): void; on(...args: any[]): void; @@ -13,6 +15,8 @@ interface IRun { currentRunLoop: boolean; } +export function peekMeta(obj: any): any; + export const run: IRun; export const PROPERTY_DID_CHANGE: symbol; @@ -35,9 +39,9 @@ export function didRender(object: any, key: string, reference: any): boolean; export function isNone(obj: any): boolean; -export function tagForProperty(object: any, propertyKey: string, _meta?: any): any; +export function tagForProperty(object: any, propertyKey: string, _meta?: any): Tag; -export function tagFor(object: any, _meta?: any): any; +export function tagFor(object: any, _meta?: any): Tag; export function watchKey(obj: any, keyName: string, meta?: any): void; diff --git a/packages/ember-metal/lib/tags.js b/packages/ember-metal/lib/tags.js index 83f49aedb4b..a1591c2f177 100644 --- a/packages/ember-metal/lib/tags.js +++ b/packages/ember-metal/lib/tags.js @@ -9,7 +9,7 @@ export function setHasViews(fn) { } function makeTag() { - return new DirtyableTag(); + return DirtyableTag.create(); } export function tagForProperty(object, propertyKey, _meta) { @@ -40,18 +40,18 @@ export function markObjectAsDirty(meta, propertyKey) { let objectTag = meta.readableTag(); if (objectTag !== undefined) { - objectTag.dirty(); + if (meta.isProxy()) { + objectTag.inner.first.inner.dirty(); + } else { + objectTag.inner.dirty(); + } } let tags = meta.readableTags(); let propertyTag = tags !== undefined ? tags[propertyKey] : undefined; if (propertyTag !== undefined) { - propertyTag.dirty(); - } - - if (propertyKey === 'content' && meta.isProxy()) { - objectTag.contentDidChange(); + propertyTag.inner.dirty(); } if (objectTag !== undefined || propertyTag !== undefined) { @@ -62,6 +62,7 @@ export function markObjectAsDirty(meta, propertyKey) { let backburner; function ensureRunloop() { if (backburner === undefined) { + // TODO why does this need to be lazy backburner = require('ember-metal').run.backburner; } diff --git a/packages/ember-runtime/lib/index.d.ts b/packages/ember-runtime/lib/index.d.ts index 2edc0f9536e..cc7401443d0 100644 --- a/packages/ember-runtime/lib/index.d.ts +++ b/packages/ember-runtime/lib/index.d.ts @@ -21,3 +21,5 @@ export const String: { export function objectAt(arr: any, i: number): any; export function isEmberArray(arr: any): boolean; + +export function _contentFor(proxy: any): any; diff --git a/packages/ember-runtime/lib/index.js b/packages/ember-runtime/lib/index.js index 225d414171f..59437b33b2e 100644 --- a/packages/ember-runtime/lib/index.js +++ b/packages/ember-runtime/lib/index.js @@ -33,7 +33,7 @@ export { export { default as Copyable } from './mixins/copyable'; export { default as Enumerable } from './mixins/enumerable'; export { - default as _ProxyMixin + default as _ProxyMixin, contentFor as _contentFor } from './mixins/-proxy'; export { onLoad, diff --git a/packages/ember-runtime/lib/mixins/-proxy.js b/packages/ember-runtime/lib/mixins/-proxy.js index 0152e7129b3..dfda4d44795 100644 --- a/packages/ember-runtime/lib/mixins/-proxy.js +++ b/packages/ember-runtime/lib/mixins/-proxy.js @@ -2,7 +2,7 @@ @module ember */ -import { CachedTag, DirtyableTag, UpdatableTag } from '@glimmer/reference'; +import { combine, CONSTANT_TAG, DirtyableTag, UpdatableTag } from '@glimmer/reference'; import { get, set, @@ -12,7 +12,7 @@ import { notifyPropertyChange, defineProperty, Mixin, - tagFor, + tagFor } from 'ember-metal'; import { assert, @@ -25,29 +25,13 @@ function contentPropertyDidChange(content, contentKey) { notifyPropertyChange(this, key); } -class ProxyTag extends CachedTag { - constructor(proxy) { - super(); - - let content = get(proxy, 'content'); - - this.proxy = proxy; - this.proxyWrapperTag = new DirtyableTag(); - this.proxyContentTag = new UpdatableTag(tagFor(content)); - } - - compute() { - return Math.max(this.proxyWrapperTag.value(), this.proxyContentTag.value()); - } - - dirty() { - this.proxyWrapperTag.dirty(); - } - - contentDidChange() { - let content = get(this.proxy, 'content'); - this.proxyContentTag.update(tagFor(content)); +export function contentFor(proxy, m) { + let content = get(proxy, 'content'); + let tag = (m === undefined ? meta(proxy) : m).readableTag(); + if (tag !== undefined) { + tag.inner.second.inner.update(tagFor(content)); } + return content; } /** @@ -73,7 +57,7 @@ export default Mixin.create({ this._super(...arguments); let m = meta(this); m.setProxy(); - m.writableTag((source)=> new ProxyTag(source)); + m.writableTag(() => combine([DirtyableTag.create(), UpdatableTag.create(CONSTANT_TAG)])); }, isTruthy: bool('content'), @@ -89,7 +73,7 @@ export default Mixin.create({ }, unknownProperty(key) { - let content = get(this, 'content'); + let content = contentFor(this); if (content) { return get(content, key); } @@ -105,7 +89,8 @@ export default Mixin.create({ return value; } - let content = get(this, 'content'); + let content = contentFor(this, m); + assert(`Cannot delegate set('${key}', ${value}) to the \'content\' property of object proxy ${this}: its 'content' is undefined.`, content); return set(content, key, value); diff --git a/packages/ember-runtime/lib/system/core_object.js b/packages/ember-runtime/lib/system/core_object.js index f98cb1a47b9..4e49c8c061e 100644 --- a/packages/ember-runtime/lib/system/core_object.js +++ b/packages/ember-runtime/lib/system/core_object.js @@ -60,6 +60,16 @@ function makeCtor() { constructor() { let self = this; + if (!wasApplied) { + Class.proto(); // prepare prototype... + } + + if (arguments.length > 0) { + initProperties = [arguments[0]]; + } + + let before = true; + if (MANDATORY_GETTER && EMBER_METAL_ES5_GETTERS && HAS_NATIVE_PROXY && typeof self.unknownProperty === 'function') { let messageFor = (obj, property) => { return `You attempted to access the \`${String(property)}\` property (of ${obj}).\n` + @@ -78,7 +88,13 @@ function makeCtor() { get(target, property, receiver) { if (property === PROXY_CONTENT) { return target; - } else if (typeof property === 'symbol' || property in target) { + } else if (before || + typeof property === 'symbol' || + property === NAME_KEY || + property === GUID_KEY_PROPERTY || + property === 'toJSON' || + property === 'toString' || + property === 'toStringExtension' || property in target) { return Reflect.get(target, property, receiver); } @@ -89,14 +105,6 @@ function makeCtor() { }); } - if (!wasApplied) { - Class.proto(); // prepare prototype... - } - - if (arguments.length > 0) { - initProperties = [arguments[0]]; - } - self.__defineNonEnumerable(GUID_KEY_PROPERTY); let m = meta(self); let proto = m.proto; @@ -195,7 +203,7 @@ function makeCtor() { if (ENV._ENABLE_BINDING_SUPPORT) { Mixin.finishPartial(self, m); } - + before = false; self.init(...arguments); self[POST_INIT](); diff --git a/packages/ember-template-compiler/lib/index.js b/packages/ember-template-compiler/lib/index.js index 2120b607e61..9ddffd9582f 100644 --- a/packages/ember-template-compiler/lib/index.js +++ b/packages/ember-template-compiler/lib/index.js @@ -14,7 +14,8 @@ export { default as precompile } from './system/precompile'; export { default as compile } from './system/compile'; export { default as compileOptions, - registerPlugin + registerPlugin, + unregisterPlugin } from './system/compile-options'; export { default as defaultPlugins } from './plugins'; diff --git a/packages/ember-template-compiler/lib/plugins/assert-input-helper-without-block.js b/packages/ember-template-compiler/lib/plugins/assert-input-helper-without-block.js index e607f6cae62..7cdd29987e3 100644 --- a/packages/ember-template-compiler/lib/plugins/assert-input-helper-without-block.js +++ b/packages/ember-template-compiler/lib/plugins/assert-input-helper-without-block.js @@ -7,7 +7,7 @@ export default function errorOnInputWithContent(env) { return { name: 'assert-input-helper-without-block', - visitors: { + visitor: { BlockStatement(node) { if (node.path.original !== 'input') { return; } diff --git a/packages/ember-template-compiler/lib/plugins/assert-reserved-named-arguments.js b/packages/ember-template-compiler/lib/plugins/assert-reserved-named-arguments.js index 160cae011a5..e845edea595 100644 --- a/packages/ember-template-compiler/lib/plugins/assert-reserved-named-arguments.js +++ b/packages/ember-template-compiler/lib/plugins/assert-reserved-named-arguments.js @@ -12,7 +12,7 @@ export default function assertReservedNamedArguments(env) { return { name: 'assert-reserved-named-arguments', - visitors: { + visitor: { PathExpression({ original, loc }) { if (isReserved(original)) { assert(`${assertMessage(original)} ${calculateLocationDisplay(moduleName, loc)}`); diff --git a/packages/ember-template-compiler/lib/plugins/deprecate-render-model.js b/packages/ember-template-compiler/lib/plugins/deprecate-render-model.js index ce3ee9bf5c5..ce74a8e61fb 100644 --- a/packages/ember-template-compiler/lib/plugins/deprecate-render-model.js +++ b/packages/ember-template-compiler/lib/plugins/deprecate-render-model.js @@ -12,7 +12,7 @@ export default function deprecateRenderModel(env) { return { name: 'deprecate-render-model', - visitors: { + visitor: { MustacheStatement(node) { if (node.path.original === 'render' && node.params.length > 1) { node.params.forEach(param => { diff --git a/packages/ember-template-compiler/lib/plugins/deprecate-render.js b/packages/ember-template-compiler/lib/plugins/deprecate-render.js index 2be88fc7364..850b950e3df 100644 --- a/packages/ember-template-compiler/lib/plugins/deprecate-render.js +++ b/packages/ember-template-compiler/lib/plugins/deprecate-render.js @@ -11,7 +11,7 @@ export default function deprecateRender(env) { return { name: 'deprecate-render', - visitors: { + visitor: { MustacheStatement(node) { if (node.path.original !== 'render') { return; } if (node.params.length !== 1) { return; } diff --git a/packages/ember-template-compiler/lib/plugins/extract-pragma-tag.js b/packages/ember-template-compiler/lib/plugins/extract-pragma-tag.js index f0cd7183712..3c40a434869 100644 --- a/packages/ember-template-compiler/lib/plugins/extract-pragma-tag.js +++ b/packages/ember-template-compiler/lib/plugins/extract-pragma-tag.js @@ -6,7 +6,7 @@ export default function extractPragmaTag(env) { return { name: 'exract-pragma-tag', - visitors: { + visitor: { MustacheStatement: { enter(node) { if (node.path.type === 'PathExpression' && node.path.original === PRAGMA_TAG) { diff --git a/packages/ember-template-compiler/lib/plugins/transform-action-syntax.js b/packages/ember-template-compiler/lib/plugins/transform-action-syntax.js index 1bc0339ba9b..3b12392d087 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-action-syntax.js +++ b/packages/ember-template-compiler/lib/plugins/transform-action-syntax.js @@ -29,7 +29,7 @@ export default function transformActionSyntax({ syntax }) { return { name: 'transform-action-syntax', - visitors: { + visitor: { ElementModifierStatement(node) { if (isAction(node)) { insertThisAsFirstParam(node, b); diff --git a/packages/ember-template-compiler/lib/plugins/transform-angle-bracket-components.js b/packages/ember-template-compiler/lib/plugins/transform-angle-bracket-components.js index 328f010bd5d..0175acd1c24 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-angle-bracket-components.js +++ b/packages/ember-template-compiler/lib/plugins/transform-angle-bracket-components.js @@ -2,7 +2,7 @@ export default function transformAngleBracketComponents(/* env */) { return { name: 'transform-angle-bracket-components', - visitors: { + visitor: { ComponentNode(node) { node.tag = `<${node.tag}>`; } diff --git a/packages/ember-template-compiler/lib/plugins/transform-attrs-into-args.js b/packages/ember-template-compiler/lib/plugins/transform-attrs-into-args.js index 955aae46322..638a7b41d43 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-attrs-into-args.js +++ b/packages/ember-template-compiler/lib/plugins/transform-attrs-into-args.js @@ -30,7 +30,7 @@ export default function transformAttrsIntoArgs(env) { return { name: 'transform-attrs-into-args', - visitors: { + visitor: { Program: { enter(node) { let parent = stack[stack.length - 1]; diff --git a/packages/ember-template-compiler/lib/plugins/transform-dot-component-invocation.js b/packages/ember-template-compiler/lib/plugins/transform-dot-component-invocation.js index 4cb8693330d..7edd10d42dc 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-dot-component-invocation.js +++ b/packages/ember-template-compiler/lib/plugins/transform-dot-component-invocation.js @@ -58,7 +58,7 @@ export default function transformDotComponentInvocation(env) { return { name: 'transform-dot-component-invocation', - visitors: { + visitor: { MustacheStatement: (node) => { if (isInlineInvocation(node.path, node.params, node.hash)) { wrapInComponent(node, b); diff --git a/packages/ember-template-compiler/lib/plugins/transform-each-in-into-each.js b/packages/ember-template-compiler/lib/plugins/transform-each-in-into-each.js index 8805428b6e3..91426126921 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-each-in-into-each.js +++ b/packages/ember-template-compiler/lib/plugins/transform-each-in-into-each.js @@ -24,7 +24,7 @@ export default function transformEachInIntoEach(env) { return { name: 'transform-each-in-into-each', - visitors: { + visitor: { BlockStatement(node) { if (node.path.original === 'each-in') { node.params[0] = b.sexpr(b.path('-each-in'), [node.params[0]]); diff --git a/packages/ember-template-compiler/lib/plugins/transform-has-block-syntax.js b/packages/ember-template-compiler/lib/plugins/transform-has-block-syntax.js index 5e4058c61d6..6ecb24246d8 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-has-block-syntax.js +++ b/packages/ember-template-compiler/lib/plugins/transform-has-block-syntax.js @@ -30,7 +30,7 @@ export default function transformHasBlockSyntax(env) { return { name: 'transform-has-block-syntax', - visitors: { + visitor: { PathExpression(node) { if (TRANSFORMATIONS[node.original]) { return b.sexpr(b.path(TRANSFORMATIONS[node.original])); diff --git a/packages/ember-template-compiler/lib/plugins/transform-inline-link-to.js b/packages/ember-template-compiler/lib/plugins/transform-inline-link-to.js index 15c6829687a..58534adac51 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-inline-link-to.js +++ b/packages/ember-template-compiler/lib/plugins/transform-inline-link-to.js @@ -26,7 +26,7 @@ export default function transformInlineLinkTo(env) { return { name: 'transform-inline-link-to', - visitors: { + visitor: { MustacheStatement(node) { if (node.path.original === 'link-to') { let content = node.escaped ? node.params[0] : unsafeHtml(b, node.params[0]); diff --git a/packages/ember-template-compiler/lib/plugins/transform-input-type-syntax.js b/packages/ember-template-compiler/lib/plugins/transform-input-type-syntax.js index 356120137f5..eb1458d5bbc 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-input-type-syntax.js +++ b/packages/ember-template-compiler/lib/plugins/transform-input-type-syntax.js @@ -29,7 +29,7 @@ export default function transformInputTypeSyntax(env) { return { name: 'transform-input-type-syntax', - visitors: { + visitor: { MustacheStatement(node) { if (isInput(node)) { insertTypeHelperParameter(node, b); diff --git a/packages/ember-template-compiler/lib/plugins/transform-old-binding-syntax.js b/packages/ember-template-compiler/lib/plugins/transform-old-binding-syntax.js index 72dc067b41f..176445d89bf 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-old-binding-syntax.js +++ b/packages/ember-template-compiler/lib/plugins/transform-old-binding-syntax.js @@ -8,7 +8,7 @@ export default function transformOldBindingSyntax(env) { return { name: 'transform-old-binding-syntax', - visitors: { + visitor: { BlockStatement(node) { processHash(b, node, moduleName); }, diff --git a/packages/ember-template-compiler/lib/plugins/transform-old-class-binding-syntax.js b/packages/ember-template-compiler/lib/plugins/transform-old-class-binding-syntax.js index d6eb0f7f9d9..71a87cf6a57 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-old-class-binding-syntax.js +++ b/packages/ember-template-compiler/lib/plugins/transform-old-class-binding-syntax.js @@ -4,7 +4,7 @@ export default function transformOldClassBindingSyntax(env) { return { name: 'transform-old-class-binding-syntax', - visitors: { + visitor: { MustacheStatement(node) { process(b, node); }, diff --git a/packages/ember-template-compiler/lib/plugins/transform-quoted-bindings-into-just-bindings.js b/packages/ember-template-compiler/lib/plugins/transform-quoted-bindings-into-just-bindings.js index 10782f55742..d2fd7cb3f19 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-quoted-bindings-into-just-bindings.js +++ b/packages/ember-template-compiler/lib/plugins/transform-quoted-bindings-into-just-bindings.js @@ -3,7 +3,7 @@ export default function transformQuotedBindingsIntoJustBindings(/* env */) { return { name: 'transform-quoted-bindings-into-just-bindings', - visitors: { + visitor: { ElementNode(node) { let styleAttr = getStyleAttr(node); diff --git a/packages/ember-template-compiler/lib/plugins/transform-top-level-components.js b/packages/ember-template-compiler/lib/plugins/transform-top-level-components.js index 304154a3c9f..35aa907c2b3 100644 --- a/packages/ember-template-compiler/lib/plugins/transform-top-level-components.js +++ b/packages/ember-template-compiler/lib/plugins/transform-top-level-components.js @@ -2,7 +2,7 @@ export default function transformTopLevelComponent(/* env */) { return { name: 'transform-top-level-component', - visitors: { + visitor: { Program(node) { hasSingleComponentNode(node, component => { component.tag = `@${component.tag}`; diff --git a/packages/ember-template-compiler/lib/system/compile-options.js b/packages/ember-template-compiler/lib/system/compile-options.js index bb1deaaa862..231b4727a28 100644 --- a/packages/ember-template-compiler/lib/system/compile-options.js +++ b/packages/ember-template-compiler/lib/system/compile-options.js @@ -16,27 +16,24 @@ export default function compileOptions(_options) { options.plugins = { ast: [...USER_PLUGINS, ...PLUGINS] }; } else { let potententialPugins = [...USER_PLUGINS, ...PLUGINS]; + let providedPlugins = options.plugins.ast.map(plugin => wrapLegacyPluginIfNeeded(plugin)); let pluginsToAdd = potententialPugins.filter((plugin) => { return options.plugins.ast.indexOf(plugin) === -1; }); - options.plugins.ast = options.plugins.ast.slice().concat(pluginsToAdd); + options.plugins.ast = providedPlugins.concat(pluginsToAdd); } return options; } -export function registerPlugin(type, _plugin) { - if (type !== 'ast') { - throw new Error(`Attempting to register ${_plugin} as "${type}" which is not a valid Glimmer plugin type.`); - } - - let plugin; +function wrapLegacyPluginIfNeeded(_plugin) { + let plugin = _plugin; if (_plugin.prototype && _plugin.prototype.transform) { plugin = (env) => { return { name: _plugin.constructor && _plugin.constructor.name, - visitors: { + visitor: { Program(node) { let plugin = new _plugin(env); @@ -45,16 +42,24 @@ export function registerPlugin(type, _plugin) { return plugin.transform(node); } } - }; + }; }; - } else { - plugin = _plugin; } + return plugin; +} + +export function registerPlugin(type, _plugin) { + if (type !== 'ast') { + throw new Error(`Attempting to register ${_plugin} as "${type}" which is not a valid Glimmer plugin type.`); + } + + let plugin = wrapLegacyPluginIfNeeded(_plugin); + USER_PLUGINS = [plugin, ...USER_PLUGINS]; } -export function removePlugin(type, PluginClass) { +export function unregisterPlugin(type, PluginClass) { if (type !== 'ast') { throw new Error(`Attempting to unregister ${PluginClass} as "${type}" which is not a valid Glimmer plugin type.`); } diff --git a/packages/ember-template-compiler/tests/system/compile_options_test.js b/packages/ember-template-compiler/tests/system/compile_options_test.js index ec6b86ff4e9..f4c78dc12dd 100644 --- a/packages/ember-template-compiler/tests/system/compile_options_test.js +++ b/packages/ember-template-compiler/tests/system/compile_options_test.js @@ -1,6 +1,5 @@ -import { compileOptions } from '../../index'; -import { defaultPlugins } from '../../index'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { compile, compileOptions, defaultPlugins, registerPlugin, unregisterPlugin } from '../../index'; +import { moduleFor, AbstractTestCase, RenderingTestCase } from 'internal-test-helpers'; moduleFor('ember-template-compiler: default compile options', class extends AbstractTestCase { ['@test default options are a new copy'](assert) { @@ -18,3 +17,69 @@ moduleFor('ember-template-compiler: default compile options', class extends Abst } } }); + +class CustomTransform { + constructor(options) { + this.options = options; + this.syntax = null; + } + + transform(ast) { + let walker = new this.syntax.Walker(); + + walker.visit(ast, node => { + if (node.type !== 'ElementNode') { + return; + } + + for (var i = 0; i < node.attributes.length; i++) { + let attribute = node.attributes[i]; + + if (attribute.name === 'data-test') { + node.attributes.splice(i, 1); + } + } + }); + + return ast; + } +} + +moduleFor('ember-template-compiler: registerPlugin with a custom plugins', class extends RenderingTestCase { + beforeEach() { + registerPlugin('ast', CustomTransform); + } + + afterEach() { + unregisterPlugin('ast', CustomTransform); + } + + ['@test custom plugins can be used']() { + this.render('
'); + this.assertElement(this.firstChild, { + tagName: 'div', + attrs: { class: 'hahaha', 'data-blah': 'derp' }, + content: '' + }); + } +}); + +moduleFor('ember-template-compiler: custom plugins passed to compile', class extends RenderingTestCase { + // override so that we can provide custom AST plugins to compile + compile(templateString) { + return compile(templateString, { + plugins: { + ast: [CustomTransform] + } + }); + } + + ['@test custom plugins can be used']() { + this.render('
'); + this.assertElement(this.firstChild, { + tagName: 'div', + attrs: { class: 'hahaha', 'data-blah': 'derp' }, + content: '' + }); + } +}); diff --git a/packages/ember-utils/lib/index.d.ts b/packages/ember-utils/lib/index.d.ts index 90a008fd350..f1eb52fda41 100644 --- a/packages/ember-utils/lib/index.d.ts +++ b/packages/ember-utils/lib/index.d.ts @@ -1,7 +1,31 @@ import { Opaque } from '@glimmer/interfaces'; +export interface Factory { + class: C; + fullName: string; + normalizedName: string; + create(props?: { [prop: string]: any; }): T; +} + +export interface LookupOptions { + source: string; +} + +export interface Owner { + lookup(fullName: string, options?: LookupOptions): T; + lookup(fullName: string, options?: LookupOptions): any; + factoryFor(fullName: string, options?: LookupOptions): Factory | undefined; + factoryFor(fullName: string, options?: LookupOptions): Factory | undefined; + buildChildEngineInstance(name: string): T; + hasRegistration(name: string, options?: LookupOptions): boolean; + + // maybe not needed, we were only using for cache key + _resolveLocalLookupName(name: string, source: string): any; +} + export const NAME_KEY: string; -export function getOwner(obj: any): any; +export function getOwner(obj: {}): Owner; +export function setOwner(obj: {}, owner: Owner): void; export function symbol(debugName: string): string; export function assign(original: any, ...args: any[]): any; export const OWNER: string; diff --git a/packages/ember-views/lib/index.d.ts b/packages/ember-views/lib/index.d.ts index 179f14209b3..cc9cdec7763 100644 --- a/packages/ember-views/lib/index.d.ts +++ b/packages/ember-views/lib/index.d.ts @@ -1,5 +1,18 @@ +import { Simple } from '@glimmer/interfaces'; import { Opaque } from '@glimmer/util'; -import { Simple } from '@glimmer/runtime'; +import { Template } from '@glimmer/runtime'; +import { Owner, Factory } from 'ember-utils'; + +export interface StaticTemplateMeta { + moduleName: string; + managerId?: string; +} + +export interface OwnedTemplateMeta extends StaticTemplateMeta { + owner: Owner; + moduleName: string; + managerId?: string; +} export const ActionSupport: any; export const ChildViewsSupport: any; @@ -9,8 +22,8 @@ export const ViewMixin: any; export const ViewStateSupport: any; export const TextSupport: any; -export function getViewElement(view: Opaque): Element; -export function setViewElement(view: Opaque, element: Element | null): void; +export function getViewElement(view: Opaque): Simple.Element; +export function setViewElement(view: Opaque, element: Simple.Element | null): void; export function isSimpleClick(event: Event): boolean; @@ -18,9 +31,12 @@ export function constructStyleDeprecationMessage(affectedStyle: any): string; export function hasPartial(name: string, owner: any): boolean; -export function lookupComponent(owner: any, name: string, options: any): any; +export function lookupComponent(owner: Owner, name: string, options?: { source?: string }): { + layout: Template; + component: Factory; +}; -export function lookupPartial(templateName: string, owner: any): any; +export function lookupPartial(templateName: string, owner: Owner): any; export function getViewId(view: any): string; diff --git a/packages/ember-views/lib/utils/lookup-component.js b/packages/ember-views/lib/utils/lookup-component.js index 9f45b74476a..5b142edf0e7 100644 --- a/packages/ember-views/lib/utils/lookup-component.js +++ b/packages/ember-views/lib/utils/lookup-component.js @@ -12,7 +12,7 @@ function lookupModuleUnificationComponentPair(componentLookup, owner, name, opti let globalLayout = componentLookup.layoutFor(name, owner); let localAndUniqueComponent = !!localComponent && (!globalComponent || localComponent.class !== globalComponent.class); - let localAndUniqueLayout = !!localLayout && (!globalLayout || localLayout.meta.moduleName !== globalLayout.meta.moduleName); + let localAndUniqueLayout = !!localLayout && (!globalLayout || localLayout.referrer.moduleName !== globalLayout.referrer.moduleName); if (localAndUniqueComponent && localAndUniqueLayout) { return { layout: localLayout, component: localComponent }; diff --git a/packages/ember/tests/component_context_test.js b/packages/ember/tests/component_context_test.js index afb46caaa7b..4bd733f1e7b 100644 --- a/packages/ember/tests/component_context_test.js +++ b/packages/ember/tests/component_context_test.js @@ -126,7 +126,7 @@ moduleFor('Application Lifecycle - Component Context', class extends Application ComponentClass: Component.extend({ didInsertElement() { // FIXME: I'm unsure if this is even the right way to access attrs - this.$().html(this.get('attrs.attrs.value')); + this.$().html(this.get('attrs.attrs').value); } }) }); diff --git a/packages/ember/tests/helpers/link_to_test/link_to_transitioning_classes_test.js b/packages/ember/tests/helpers/link_to_test/link_to_transitioning_classes_test.js index 7819e67eec7..c9a217bf412 100644 --- a/packages/ember/tests/helpers/link_to_test/link_to_transitioning_classes_test.js +++ b/packages/ember/tests/helpers/link_to_test/link_to_transitioning_classes_test.js @@ -201,12 +201,16 @@ moduleFor(`The {{link-to}} helper: .transitioning-in .transitioning-out CSS clas } [`@test while a transition is underway with nested link-to's`](assert) { - let $index = this.$('#index-link'); + // TODO undo changes to this test but currently this test navigates away if navigation + // outlet is not stable and the second $about.click() is triggered. let $about = this.$('#about-link'); - let $other = this.$('#other-link'); $about.click(); + let $index = this.$('#index-link'); + $about = this.$('#about-link'); + let $other = this.$('#other-link'); + assertHasClass(assert, $index, 'active'); assertHasNoClass(assert, $about, 'active'); assertHasNoClass(assert, $about, 'active'); @@ -221,6 +225,10 @@ moduleFor(`The {{link-to}} helper: .transitioning-in .transitioning-out CSS clas this.resolveAbout(); + $index = this.$('#index-link'); + $about = this.$('#about-link'); + $other = this.$('#other-link'); + assertHasNoClass(assert, $index, 'active'); assertHasClass(assert, $about, 'active'); assertHasNoClass(assert, $other, 'active'); @@ -235,6 +243,10 @@ moduleFor(`The {{link-to}} helper: .transitioning-in .transitioning-out CSS clas $other.click(); + $index = this.$('#index-link'); + $about = this.$('#about-link'); + $other = this.$('#other-link'); + assertHasNoClass(assert, $index, 'active'); assertHasClass(assert, $about, 'active'); assertHasNoClass(assert, $other, 'active'); @@ -249,6 +261,10 @@ moduleFor(`The {{link-to}} helper: .transitioning-in .transitioning-out CSS clas this.resolveOther(); + $index = this.$('#index-link'); + $about = this.$('#about-link'); + $other = this.$('#other-link'); + assertHasNoClass(assert, $index, 'active'); assertHasNoClass(assert, $about, 'active'); assertHasClass(assert, $other, 'active'); @@ -263,6 +279,9 @@ moduleFor(`The {{link-to}} helper: .transitioning-in .transitioning-out CSS clas $about.click(); + $index = this.$('#index-link'); + $about = this.$('#about-link'); + $other = this.$('#other-link'); assertHasNoClass(assert, $index, 'active'); assertHasNoClass(assert, $about, 'active'); @@ -278,6 +297,10 @@ moduleFor(`The {{link-to}} helper: .transitioning-in .transitioning-out CSS clas this.resolveAbout(); + $index = this.$('#index-link'); + $about = this.$('#about-link'); + $other = this.$('#other-link'); + assertHasNoClass(assert, $index, 'active'); assertHasClass(assert, $about, 'active'); assertHasNoClass(assert, $other, 'active'); diff --git a/packages/internal-test-helpers/lib/test-cases/rendering.js b/packages/internal-test-helpers/lib/test-cases/rendering.js index 3c4a44b05f7..22df8187102 100644 --- a/packages/internal-test-helpers/lib/test-cases/rendering.js +++ b/packages/internal-test-helpers/lib/test-cases/rendering.js @@ -1,6 +1,5 @@ -import { ComponentLookup } from 'ember-views'; - import AbstractRenderingTestCase from './abstract-rendering'; +import { privatize as P } from 'container'; export default class RenderingTestCase extends AbstractRenderingTestCase { constructor() { @@ -8,8 +7,8 @@ export default class RenderingTestCase extends AbstractRenderingTestCase { let { owner } = this; this.env = owner.lookup('service:-glimmer-environment'); - owner.register('component-lookup:main', ComponentLookup); - owner.registerOptionsForType('helper', { instantiate: false }); - owner.registerOptionsForType('component', { singleton: false }); + this.templateOptions = owner.lookup(P`template-options:main`); + this.compileTimeLookup = this.templateOptions.resolver; + this.runtimeResolver = this.compileTimeLookup.resolver; } } diff --git a/testem.dist.js b/testem.dist.js index 6088d262f3a..bc1f8e680de 100644 --- a/testem.dist.js +++ b/testem.dist.js @@ -50,38 +50,38 @@ module.exports = { test_page: "dist/tests/index.html?hidepassed&hideskipped&timeout=60000", timeout: 540, reporter: FailureOnlyPerBrowserReporter, - browser_start_timeout: 600, + browser_start_timeout: 1200, parallel: 4, disable_watching: true, launchers: { BS_Chrome_Current: { exe: "node_modules/.bin/browserstack-launch", - args: ["--os", "Windows", "--osv", "10", "--b", "chrome", "--bv", "latest", "-t", "600", "--u", ""], + args: ["--os", "Windows", "--osv", "10", "--b", "chrome", "--bv", "latest", "-t", "1200", "--u", ""], protocol: "browser" }, BS_Firefox_Current: { exe: "node_modules/.bin/browserstack-launch", - args: ["--os", "Windows", "--osv", "10", "--b", "firefox", "--bv", "latest", "-t", "600", "--u", ""], + args: ["--os", "Windows", "--osv", "10", "--b", "firefox", "--bv", "latest", "-t", "1200", "--u", ""], protocol: "browser" }, BS_Safari_Current: { exe: "node_modules/.bin/browserstack-launch", - args: ["--os", "OS X", "--osv", "High Sierra", "--b", "safari", "--bv", "11", "-t", "600", "--u", ""], + args: ["--os", "OS X", "--osv", "High Sierra", "--b", "safari", "--bv", "11", "-t", "1200", "--u", ""], protocol: "browser" }, BS_Safari_Last: { exe: "node_modules/.bin/browserstack-launch", - args: ["--os", "OS X", "--osv", "Sierra", "--b", "safari", "--bv", "10.1", "-t", "600", "--u", ""], + args: ["--os", "OS X", "--osv", "Sierra", "--b", "safari", "--bv", "10.1", "-t", "1200", "--u", ""], protocol: "browser" }, BS_MS_Edge: { exe: "node_modules/.bin/browserstack-launch", - args: ["--os", "Windows", "--osv", "10", "--b", "edge", "--bv", "latest", "-t", "600", "--u", ""], + args: ["--os", "Windows", "--osv", "10", "--b", "edge", "--bv", "latest", "-t", "1200", "--u", ""], protocol: "browser" }, BS_IE_11: { exe: "node_modules/.bin/browserstack-launch", - args: ["--os", "Windows", "--osv", "10", "--b", "ie", "--bv", "11.0", "-t", "600", "--u", ""], + args: ["--os", "Windows", "--osv", "10", "--b", "ie", "--bv", "11.0", "-t", "1200", "--u", ""], protocol: "browser" } }, diff --git a/tests/index.html b/tests/index.html index b41426acf60..678e3bf9626 100644 --- a/tests/index.html +++ b/tests/index.html @@ -180,6 +180,12 @@ }); QUnit.testDone(function(results) { + var oldFixture = document.getElementById('qunit-fixture'); + var parent = oldFixture.parentElement; + var newFixture = document.createElement('div'); + newFixture.id = 'qunit-fixture'; + parent.replaceChild(newFixture, oldFixture); + testsTotal++; if (results.failed) { diff --git a/tests/node/helpers/component-module.js b/tests/node/helpers/component-module.js index 0a0ab46d0b7..ebdb331b501 100644 --- a/tests/node/helpers/component-module.js +++ b/tests/node/helpers/component-module.js @@ -107,7 +107,6 @@ function render(_template) { name: 'index', controller: this, template: this.owner.lookup(templateFullName), - outlets: { } }; stateToRender.name = 'index'; diff --git a/tsconfig.json b/tsconfig.json index e318fb334c3..e2b0b442e85 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,14 +14,12 @@ "moduleResolution": "node", // Enhance Strictness - "noImplicitAny": true, + "strict": true, + "noImplicitReturns": true, + "allowUnreachableCode": false, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "noUnusedParameters": true, - "allowUnreachableCode": false, - "strictNullChecks": true, - "noImplicitReturns": true, - "noImplicitThis": true, "newLine": "LF", "noEmit": true, diff --git a/tslint.json b/tslint.json index 0943a1d2394..d1eb7fac9be 100644 --- a/tslint.json +++ b/tslint.json @@ -16,6 +16,11 @@ "semicolon": [true, "always"], "triple-equals": true, "class-name": true, - "no-require-imports": true + "no-require-imports": true, + "no-duplicate-imports": true, + "ordered-imports": true, + "quotemark": { + "options": ["single", "avoid-escape"] + } } } diff --git a/yarn.lock b/yarn.lock index 19212a174a6..3bc7f850683 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,78 +2,102 @@ # yarn lockfile v1 -"@glimmer/compiler@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.25.6.tgz#dcc2b8bfa6f36b70c34e41e85626f888315d3ad7" +"@glimmer/compiler@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.30.5.tgz#b65e377653475b7c401c7eafd0e554cc388103e3" dependencies: - "@glimmer/interfaces" "^0.25.6" - "@glimmer/syntax" "^0.25.6" - "@glimmer/util" "^0.25.6" - "@glimmer/wire-format" "^0.25.6" - simple-html-tokenizer "^0.3.0" + "@glimmer/interfaces" "^0.30.5" + "@glimmer/syntax" "^0.30.5" + "@glimmer/util" "^0.30.5" + "@glimmer/wire-format" "^0.30.5" + simple-html-tokenizer "^0.4.1" -"@glimmer/interfaces@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.25.6.tgz#88a6cdb4f414c3a82662cc5fc5cb5bfd91d4ef10" - dependencies: - "@glimmer/wire-format" "^0.25.6" +"@glimmer/encoder@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/encoder/-/encoder-0.30.5.tgz#461d81f09215d34294b02e8f18b6cf0e8a2078aa" -"@glimmer/node@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/node/-/node-0.25.6.tgz#3887a79d8fd2cf9376511245babe87647e4349df" +"@glimmer/interfaces@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.30.5.tgz#9fd391ff4b9e4b29cf56495a2c3f5f660e45497a" dependencies: - "@glimmer/runtime" "^0.25.6" - simple-dom "^0.3.0" + "@glimmer/wire-format" "^0.30.5" -"@glimmer/object-reference@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/object-reference/-/object-reference-0.25.6.tgz#ab45f51c9416bdaff402a0ea3cedd3b1807b1158" - dependencies: - "@glimmer/reference" "^0.25.6" - "@glimmer/util" "^0.25.6" +"@glimmer/low-level@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/low-level/-/low-level-0.30.5.tgz#d1e52d4cc5ad753d28ff4168f91d519fc416ff54" -"@glimmer/object@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/object/-/object-0.25.6.tgz#a07860e551980488c2839c6393f7a2c071d68a6e" +"@glimmer/node@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/node/-/node-0.30.5.tgz#79ae64537a3046daff0497e586ff29ca712f0821" dependencies: - "@glimmer/object-reference" "^0.25.6" - "@glimmer/util" "^0.25.6" + "@glimmer/interfaces" "^0.30.5" + "@glimmer/runtime" "^0.30.5" + simple-dom "^0.3.0" -"@glimmer/reference@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.25.6.tgz#bc57ccc351fc4bcc5750bdb7760cec4b9ef628f4" - dependencies: - "@glimmer/util" "^0.25.6" +"@glimmer/opcode-compiler@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/opcode-compiler/-/opcode-compiler-0.30.5.tgz#7a9d15740c2cf17b76a1ea77b69eb66baa8220c9" + dependencies: + "@glimmer/encoder" "^0.30.5" + "@glimmer/interfaces" "^0.30.5" + "@glimmer/program" "^0.30.5" + "@glimmer/util" "^0.30.5" + "@glimmer/vm" "^0.30.5" + "@glimmer/wire-format" "^0.30.5" + +"@glimmer/program@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/program/-/program-0.30.5.tgz#976bcd0a10361f96e0d6d2d2b483c175c767ee26" + dependencies: + "@glimmer/encoder" "^0.30.5" + "@glimmer/interfaces" "^0.30.5" + "@glimmer/util" "^0.30.5" + +"@glimmer/reference@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.30.5.tgz#71062ea435ea47830b93ab87d4be818865fdc0e6" + dependencies: + "@glimmer/util" "^0.30.5" + +"@glimmer/runtime@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/runtime/-/runtime-0.30.5.tgz#41ffdbd1c1268d56ad8f6b811e4d320abf8359da" + dependencies: + "@glimmer/interfaces" "^0.30.5" + "@glimmer/low-level" "^0.30.5" + "@glimmer/opcode-compiler" "^0.30.5" + "@glimmer/program" "^0.30.5" + "@glimmer/reference" "^0.30.5" + "@glimmer/util" "^0.30.5" + "@glimmer/vm" "^0.30.5" + "@glimmer/wire-format" "^0.30.5" + +"@glimmer/syntax@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.30.5.tgz#763a631fccdb5ee2468269becd215752fc2361cc" + dependencies: + "@glimmer/interfaces" "^0.30.5" + "@glimmer/util" "^0.30.5" + handlebars "^4.0.6" + simple-html-tokenizer "^0.4.1" -"@glimmer/runtime@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/runtime/-/runtime-0.25.6.tgz#2a776dcdd3b8f844e1f6b4e7ca4478b6dc560133" - dependencies: - "@glimmer/interfaces" "^0.25.6" - "@glimmer/object" "^0.25.6" - "@glimmer/object-reference" "^0.25.6" - "@glimmer/reference" "^0.25.6" - "@glimmer/util" "^0.25.6" - "@glimmer/wire-format" "^0.25.6" +"@glimmer/util@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.30.5.tgz#97f3dcd4029f713c503371e1608e129a833a70e1" -"@glimmer/syntax@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.25.6.tgz#aeed43004ece8715d09189b61037c793225b4e88" +"@glimmer/vm@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/vm/-/vm-0.30.5.tgz#10512f7a8b7505d25cafe726c4a95fce01c67fd1" dependencies: - "@glimmer/interfaces" "^0.25.6" - "@glimmer/util" "^0.25.6" - handlebars "^4.0.6" - simple-html-tokenizer "^0.3.0" - -"@glimmer/util@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.25.6.tgz#de8dde7f5d30f9c0e3e2f083e3f6d3426a92b302" + "@glimmer/interfaces" "^0.30.5" + "@glimmer/program" "^0.30.5" + "@glimmer/util" "^0.30.5" -"@glimmer/wire-format@^0.25.6": - version "0.25.6" - resolved "https://registry.yarnpkg.com/@glimmer/wire-format/-/wire-format-0.25.6.tgz#f0543801d234e133bf72d2bdec68dd2c36c06eca" +"@glimmer/wire-format@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@glimmer/wire-format/-/wire-format-0.30.5.tgz#17d79a320b931950ac03887733e56ecc7bbd0a06" dependencies: - "@glimmer/util" "^0.25.6" + "@glimmer/util" "^0.30.5" "@types/acorn@*": version "4.0.3" @@ -6204,10 +6228,6 @@ simple-dom@^0.3.0: version "0.3.2" resolved "https://registry.yarnpkg.com/simple-dom/-/simple-dom-0.3.2.tgz#0663d10f1556f1500551d518f56e3aba0871371d" -simple-html-tokenizer@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.3.0.tgz#9b8b5559d80e331a544dd13dd59382e5d0d94411" - simple-html-tokenizer@^0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.4.3.tgz#9b00b766e30058b4bb377c0d4f97566a13ab1be1"