Skip to content

Commit

Permalink
fix(engine): @wire() protocol reform RFC
Browse files Browse the repository at this point in the history
  • Loading branch information
caridy committed Aug 28, 2019
1 parent 8d6baab commit 6bcf0be
Show file tree
Hide file tree
Showing 30 changed files with 873 additions and 1,180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ describe('error boundary component', () => {
}
}
registerDecorators(PreErrorChildContent, {
publicProps: { foo: { config: 1 } },
publicProps: { foo: { config: 3 } },
});
const baseTmpl = compileTemplate(
`
Expand Down
31 changes: 14 additions & 17 deletions packages/@lwc/engine/src/framework/__tests__/html-element.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,10 +494,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { foo: {} },
});

registerDecorators(MyComponent, {
publicProps: { foo: { config: 3 } },
track: { state: 1 },
});

Expand Down Expand Up @@ -550,7 +547,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { role: {} },
publicProps: { role: { config: 3 } },
});
const element = createElement('prop-getter-aria-role', { is: MyComponent });
document.body.appendChild(element);
Expand Down Expand Up @@ -588,7 +585,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { lang: {} },
publicProps: { lang: { config: 3 } },
});

const element = createElement('prop-setter-lang', { is: MyComponent });
Expand Down Expand Up @@ -634,7 +631,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { lang: {} },
publicProps: { lang: { config: 1 } },
});

const element = createElement('prop-getter-lang-imperative', { is: MyComponent });
Expand Down Expand Up @@ -708,7 +705,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { hidden: {} },
publicProps: { hidden: { config: 3 } },
});

const element = createElement('prop-setter-hidden', { is: MyComponent });
Expand Down Expand Up @@ -753,7 +750,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { hidden: {} },
publicProps: { hidden: { config: 1 } },
});

const element = createElement('prop-getter-hidden-imperative', { is: MyComponent });
Expand Down Expand Up @@ -831,7 +828,7 @@ describe('html-element', () => {
}

registerDecorators(MyComponent, {
publicProps: { dir: {} },
publicProps: { dir: { config: 3 } },
});

const element = createElement('prop-setter-dir', { is: MyComponent });
Expand Down Expand Up @@ -877,7 +874,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { dir: {} },
publicProps: { dir: { config: 1 } },
});

const element = createElement('prop-getter-dir-imperative', { is: MyComponent });
Expand Down Expand Up @@ -953,7 +950,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { id: {} },
publicProps: { id: { config: 3 } },
});

const element = createElement('prop-setter-id', { is: MyComponent });
Expand Down Expand Up @@ -999,7 +996,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { id: {} },
publicProps: { id: { config: 1 } },
});

const element = createElement('prop-getter-id-imperative', { is: MyComponent });
Expand Down Expand Up @@ -1077,7 +1074,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { accessKey: {} },
publicProps: { accessKey: { config: 3 } },
});

const element = createElement('prop-setter-accessKey', { is: MyComponent });
Expand Down Expand Up @@ -1124,7 +1121,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { accessKey: {} },
publicProps: { accessKey: { config: 1 } },
});
const element = createElement('prop-getter-accessKey-imperative', {
is: MyComponent,
Expand Down Expand Up @@ -1201,7 +1198,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { title: {} },
publicProps: { title: { config: 3 } },
});
const element = createElement('prop-setter-title', { is: MyComponent });
(element.title = {}), expect(count).toBe(1);
Expand Down Expand Up @@ -1244,7 +1241,7 @@ describe('html-element', () => {
}
}
registerDecorators(MyComponent, {
publicProps: { title: {} },
publicProps: { title: { config: 1 } },
});

const element = createElement('prop-getter-title-imperative', { is: MyComponent });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export interface LightningElementConstructor {

export declare var LightningElement: LightningElementConstructor;

export interface LightningElement {
export interface LightningElement extends EventTarget {
// DOM - The good parts
dispatchEvent(event: Event): boolean;
addEventListener(
Expand Down
18 changes: 6 additions & 12 deletions packages/@lwc/engine/src/framework/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import {
invokeEventListener,
} from './invoker';
import { isArray, isFunction, isUndefined, StringToLowerCase, isFalse } from '../shared/language';
import { invokeServiceHook, Services } from './services';
import { VM, getComponentVM, UninitializedVM, scheduleRehydration } from './vm';
import { VNodes } from '../3rdparty/snabbdom/types';
import { tagNameGetter } from '../env/element';
import { Template } from './template';
import { ReactiveObserver } from '../libs/mutation-tracker';
import { LightningElementConstructor } from './base-lightning-element';
import { LightningElementConstructor, LightningElement } from './base-lightning-element';
import { installWireAdapters } from './wiring';

export type ErrorCallback = (error: any, stack: string) => void;
export interface ComponentInterface {
export interface ComponentInterface extends LightningElement {
// TODO: #1291 - complete the entire interface used by the engine
setAttribute(attrName: string, value: any): void;
}
Expand Down Expand Up @@ -81,15 +81,9 @@ export function linkComponent(vm: VM) {
if (process.env.NODE_ENV !== 'production') {
assert.isTrue(vm && 'cmpRoot' in vm, `${vm} is not a vm.`);
}
// wiring service
const {
def: { wire },
} = vm;
if (wire) {
const { wiring } = Services;
if (wiring) {
invokeServiceHook(vm, wiring);
}
// initializing the wire decorator per instance only when really needed
if (vm.def.wire.length > 0) {
installWireAdapters(vm);
}
}

Expand Down
56 changes: 56 additions & 0 deletions packages/@lwc/engine/src/framework/context-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2018, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { isUndefined, ArrayPush, forEach } from '../shared/language';
import { guid } from './utils';
import { WireAdapterConstructor, ContextValue, getAdapterToken, setAdapterToken } from './wiring';

type WireContextDisconnectCallback = () => void;
type WireContextInternalProtocolCallback = (
newContext: ContextValue,
disconnectCallback: WireContextDisconnectCallback
) => void;
interface ContextConsumer {
provide(newContext: ContextValue): void;
}

interface WireContextEvent extends CustomEvent {
detail: WireContextInternalProtocolCallback;
}

// this is lwc internal implementation
export function createContextProvider(adapter: WireAdapterConstructor) {
let adapterContextToken = getAdapterToken(adapter);
if (!isUndefined(adapterContextToken)) {
throw new Error(`Adapter already have a context provider.`);
}
adapterContextToken = guid();
setAdapterToken(adapter, adapterContextToken);
return (elm: EventTarget) => {
const connectQueue = [];
const disconnectQueue = [];
elm.addEventListener(adapterContextToken as string, (evt: WireContextEvent) => {
const { detail } = evt;
const consumer: ContextConsumer = {
provide(newContext) {
detail(newContext, disconnectCallback);
},
};
const disconnectCallback = () => {
forEach.call(disconnectQueue, callback => callback(consumer));
};
forEach.call(connectQueue, callback => callback(consumer));
});
return {
onConsumerConnected(callback: (consumer: ContextConsumer) => void) {
ArrayPush.call(connectQueue, callback);
},
onConsumerDisconnected(callback: (consumer: ContextConsumer) => void) {
ArrayPush.call(disconnectQueue, callback);
},
};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('wire.ts', () => {
});
});

it('should make properties of a wired object property reactive', () => {
it('should make wired properties as readonly', () => {
let counter = 0;
class MyComponent extends LightningElement {
injectFooDotX(x) {
Expand All @@ -108,10 +108,11 @@ describe('wire.ts', () => {

const elm = createElement('x-foo', { is: MyComponent });
document.body.appendChild(elm);
elm.injectFooDotX(2);

expect(() => {
elm.injectFooDotX(2);
}).toThrowError();
return Promise.resolve().then(() => {
expect(counter).toBe(2);
expect(counter).toBe(1);
});
});

Expand Down
68 changes: 12 additions & 56 deletions packages/@lwc/engine/src/framework/decorators/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,27 @@
*/
import assert from '../../shared/assert';
import { isRendering, vmBeingRendered, isBeingConstructed } from '../invoker';
import { isObject, toString, isFalse } from '../../shared/language';
import { toString, isFalse } from '../../shared/language';
import { valueObserved, valueMutated } from '../../libs/mutation-tracker';
import { ComponentInterface, ComponentConstructor } from '../component';
import { ComponentInterface } from '../component';
import { getComponentVM } from '../vm';
import { isUndefined, isFunction } from '../../shared/language';
import { getDecoratorsRegisteredMeta } from './register';
import { isFunction } from '../../shared/language';

/**
* @api decorator to mark public fields and public methods in
* LWC Components. This function implements the internals of this
* decorator.
*/
export default function api(
target: ComponentConstructor,
propName: PropertyKey,
descriptor: PropertyDescriptor | undefined
): PropertyDescriptor {
// TODO: how to make api a decoratorFunction type as well?
export default function api() {
if (process.env.NODE_ENV !== 'production') {
if (arguments.length !== 3) {
if (arguments.length !== 0) {
assert.fail(`@api decorator can only be used as a decorator function.`);
}
}
if (process.env.NODE_ENV !== 'production') {
assert.invariant(
!descriptor || (isFunction(descriptor.get) || isFunction(descriptor.set)),
`Invalid property ${toString(
propName
)} definition in ${target}, it cannot be a prototype definition if it is a public property. Instead use the constructor to define it.`
);
if (isObject(descriptor) && isFunction(descriptor.set)) {
assert.isTrue(
isObject(descriptor) && isFunction(descriptor.get),
`Missing getter for property ${toString(
propName
)} decorated with @api in ${target}. You cannot have a setter without the corresponding getter.`
);
}
}
const meta = getDecoratorsRegisteredMeta(target);
// initializing getters and setters for each public prop on the target prototype
if (isObject(descriptor) && (isFunction(descriptor.get) || isFunction(descriptor.set))) {
// if it is configured as an accessor it must have a descriptor
// @ts-ignore it must always be set before calling this method
meta.props[propName].config = isFunction(descriptor.set) ? 3 : 1;
return createPublicAccessorDescriptor(target, propName, descriptor);
} else {
// @ts-ignore it must always be set before calling this method
meta.props[propName].config = 0;
return createPublicPropertyDescriptor(target, propName, descriptor);
}
}

function createPublicPropertyDescriptor(
proto: ComponentConstructor,
key: PropertyKey,
descriptor: PropertyDescriptor | undefined
): PropertyDescriptor {
export function createPublicPropertyDescriptor(key: string): PropertyDescriptor {
return {
get(this: ComponentInterface): any {
const vm = getComponentVM(this);
Expand Down Expand Up @@ -103,26 +67,17 @@ function createPublicPropertyDescriptor(
valueMutated(this, key);
}
},
enumerable: isUndefined(descriptor) ? true : descriptor.enumerable,
enumerable: true,
configurable: true,
};
}

function createPublicAccessorDescriptor(
Ctor: ComponentConstructor,
export function createPublicAccessorDescriptor(
key: PropertyKey,
descriptor: PropertyDescriptor
): PropertyDescriptor {
const { get, set, enumerable } = descriptor;
const { get, set, enumerable, configurable } = descriptor;
if (!isFunction(get)) {
if (process.env.NODE_ENV !== 'production') {
assert.fail(
`Invalid attempt to create public property descriptor ${toString(
key
)} in ${Ctor}. It is missing the getter declaration with @api get ${toString(
key
)}() {} syntax.`
);
}
throw new TypeError();
}
return {
Expand Down Expand Up @@ -155,5 +110,6 @@ function createPublicAccessorDescriptor(
}
},
enumerable,
configurable,
};
}
Loading

0 comments on commit 6bcf0be

Please sign in to comment.