Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

[FEAT] Modifier Managers #245

Merged
merged 1 commit into from
Mar 27, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ module.exports = {
},
// source Js
{
files: ['src/**/*.js'],
files: ['**/src/**/*.js', '**/test/**/*.js'],
env: {
es2020: true,
browser: true,
},
parserOptions: {
sourceType: 'module',
},
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"**/bower_components": true,
"dist": true
},
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"eslint.enable": true
}
7 changes: 7 additions & 0 deletions packages/@glimmer/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export {

export { Args as CapturedArgs } from './src/interfaces';

export {
ModifierManager,
ModifierDefinition,
capabilities as modifierCapabilities,
Capabilities as ModifierCapabilities,
} from './src/managers/modifier';
Comment on lines +18 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for not using export * 👯‍♂


export {
HelperManager,
HelperDefinition,
Expand Down
4 changes: 2 additions & 2 deletions packages/@glimmer/core/src/managers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function getManagerInstanceForOwner<D extends ManagerDelegate>(
///////////

export function setModifierManager<StateBucket>(
factory: ManagerFactory<ModifierManager<unknown>>,
factory: ManagerFactory<ModifierManager<StateBucket>>,
definition: ModifierDefinition<StateBucket>
): {} {
return setManager({ factory, type: 'modifier' }, definition);
Expand All @@ -102,7 +102,7 @@ export function setModifierManager<StateBucket>(
export function getModifierManager<StateBucket = unknown>(
owner: object,
definition: ModifierDefinition<StateBucket>
): ModifierManager<unknown> | undefined {
): ModifierManager<StateBucket> | undefined {
const wrapper = getManager(definition);

if (wrapper !== undefined && wrapper.type === 'modifier') {
Expand Down
168 changes: 161 additions & 7 deletions packages/@glimmer/core/src/managers/modifier.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { assert } from '@glimmer/util';
import { DEBUG } from '@glimmer/env';
import {
ModifierManager as VMModifierManager,
VMArguments,
CapturedArguments,
Destroyable,
DynamicScope,
} from '@glimmer/interfaces';
import { Tag, createUpdatableTag, track, untrack, combine, update } from '@glimmer/validator';
import { assert, debugToString } from '@glimmer/util';
import { SimpleElement } from '@simple-dom/interface';
import { Args } from '../interfaces';
import debugRenderMessage from '../utils/debug';
import { argsProxyFor } from './util';
import { getModifierManager } from '.';
import { OWNER_KEY } from '../owner';
import { VMModifierDefinitionWithHandle } from '../render-component/vm-definitions';

///////////

Expand All @@ -10,7 +24,7 @@ export interface Capabilities {

export type OptionalCapabilities = Partial<Capabilities>;

export type ManagerAPIVersion = '3.4' | '3.13';
export type ManagerAPIVersion = '3.13';

export function capabilities(
managerAPI: ManagerAPIVersion,
Expand All @@ -25,12 +39,152 @@ export function capabilities(

///////////

export interface ModifierManager<ModifierInstance> {
export interface ModifierManager<ModifierStateBucket> {
capabilities: Capabilities;
createModifier(definition: unknown, args: Args): ModifierInstance;
installModifier(instance: ModifierInstance, element: SimpleElement, args: Args): void;
updateModifier(instance: ModifierInstance, args: Args): void;
destroyModifier(instance: ModifierInstance, args: Args): void;
createModifier(definition: unknown, args: Args): ModifierStateBucket;
installModifier(instance: ModifierStateBucket, element: Element, args: Args): void;
updateModifier(instance: ModifierStateBucket, args: Args): void;
destroyModifier(instance: ModifierStateBucket, args: Args): void;
}

export type ModifierDefinition<_Instance = unknown> = {};

export type SimpleModifier = (element: Element, ...args: unknown[]) => undefined | (() => void);

interface SimpleModifierStateBucket {
definition: SimpleModifier;
destructor?(): void;
element?: Element;
}

class SimpleModifierManager implements ModifierManager<SimpleModifierStateBucket> {
capabilities = capabilities('3.13');

createModifier(definition: SimpleModifier, args: Args): SimpleModifierStateBucket {
if (DEBUG) {
assert(Object.keys(args.named).length === 0, `You used named arguments with the ${definition.name} modifier, but it is a standard function. Normal functions cannot receive named arguments when used as modifiers.`);
}

return { definition };
}

installModifier(bucket: SimpleModifierStateBucket, element: Element, args: Args): void {
bucket.destructor = bucket.definition(element, ...args.positional);
bucket.element = element;
}

updateModifier(bucket: SimpleModifierStateBucket, args: Args): void {
this.destroyModifier(bucket);
this.installModifier(bucket, bucket.element!, args);
}

destroyModifier(bucket: SimpleModifierStateBucket): void {
const { destructor } = bucket;

if (destructor !== undefined) {
destructor();
}
}
}

const SIMPLE_MODIFIER_MANAGER = new SimpleModifierManager();

///////////

export class CustomModifierState<ModifierStateBucket> {
public tag = createUpdatableTag();

constructor(
public element: SimpleElement,
public delegate: ModifierManager<ModifierStateBucket>,
public modifier: ModifierStateBucket,
public argsProxy: Args,
public capturedArgs: CapturedArguments
) {}

destroy(): void {
const { delegate, modifier, argsProxy } = this;
delegate.destroyModifier(modifier, argsProxy);
}
}

export class CustomModifierManager<ModifierStateBucket>
implements
VMModifierManager<
CustomModifierState<ModifierStateBucket>,
ModifierDefinition<ModifierStateBucket>
> {
create(
element: SimpleElement,
definition: ModifierDefinition<ModifierStateBucket>,
args: VMArguments,
dynamicScope: DynamicScope
): CustomModifierState<ModifierStateBucket> {
const owner = dynamicScope.get(OWNER_KEY).value() as object;
let delegate = getModifierManager(owner, definition);

if (delegate === undefined) {
if (DEBUG) {
assert(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, do these get stripped properly on their own or do they have to be within an if (DEBUG) {? Also, if they do have to be in an if (DEBUG) { what happens to the import?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the import should be tree-shaken, which is why I figured this pattern would be alright for now. We currently only replace DEBUG.

typeof definition === 'function',
`No modifier manager found for ${definition}, and it was not a plain function, so it could not be used as a modifier`
);
}

delegate = (SIMPLE_MODIFIER_MANAGER as unknown) as ModifierManager<ModifierStateBucket>;
}

const capturedArgs = args.capture();
const argsProxy = argsProxyFor(capturedArgs, 'modifier');

const instance = delegate.createModifier(definition, argsProxy);

return new CustomModifierState(element, delegate, instance, argsProxy, capturedArgs);
}

getTag({ tag, capturedArgs }: CustomModifierState<ModifierStateBucket>): Tag {
return combine([tag, capturedArgs.tag]);
}

install(state: CustomModifierState<ModifierStateBucket>): void {
const { element, argsProxy, delegate, modifier, tag } = state;

if (delegate.capabilities.disableAutoTracking === true) {
untrack(() => delegate.installModifier(modifier, element as Element, argsProxy));
} else {
const combinedTrackingTag = track(
() => delegate.installModifier(modifier, element as Element, argsProxy),
DEBUG && debugRenderMessage!(`(instance of a \`${debugToString!(modifier)}\` modifier)`)
);

update(tag, combinedTrackingTag);
}
}

update(state: CustomModifierState<ModifierStateBucket>): void {
const { argsProxy, delegate, modifier, tag } = state;

if (delegate.capabilities.disableAutoTracking === true) {
untrack(() => delegate.updateModifier(modifier, argsProxy));
} else {
const combinedTrackingTag = track(
() => delegate.updateModifier(modifier, argsProxy),
DEBUG && debugRenderMessage!(`(instance of a \`${debugToString!(modifier)}\` modifier)`)
);
update(tag, combinedTrackingTag);
}
}

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

export const CUSTOM_MODIFIER_MANAGER = new CustomModifierManager();

export class VMCustomModifierDefinition<ModifierStateBucket>
implements VMModifierDefinitionWithHandle {
public manager = CUSTOM_MODIFIER_MANAGER;

constructor(public handle: number, public state: ModifierDefinition<ModifierStateBucket>) {}
}
4 changes: 3 additions & 1 deletion packages/@glimmer/core/src/render-component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ const context = JitContext(new CompileTimeResolver(resolver));
const program = new RuntimeProgramImpl(context.program.constants, context.program.heap);

function dictToReference(dict: Dict<unknown>, env: Environment): Dict<PathReference> {
const root = new ComponentRootReference(dict, env);

return Object.keys(dict).reduce((acc, key) => {
acc[key] = new ComponentRootReference(dict[key], env);
acc[key] = root.get(key);
return acc;
}, {} as Dict<PathReference>);
}
Expand Down
13 changes: 8 additions & 5 deletions packages/@glimmer/core/src/render-component/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import { ResolverDelegate, unwrapTemplate } from '@glimmer/opcode-compiler';
import {
vmDefinitionForComponent,
vmDefinitionForHelper,
vmDefinitionForModifier,
vmDefinitionForBuiltInHelper,
Modifier,
vmHandleForModifier,
VMHelperDefinition,
} from './vm-definitions';

Expand All @@ -22,6 +21,7 @@ import { TemplateMeta } from '../template';
import { HelperDefinition } from '../managers/helper';
import { ifHelper } from './built-ins';
import { DEBUG } from '@glimmer/env';
import { ModifierDefinition } from '../managers/modifier';

const builtInHelpers: { [key: string]: VMHelperDefinition } = {
if: vmDefinitionForBuiltInHelper(ifHelper),
Expand Down Expand Up @@ -92,10 +92,13 @@ export class CompileTimeResolver implements ResolverDelegate {

lookupModifier(name: string, referrer: TemplateMeta): Option<number> {
const scope = referrer.scope();
const modifier = scope[name] as Modifier;
const modifier = scope[name] as ModifierDefinition;

const definition = vmDefinitionForModifier(modifier);
const { handle } = definition;

this.inner.registry[handle] = definition;

const handle = vmHandleForModifier(modifier);
this.inner.registry[handle] = modifier;
return handle;
}

Expand Down
30 changes: 20 additions & 10 deletions packages/@glimmer/core/src/render-component/vm-definitions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ComponentDefinition as VMComponentDefinition,
ModifierDefinition as VMModifierDefinition,
Helper as VMHelperFactory,
ModifierManager,
TemplateOk,
Expand All @@ -14,12 +15,18 @@ import {
TemplateOnlyComponent,
} from '../managers/component/template-only';
import { DEBUG } from '@glimmer/env';
import { ModifierDefinition, VMCustomModifierDefinition } from '../managers/modifier';

export interface VMComponentDefinitionWithHandle extends VMComponentDefinition {
handle: number;
template: TemplateOk<TemplateMeta>;
}

export interface VMModifierDefinitionWithHandle extends VMModifierDefinition {
handle: number;
}


export interface VMHelperDefinition {
helper: VMHelperFactory;
handle: number;
Expand All @@ -36,7 +43,7 @@ let HANDLE = 0;

const VM_COMPONENT_DEFINITIONS = new WeakMap<ComponentDefinition, VMComponentDefinitionWithHandle>();
const VM_HELPER_DEFINITIONS = new WeakMap<HelperDefinition, VMHelperDefinition>();
const VM_MODIFIER_HANDLES = new WeakMap<Modifier, number>();
const VM_MODIFIER_DEFINITIONS = new WeakMap<ModifierDefinition, VMModifierDefinitionWithHandle>();

export function vmDefinitionForComponent(
ComponentDefinition: ComponentDefinition
Expand All @@ -48,15 +55,8 @@ export function vmDefinitionForHelper(Helper: HelperDefinition): VMHelperDefinit
return VM_HELPER_DEFINITIONS.get(Helper) || createVMHelperDefinition(Helper);
}

export function vmHandleForModifier(modifier: Modifier): number {
let handle = VM_MODIFIER_HANDLES.get(modifier);

if (!handle) {
handle = HANDLE++;
VM_MODIFIER_HANDLES.set(modifier, handle);
}

return handle;
export function vmDefinitionForModifier(Modifier: ModifierDefinition): VMModifierDefinitionWithHandle {
return VM_MODIFIER_DEFINITIONS.get(Modifier) || createVMModifierDefinition(Modifier);
}

///////////
Expand Down Expand Up @@ -115,3 +115,13 @@ function createVMHelperDefinition(userDefinition: HelperDefinition): VMHelperDef
VM_HELPER_DEFINITIONS.set(userDefinition, definition);
return definition;
}

function createVMModifierDefinition(
Modifier: ModifierDefinition
): VMModifierDefinitionWithHandle {
const definition = new VMCustomModifierDefinition(HANDLE++, Modifier);

VM_MODIFIER_DEFINITIONS.set(Modifier, definition);

return definition;
}
11 changes: 11 additions & 0 deletions packages/@glimmer/core/src/utils/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DEBUG } from '@glimmer/env';

let debugRenderMessage: undefined | ((renderingStack: string) => string);

if (DEBUG) {
debugRenderMessage = (renderingStack: string): string => {
return `While rendering:\n----------------\n${renderingStack.replace(/^/gm, ' ')}`;
};
}

export default debugRenderMessage;
Loading