Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Modifier Manager #17143

Merged
merged 4 commits into from Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/@ember/-internals/glimmer/index.ts
Expand Up @@ -306,4 +306,6 @@ export { default as DebugStack } from './lib/utils/debug-stack';
export { default as OutletView } from './lib/views/outlet';
export { capabilities } from './lib/component-managers/custom';
export { setComponentManager, getComponentManager } from './lib/utils/custom-component-manager';
export { setModifierManager, getModifierManager } from './lib/utils/custom-modifier-manager';
export { capabilities as modifierCapabilties } from './lib/modifiers/custom';
export { isSerializationFirstNode } from './lib/utils/serialization-first-node-helpers';
Expand Up @@ -22,6 +22,7 @@ import { Destroyable } from '@glimmer/util';
import Environment from '../environment';
import RuntimeResolver from '../resolver';
import { OwnedTemplate } from '../template';
import { ManagerArgs, valueForCapturedArgs } from '../utils/managers';
import { RootReference } from '../utils/references';
import AbstractComponentManager from './abstract';

Expand Down Expand Up @@ -64,15 +65,10 @@ export interface Capabilities {
destructor: boolean;
}

export interface CustomComponentManagerArgs {
named: Dict<Opaque>;
positional: Opaque[];
}

export interface ManagerDelegate<ComponentInstance> {
capabilities: Capabilities;
createComponent(factory: Opaque, args: CustomComponentManagerArgs): ComponentInstance;
updateComponent(instance: ComponentInstance, args: CustomComponentManagerArgs): void;
createComponent(factory: Opaque, args: ManagerArgs): ComponentInstance;
updateComponent(instance: ComponentInstance, args: ManagerArgs): void;
getContext(instance: ComponentInstance): Opaque;
}

Expand Down Expand Up @@ -104,12 +100,6 @@ export interface ComponentArguments {
named: Dict<Opaque>;
}

function valueForCapturedArgs(args: CapturedArguments): CustomComponentManagerArgs {
return {
named: args.named.value(),
positional: args.positional.value(),
};
}
/**
The CustomComponentManager allows addons to provide custom component
implementations that integrate seamlessly into Ember. This is accomplished
Expand Down
97 changes: 97 additions & 0 deletions packages/@ember/-internals/glimmer/lib/modifiers/custom.ts
@@ -0,0 +1,97 @@
import { Factory } from '@ember/-internals/owner';
import { Opaque } from '@glimmer/interfaces';
import { Tag } from '@glimmer/reference';
import { Arguments, CapturedArguments, ModifierManager } from '@glimmer/runtime';
import { ManagerArgs, valueForCapturedArgs } from '../utils/managers';

export interface CustomModifierDefinitionState<ModifierInstance> {
ModifierClass: Factory<ModifierInstance>;
name: string;
delegate: ModifierManagerDelegate<ModifierInstance>;
}

export interface Capabilities {}

// Currently there are no capabilities for modifiers
export function capabilities(_managerAPI: string, _optionalFeatures?: {}): Capabilities {
return {};
}

export class CustomModifierDefinition<ModifierInstance> {
public state: CustomModifierDefinitionState<ModifierInstance>;
public manager = CUSTOM_MODIFIER_MANAGER;
constructor(
public name: string,
public ModifierClass: Factory<ModifierInstance>,
public delegate: ModifierManagerDelegate<ModifierInstance>
) {
this.state = {
ModifierClass,
name,
delegate,
};
}
}

export class CustomModifierState<ModifierInstance> {
constructor(
public element: Element,
public delegate: ModifierManagerDelegate<ModifierInstance>,
public modifier: ModifierInstance,
public args: CapturedArguments
) {}

destroy() {
const { delegate, modifier, args } = this;
let modifierArgs = valueForCapturedArgs(args);
delegate.destroyModifier(modifier, modifierArgs);
}
}

export interface ModifierManagerDelegate<ModifierInstance> {
capabilities: Capabilities;
createModifier(factory: Opaque, args: ManagerArgs): ModifierInstance;
installModifier(instance: ModifierInstance, element: Element, args: ManagerArgs): void;
updateModifier(instance: ModifierInstance, args: ManagerArgs): void;
destroyModifier(instance: ModifierInstance, args: ManagerArgs): void;
}

class CustomModifierManager<ModifierInstance>
implements
ModifierManager<
CustomModifierState<ModifierInstance>,
CustomModifierDefinitionState<ModifierInstance>
> {
create(
element: Element,
definition: CustomModifierDefinitionState<ModifierInstance>,
args: Arguments
) {
const capturedArgs = args.capture();
let modifierArgs = valueForCapturedArgs(capturedArgs);
let instance = definition.delegate.createModifier(definition.ModifierClass, modifierArgs);
return new CustomModifierState(element, definition.delegate, instance, capturedArgs);
}

getTag({ args }: CustomModifierState<ModifierInstance>): Tag {
return args.tag;
}

install(state: CustomModifierState<ModifierInstance>) {
let { element, args, delegate, modifier } = state;
let modifierArgs = valueForCapturedArgs(args);
delegate.installModifier(modifier, element, modifierArgs);
}

update(state: CustomModifierState<ModifierInstance>) {
let { args, delegate, modifier } = state;
let modifierArgs = valueForCapturedArgs(args);
delegate.updateModifier(modifier, modifierArgs);
}

getDestructor(state: CustomModifierState<ModifierInstance>) {
return state;
}
}

const CUSTOM_MODIFIER_MANAGER = new CustomModifierManager();
43 changes: 28 additions & 15 deletions packages/@ember/-internals/glimmer/lib/resolver.ts
Expand Up @@ -2,7 +2,11 @@ import { privatize as P } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { LookupOptions, Owner, setOwner } from '@ember/-internals/owner';
import { lookupComponent, lookupPartial, OwnedTemplateMeta } from '@ember/-internals/views';
import { EMBER_MODULE_UNIFICATION, GLIMMER_CUSTOM_COMPONENT_MANAGER } from '@ember/canary-features';
import {
EMBER_MODULE_UNIFICATION,
GLIMMER_CUSTOM_COMPONENT_MANAGER,
GLIMMER_MODIFIER_MANAGER,
} from '@ember/canary-features';
import { assert } from '@ember/debug';
import { _instrumentStart } from '@ember/instrumentation';
import { DEBUG } from '@glimmer/env';
Expand Down Expand Up @@ -37,11 +41,13 @@ 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 { CustomModifierDefinition, ModifierManagerDelegate } from './modifiers/custom';
import { populateMacros } from './syntax';
import { mountHelper } from './syntax/mount';
import { outletHelper } from './syntax/outlet';
import { Factory as TemplateFactory, Injections, OwnedTemplate } from './template';
import { getComponentManager } from './utils/custom-component-manager';
import { getModifierManager } from './utils/custom-modifier-manager';
import { ClassBasedHelperReference, SimpleHelperReference } from './utils/references';

function instrumentationPayload(name: string) {
Expand Down Expand Up @@ -175,8 +181,8 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
/**
* Called by CompileTimeLookup compiling the
*/
lookupModifier(name: string, _meta: OwnedTemplateMeta): Option<number> {
return this.handle(this._lookupModifier(name));
lookupModifier(name: string, meta: OwnedTemplateMeta): Option<number> {
return this.handle(this._lookupModifier(name, meta));
}

/**
Expand Down Expand Up @@ -272,8 +278,21 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
}
}

private _lookupModifier(name: string) {
return this.builtInModifiers[name];
private _lookupModifier(name: string, meta: OwnedTemplateMeta) {
let builtin = this.builtInModifiers[name];

if (GLIMMER_MODIFIER_MANAGER && builtin === undefined) {
let { owner } = meta;
let modifier = owner.factoryFor(`modifier:${name}`);
if (modifier !== undefined) {
let managerFactory = getModifierManager<ModifierManagerDelegate<Opaque>>(modifier.class);
let manager = managerFactory!(owner);

return new CustomModifierDefinition(name, modifier, manager);
}
}

return builtin;
}

private _parseNameForNamespace(_name: string) {
Expand Down Expand Up @@ -326,20 +345,14 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
}

if (GLIMMER_CUSTOM_COMPONENT_MANAGER && component && component.class) {
let managerId = getComponentManager(component.class);
if (managerId) {
let manager = this._lookupComponentManager(meta.owner, managerId);
assert(
`Could not find custom component manager '${managerId}' which was specified by ${
component.class
}`,
!!manager
);
let managerFactory = getComponentManager<ManagerDelegate<Opaque>>(component.class);
if (managerFactory) {
let delegate = managerFactory(meta.owner);

let definition = new CustomManagerDefinition(
name,
component,
manager,
delegate,
layout || meta.owner.lookup<OwnedTemplate>(P`template:components/-default`)
);
finalizer();
Expand Down
@@ -1,27 +1,24 @@
import { Owner } from '@ember/-internals/owner';
import { GLIMMER_CUSTOM_COMPONENT_MANAGER } from '@ember/canary-features';
import { Opaque } from '@glimmer/interfaces';
import { getManager, ManagerFactory, setManager } from './managers';

const getPrototypeOf = Object.getPrototypeOf;
const MANAGERS: WeakMap<any, string> = new WeakMap();

export function setComponentManager(managerId: string, obj: any) {
MANAGERS.set(obj, managerId);

return obj;
export function setComponentManager(stringOrFunction: string | ManagerFactory<Opaque>, obj: any) {
let factory;
if (typeof stringOrFunction === 'string') {
factory = function(owner: Owner) {
return owner.lookup(`component-manager:${stringOrFunction}`);
};
} else {
factory = stringOrFunction;
}
return setManager(factory, obj);
}

export function getComponentManager(obj: any): string | undefined {
export function getComponentManager<T>(obj: any): undefined | ManagerFactory<T> {
if (!GLIMMER_CUSTOM_COMPONENT_MANAGER) {
return;
}

let pointer = obj;
while (pointer !== undefined && pointer !== null) {
if (MANAGERS.has(pointer)) {
return MANAGERS.get(pointer);
}

pointer = getPrototypeOf(pointer);
}

return;
return getManager(obj);
}
@@ -0,0 +1,15 @@
import { GLIMMER_MODIFIER_MANAGER } from '@ember/canary-features';
import { Opaque } from '@glimmer/util';
import { getManager, ManagerFactory, setManager } from './managers';

export function setModifierManager(factory: ManagerFactory<Opaque>, obj: any) {
return setManager(factory, obj);
}

export function getModifierManager<T>(obj: any): undefined | ManagerFactory<T> {
if (!GLIMMER_MODIFIER_MANAGER) {
return;
}

return getManager(obj);
}
39 changes: 39 additions & 0 deletions packages/@ember/-internals/glimmer/lib/utils/managers.ts
@@ -0,0 +1,39 @@
import { Owner } from '@ember/-internals/owner';
import { Dict, Opaque } from '@glimmer/interfaces';
import { CapturedArguments } from '@glimmer/runtime';

const MANAGERS: WeakMap<any, ManagerFactory<Opaque>> = new WeakMap();

const getPrototypeOf = Object.getPrototypeOf;

export type ManagerFactory<ManagerDelegate> = (owner: Owner) => ManagerDelegate;

export function setManager<ManagerDelegate>(factory: ManagerFactory<ManagerDelegate>, obj: any) {
MANAGERS.set(obj, factory);
return obj;
}

export function getManager<T>(obj: any): undefined | ManagerFactory<T> {
let pointer = obj;
while (pointer !== undefined && pointer !== null) {
if (MANAGERS.has(pointer)) {
return MANAGERS.get(pointer) as ManagerFactory<T>;
}

pointer = getPrototypeOf(pointer);
}

return;
}

export function valueForCapturedArgs(args: CapturedArguments): ManagerArgs {
return {
named: args.named.value(),
positional: args.positional.value(),
};
}

export interface ManagerArgs {
named: Dict<Opaque>;
positional: Opaque[];
}