Skip to content

Commit

Permalink
refactor(engine-core): encapsulate renderer as an object and allow it…
Browse files Browse the repository at this point in the history
… to be injectable in vnodes (#2763)

* refactor(engine-core): passing the renderer from an import statement in compiled templates

Co-authored-by: Ravi Jayaramappa <ravi.jayaramappa@salesforce.com>
Co-authored-by: Pierre-Marie Dartus <p.dartus@salesforce.com>
Co-authored-by: Nolan Lawson <nolan@nolanlawson.com>
  • Loading branch information
4 people authored Jun 3, 2022
1 parent ec3a14f commit da49079
Show file tree
Hide file tree
Showing 28 changed files with 671 additions and 1,006 deletions.
217 changes: 117 additions & 100 deletions packages/@lwc/engine-core/src/framework/base-lightning-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,6 @@ import {

import { logError } from '../shared/logger';
import { getComponentTag } from '../shared/format';
import {
getChildren,
getChildNodes,
getFirstChild,
getFirstElementChild,
getLastChild,
getLastElementChild,
assertInstanceOfHTMLElement,
attachShadow,
addEventListener,
removeEventListener,
getAttribute,
removeAttribute,
setAttribute,
getBoundingClientRect,
isConnected,
getClassList,
dispatchEvent,
getElementsByClassName,
getElementsByTagName,
querySelector,
querySelectorAll,
} from '../renderer';

import { HTMLElementOriginalDescriptors } from './html-properties';
import { getWrappedComponentsListener } from './component';
Expand Down Expand Up @@ -220,6 +197,7 @@ export const LightningElement: LightningElementConstructor = function (
const { bridge } = def;

if (process.env.NODE_ENV !== 'production') {
const { assertInstanceOfHTMLElement } = vm.renderer;
assertInstanceOfHTMLElement(
vm.elm,
`Component creation requires a DOM element to be associated to ${vm}.`
Expand Down Expand Up @@ -270,6 +248,7 @@ function doAttachShadow(vm: VM): ShadowRoot {
mode,
shadowMode,
def: { ctor },
renderer: { attachShadow },
} = vm;

const shadowRoot = attachShadow(elm, {
Expand Down Expand Up @@ -303,7 +282,11 @@ LightningElement.prototype = {
constructor: LightningElement,

dispatchEvent(event: Event): boolean {
const { elm } = getAssociatedVM(this);
const vm = getAssociatedVM(this);
const {
elm,
renderer: { dispatchEvent },
} = vm;
return dispatchEvent(elm, event);
},

Expand All @@ -313,7 +296,10 @@ LightningElement.prototype = {
options?: boolean | AddEventListenerOptions
): void {
const vm = getAssociatedVM(this);
const { elm } = vm;
const {
elm,
renderer: { addEventListener },
} = vm;

if (process.env.NODE_ENV !== 'production') {
const vmBeingRendered = getVMBeingRendered();
Expand Down Expand Up @@ -341,51 +327,74 @@ LightningElement.prototype = {
options?: boolean | AddEventListenerOptions
): void {
const vm = getAssociatedVM(this);
const { elm } = vm;
const {
elm,
renderer: { removeEventListener },
} = vm;

const wrappedListener = getWrappedComponentsListener(vm, listener);
removeEventListener(elm, type, wrappedListener, options);
},

hasAttribute(name: string): boolean {
const { elm } = getAssociatedVM(this);
const vm = getAssociatedVM(this);
const {
elm,
renderer: { getAttribute },
} = vm;
return !isNull(getAttribute(elm, name));
},

hasAttributeNS(namespace: string | null, name: string): boolean {
const { elm } = getAssociatedVM(this);
const vm = getAssociatedVM(this);
const {
elm,
renderer: { getAttribute },
} = vm;
return !isNull(getAttribute(elm, name, namespace));
},

removeAttribute(name: string): void {
const { elm } = getAssociatedVM(this);

const vm = getAssociatedVM(this);
const {
elm,
renderer: { removeAttribute },
} = vm;
unlockAttribute(elm, name);
removeAttribute(elm, name);
lockAttribute(elm, name);
},

removeAttributeNS(namespace: string | null, name: string): void {
const { elm } = getAssociatedVM(this);

const {
elm,
renderer: { removeAttribute },
} = getAssociatedVM(this);
unlockAttribute(elm, name);
removeAttribute(elm, name, namespace);
lockAttribute(elm, name);
},

getAttribute(name: string): string | null {
const { elm } = getAssociatedVM(this);
const vm = getAssociatedVM(this);
const { elm } = vm;
const { getAttribute } = vm.renderer;
return getAttribute(elm, name);
},

getAttributeNS(namespace: string | null, name: string): string | null {
const { elm } = getAssociatedVM(this);
const vm = getAssociatedVM(this);
const { elm } = vm;
const { getAttribute } = vm.renderer;
return getAttribute(elm, name, namespace);
},

setAttribute(name: string, value: string): void {
const vm = getAssociatedVM(this);
const { elm } = vm;
const {
elm,
renderer: { setAttribute },
} = vm;

if (process.env.NODE_ENV !== 'production') {
assert.isFalse(
Expand All @@ -401,7 +410,10 @@ LightningElement.prototype = {

setAttributeNS(namespace: string | null, name: string, value: string): void {
const vm = getAssociatedVM(this);
const { elm } = vm;
const {
elm,
renderer: { setAttribute },
} = vm;

if (process.env.NODE_ENV !== 'production') {
assert.isFalse(
Expand All @@ -417,7 +429,10 @@ LightningElement.prototype = {

getBoundingClientRect(): ClientRect {
const vm = getAssociatedVM(this);
const { elm } = vm;
const {
elm,
renderer: { getBoundingClientRect },
} = vm;

if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'getBoundingClientRect()');
Expand All @@ -427,13 +442,20 @@ LightningElement.prototype = {
},

get isConnected(): boolean {
const { elm } = getAssociatedVM(this);
const vm = getAssociatedVM(this);
const {
elm,
renderer: { isConnected },
} = vm;
return isConnected(elm);
},

get classList(): DOMTokenList {
const vm = getAssociatedVM(this);
const { elm } = vm;
const {
elm,
renderer: { getClassList },
} = vm;

if (process.env.NODE_ENV !== 'production') {
// TODO [#1290]: this still fails in dev but works in production, eventually, we should
Expand Down Expand Up @@ -467,6 +489,60 @@ LightningElement.prototype = {
return null;
},

get children() {
const vm = getAssociatedVM(this);
const renderer = vm.renderer;
if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'children');
}
return renderer.getChildren(vm.elm);
},

get childNodes() {
const vm = getAssociatedVM(this);
const renderer = vm.renderer;
if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'childNodes');
}
return renderer.getChildNodes(vm.elm);
},

get firstChild() {
const vm = getAssociatedVM(this);
const renderer = vm.renderer;
if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'firstChild');
}
return renderer.getFirstChild(vm.elm);
},

get firstElementChild() {
const vm = getAssociatedVM(this);
const renderer = vm.renderer;
if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'firstElementChild');
}
return renderer.getFirstElementChild(vm.elm);
},

get lastChild() {
const vm = getAssociatedVM(this);
const renderer = vm.renderer;
if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'lastChild');
}
return renderer.getLastChild(vm.elm);
},

get lastElementChild() {
const vm = getAssociatedVM(this);
const renderer = vm.renderer;
if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, 'lastElementChild');
}
return renderer.getLastElementChild(vm.elm);
},

render(): Template {
const vm = getAssociatedVM(this);
return vm.def.template;
Expand All @@ -480,84 +556,25 @@ LightningElement.prototype = {

const queryAndChildGetterDescriptors: PropertyDescriptorMap = create(null);

// The reason we don't just call `import * as renderer from '../renderer'` here is that the bundle size
// is smaller if we reference each function individually. Otherwise Rollup will create one big frozen
// object representing the renderer, with a lot of methods we don't actually need.
const childGetters = [
'children',
'childNodes',
'firstChild',
'firstElementChild',
'lastChild',
'lastElementChild',
] as const;

function getChildGetter(methodName: typeof childGetters[number]) {
switch (methodName) {
case 'children':
return getChildren;
case 'childNodes':
return getChildNodes;
case 'firstChild':
return getFirstChild;
case 'firstElementChild':
return getFirstElementChild;
case 'lastChild':
return getLastChild;
case 'lastElementChild':
return getLastElementChild;
}
}

// Generic passthrough for child getters on HTMLElement to the relevant Renderer APIs
for (const childGetter of childGetters) {
queryAndChildGetterDescriptors[childGetter] = {
get(this: LightningElement) {
const vm = getAssociatedVM(this);
const { elm } = vm;

if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, childGetter);
}

return getChildGetter(childGetter)(elm);
},
configurable: true,
enumerable: true,
};
}

const queryMethods = [
'getElementsByClassName',
'getElementsByTagName',
'querySelector',
'querySelectorAll',
] as const;
function getQueryMethod(methodName: typeof queryMethods[number]) {
switch (methodName) {
case 'getElementsByClassName':
return getElementsByClassName;
case 'getElementsByTagName':
return getElementsByTagName;
case 'querySelector':
return querySelector;
case 'querySelectorAll':
return querySelectorAll;
}
}

// Generic passthrough for query APIs on HTMLElement to the relevant Renderer APIs
for (const queryMethod of queryMethods) {
queryAndChildGetterDescriptors[queryMethod] = {
value(this: LightningElement, arg: string) {
const vm = getAssociatedVM(this);
const { elm } = vm;
const { elm, renderer } = vm;

if (process.env.NODE_ENV !== 'production') {
warnIfInvokedDuringConstruction(vm, `${queryMethod}()`);
}

return getQueryMethod(queryMethod)(elm, arg);
return renderer[queryMethod](elm, arg);
},
configurable: true,
enumerable: true,
Expand Down
Loading

0 comments on commit da49079

Please sign in to comment.