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

feat(engine): @lwc/node-reactions #1747

Closed
wants to merge 10 commits into from
3 changes: 2 additions & 1 deletion packages/@lwc/engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"types/"
],
"dependencies": {
"observable-membrane": "0.26.1"
"observable-membrane": "0.26.1",
"@lwc/node-reactions": "1.3.5"
},
"devDependencies": {
"@lwc/features": "1.3.5",
Expand Down
9 changes: 9 additions & 0 deletions packages/@lwc/engine/scripts/jest/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ function compileTemplate(source, config = {}) {
return registerTemplate(templateFactory(modules));
}

/**
* Strip out data markers added by @lwc/node-reactions
* @param {string} str
*/
function stripNodeReactionsMarker(str) {
return str.replace(/ data-node-reactions(="")*/gm, '');
}

module.exports = {
compileTemplate,
stripNodeReactionsMarker,
};
4 changes: 0 additions & 4 deletions packages/@lwc/engine/src/3rdparty/snabbdom/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,11 @@ export interface VElement extends VNode {
elm: Element | undefined;
text: undefined;
key: Key;
// TODO [#1364]: support the ability to provision a cloned StyleElement
// for native shadow as a perf optimization
clonedElement?: HTMLStyleElement;
}

export interface VCustomElement extends VElement {
mode: 'closed' | 'open';
ctor: any;
clonedElement?: undefined;
}

export interface VComment extends VNode {
Expand Down
8 changes: 1 addition & 7 deletions packages/@lwc/engine/src/env/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { getOwnPropertyDescriptor } from '@lwc/shared';

const ShadowRootInnerHTMLSetter: (this: ShadowRoot, s: string) => void = getOwnPropertyDescriptor(
ShadowRoot.prototype,
'innerHTML'
)!.set!;

const dispatchEvent =
'EventTarget' in window ? EventTarget.prototype.dispatchEvent : Node.prototype.dispatchEvent; // IE11

export { dispatchEvent, ShadowRootInnerHTMLSetter };
export { dispatchEvent };
8 changes: 4 additions & 4 deletions packages/@lwc/engine/src/framework/__tests__/vm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { compileTemplate } from 'test-utils';
import { compileTemplate, stripNodeReactionsMarker } from 'test-utils';
import { createElement, LightningElement, registerDecorators } from '../main';
import { getAssociatedVM } from '../vm';

Expand Down Expand Up @@ -197,9 +197,9 @@ describe('vm', () => {
// at this point, if we are reusing the h1 and h2 from the default content
// of the slots in c-child, they will have an extraneous attribute on them,
// which will be a problem.
expect(parentTemplate.querySelector('c-child').outerHTML).toBe(
`<c-child><h1 slot="">slotted</h1><h2 slot="foo"></h2></c-child>`
);
expect(
stripNodeReactionsMarker(parentTemplate.querySelector('c-child').outerHTML)
).toBe(`<c-child><h1 slot="">slotted</h1><h2 slot="foo"></h2></c-child>`);
});
});
});
Expand Down
48 changes: 9 additions & 39 deletions packages/@lwc/engine/src/framework/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
EmptyObject,
useSyntheticShadow,
} from './utils';
import { getAssociatedVM, runConnectedCallback, SlotSet, VM, VMState } from './vm';
import { SlotSet, VM } from './vm';
import { ComponentConstructor } from './component';
import {
VNode,
Expand All @@ -47,10 +47,8 @@ import {
} from '../3rdparty/snabbdom/types';
import {
createViewModelHook,
insertCustomElmHook,
fallbackElmHook,
rerenderCustomElmHook,
removeElmHook,
createChildrenHook,
updateNodeHook,
insertNodeHook,
Expand All @@ -61,7 +59,6 @@ import {
updateCustomElmHook,
updateChildrenHook,
allocateChildrenHook,
removeCustomElmHook,
markAsDynamicChildren,
} from './hooks';
import { Services, invokeServiceHook } from './services';
Expand Down Expand Up @@ -137,17 +134,11 @@ const CommentHook: Hooks = {
// Custom Element that is inserted via a template.
const ElementHook: Hooks = {
create: (vnode: VElement) => {
const { data, sel, clonedElement } = vnode;
const { data, sel } = vnode;
const { ns } = data;
// TODO [#1364]: supporting the ability to inject a cloned StyleElement via a vnode this is
// used for style tags for native shadow
if (isUndefined(clonedElement)) {
vnode.elm = isUndefined(ns)
? document.createElement(sel)
: document.createElementNS(ns, sel);
} else {
vnode.elm = clonedElement;
}
vnode.elm = isUndefined(ns)
? document.createElement(sel)
: document.createElementNS(ns, sel);
linkNodeToShadow(vnode);
if (process.env.NODE_ENV !== 'production') {
markNodeFromVNode(vnode.elm);
Expand All @@ -163,13 +154,8 @@ const ElementHook: Hooks = {
insertNodeHook(vnode, parentNode, referenceNode);
createChildrenHook(vnode);
},
move: (vnode: VElement, parentNode: Node, referenceNode: Node | null) => {
insertNodeHook(vnode, parentNode, referenceNode);
},
remove: (vnode: VElement, parentNode: Node) => {
removeNodeHook(vnode, parentNode);
removeElmHook(vnode);
},
move: insertNodeHook,
remove: removeNodeHook,
};

const CustomElementHook: Hooks = {
Expand Down Expand Up @@ -197,21 +183,10 @@ const CustomElementHook: Hooks = {
},
insert: (vnode: VCustomElement, parentNode: Node, referenceNode: Node | null) => {
insertNodeHook(vnode, parentNode, referenceNode);
const vm = getAssociatedVM(vnode.elm!);
if (process.env.NODE_ENV !== 'production') {
assert.isTrue(vm.state === VMState.created, `${vm} cannot be recycled.`);
}
runConnectedCallback(vm);
createChildrenHook(vnode);
insertCustomElmHook(vnode);
},
move: (vnode: VCustomElement, parentNode: Node, referenceNode: Node | null) => {
insertNodeHook(vnode, parentNode, referenceNode);
},
remove: (vnode: VCustomElement, parentNode: Node) => {
removeNodeHook(vnode, parentNode);
removeCustomElmHook(vnode);
},
move: insertNodeHook,
remove: removeNodeHook,
};

function linkNodeToShadow(vnode: VNode) {
Expand All @@ -234,10 +209,6 @@ function addNS(vnode: VElement) {
}
}

function addVNodeToChildLWC(vnode: VCustomElement) {
ArrayPush.call(getVMBeingRendered()!.velements, vnode);
}

// [h]tml node
export function h(sel: string, data: ElementCompilerData, children: VNodes): VElement {
const vmBeingRendered = getVMBeingRendered()!;
Expand Down Expand Up @@ -415,7 +386,6 @@ export function c(
owner: vmBeingRendered,
mode: 'open', // TODO [#1294]: this should be defined in Ctor
};
addVNodeToChildLWC(vnode);
return vnode;
}

Expand Down
56 changes: 28 additions & 28 deletions packages/@lwc/engine/src/framework/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { assert, isArray, isNull, isTrue, isUndefined } from '@lwc/shared';
import { assert, isArray, isTrue, isUndefined } from '@lwc/shared';
import { reactWhenConnected, reactWhenDisconnected } from '@lwc/node-reactions';
import { EmptyArray, EmptyObject, useSyntheticShadow } from './utils';
import {
rerenderVM,
Expand All @@ -15,6 +16,7 @@ import {
appendVM,
runWithBoundaryProtection,
getAssociatedVMIfPresent,
VMState,
} from './vm';
import { VNode, VCustomElement, VElement, VNodes } from '../3rdparty/snabbdom/types';
import modEvents from './modules/events';
Expand Down Expand Up @@ -140,11 +142,6 @@ export function updateElmHook(oldVnode: VElement, vnode: VElement) {
modComputedStyle.update(oldVnode, vnode);
}

export function insertCustomElmHook(vnode: VCustomElement) {
const vm = getAssociatedVM(vnode.elm!);
appendVM(vm);
}

export function updateChildrenHook(oldVnode: VElement, vnode: VElement) {
const { children, owner } = vnode;
const fn = hasDynamicChildren(children) ? updateDynamicChildren : updateStaticChildren;
Expand All @@ -162,7 +159,6 @@ export function updateChildrenHook(oldVnode: VElement, vnode: VElement) {
export function allocateChildrenHook(vnode: VCustomElement) {
const vm = getAssociatedVM(vnode.elm!);
const { children } = vnode;
vm.aChildren = children;
if (isTrue(useSyntheticShadow)) {
// slow path
allocateInSlot(vm, children);
Expand All @@ -171,6 +167,26 @@ export function allocateChildrenHook(vnode: VCustomElement) {
}
}

function customElementConnectedHook(elm: HTMLElement) {
const vm = getAssociatedVM(elm);
if (process.env.NODE_ENV !== 'production') {
// Either the vm was just created or the node is being moved to another subtree
assert.isTrue(
vm.state === VMState.created || vm.state === VMState.disconnected,
`${vm} cannot be connected.`
);
}
appendVM(vm);
}

function customElementDisconnectedHook(elm: HTMLElement) {
const vm = getAssociatedVM(elm);
if (process.env.NODE_ENV !== 'production') {
assert.isTrue(vm.state === VMState.connected, `${vm} should be connected.`);
}
removeVM(vm);
}

export function createViewModelHook(vnode: VCustomElement) {
const elm = vnode.elm as HTMLElement;
if (!isUndefined(getAssociatedVMIfPresent(elm))) {
Expand All @@ -192,13 +208,16 @@ export function createViewModelHook(vnode: VCustomElement) {
mode,
owner,
});
reactWhenConnected(elm, customElementConnectedHook);
reactWhenDisconnected(elm, customElementDisconnectedHook);
if (process.env.NODE_ENV !== 'production') {
const vm = getAssociatedVM(elm);
assert.isTrue(vm && 'cmpRoot' in vm, `${vm} is not a vm.`);
assert.isTrue(
isArray(vnode.children),
`Invalid vnode for a custom element, it must have children defined.`
);
}
if (process.env.NODE_ENV !== 'production') {

patchCustomElementWithRestrictions(elm, EmptyObject);
}
}
Expand Down Expand Up @@ -249,25 +268,6 @@ export function updateCustomElmHook(oldVnode: VCustomElement, vnode: VCustomElem
modComputedStyle.update(oldVnode, vnode);
}

export function removeElmHook(vnode: VElement) {
// this method only needs to search on child vnodes from template
// to trigger the remove hook just in case some of those children
// are custom elements.
const { children, elm } = vnode;
for (let j = 0, len = children.length; j < len; ++j) {
const ch = children[j];
if (!isNull(ch)) {
ch.hook.remove(ch, elm!);
}
}
}

export function removeCustomElmHook(vnode: VCustomElement) {
// for custom elements we don't have to go recursively because the removeVM routine
// will take care of disconnecting any child VM attached to its shadow as well.
removeVM(getAssociatedVM(vnode.elm!));
}

// Using a WeakMap instead of a WeakSet because this one works in IE11 :(
const FromIteration: WeakMap<VNodes, 1> = new WeakMap();

Expand Down
Loading