Skip to content

Commit da49079

Browse files
caridyravijayaramappapmdartusnolanlawson
authored
refactor(engine-core): encapsulate renderer as an object and allow it 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>
1 parent ec3a14f commit da49079

28 files changed

+671
-1006
lines changed

packages/@lwc/engine-core/src/framework/base-lightning-element.ts

Lines changed: 117 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,6 @@ import {
2727

2828
import { logError } from '../shared/logger';
2929
import { getComponentTag } from '../shared/format';
30-
import {
31-
getChildren,
32-
getChildNodes,
33-
getFirstChild,
34-
getFirstElementChild,
35-
getLastChild,
36-
getLastElementChild,
37-
assertInstanceOfHTMLElement,
38-
attachShadow,
39-
addEventListener,
40-
removeEventListener,
41-
getAttribute,
42-
removeAttribute,
43-
setAttribute,
44-
getBoundingClientRect,
45-
isConnected,
46-
getClassList,
47-
dispatchEvent,
48-
getElementsByClassName,
49-
getElementsByTagName,
50-
querySelector,
51-
querySelectorAll,
52-
} from '../renderer';
5330

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

222199
if (process.env.NODE_ENV !== 'production') {
200+
const { assertInstanceOfHTMLElement } = vm.renderer;
223201
assertInstanceOfHTMLElement(
224202
vm.elm,
225203
`Component creation requires a DOM element to be associated to ${vm}.`
@@ -270,6 +248,7 @@ function doAttachShadow(vm: VM): ShadowRoot {
270248
mode,
271249
shadowMode,
272250
def: { ctor },
251+
renderer: { attachShadow },
273252
} = vm;
274253

275254
const shadowRoot = attachShadow(elm, {
@@ -303,7 +282,11 @@ LightningElement.prototype = {
303282
constructor: LightningElement,
304283

305284
dispatchEvent(event: Event): boolean {
306-
const { elm } = getAssociatedVM(this);
285+
const vm = getAssociatedVM(this);
286+
const {
287+
elm,
288+
renderer: { dispatchEvent },
289+
} = vm;
307290
return dispatchEvent(elm, event);
308291
},
309292

@@ -313,7 +296,10 @@ LightningElement.prototype = {
313296
options?: boolean | AddEventListenerOptions
314297
): void {
315298
const vm = getAssociatedVM(this);
316-
const { elm } = vm;
299+
const {
300+
elm,
301+
renderer: { addEventListener },
302+
} = vm;
317303

318304
if (process.env.NODE_ENV !== 'production') {
319305
const vmBeingRendered = getVMBeingRendered();
@@ -341,51 +327,74 @@ LightningElement.prototype = {
341327
options?: boolean | AddEventListenerOptions
342328
): void {
343329
const vm = getAssociatedVM(this);
344-
const { elm } = vm;
330+
const {
331+
elm,
332+
renderer: { removeEventListener },
333+
} = vm;
345334

346335
const wrappedListener = getWrappedComponentsListener(vm, listener);
347336
removeEventListener(elm, type, wrappedListener, options);
348337
},
349338

350339
hasAttribute(name: string): boolean {
351-
const { elm } = getAssociatedVM(this);
340+
const vm = getAssociatedVM(this);
341+
const {
342+
elm,
343+
renderer: { getAttribute },
344+
} = vm;
352345
return !isNull(getAttribute(elm, name));
353346
},
354347

355348
hasAttributeNS(namespace: string | null, name: string): boolean {
356-
const { elm } = getAssociatedVM(this);
349+
const vm = getAssociatedVM(this);
350+
const {
351+
elm,
352+
renderer: { getAttribute },
353+
} = vm;
357354
return !isNull(getAttribute(elm, name, namespace));
358355
},
359356

360357
removeAttribute(name: string): void {
361-
const { elm } = getAssociatedVM(this);
362-
358+
const vm = getAssociatedVM(this);
359+
const {
360+
elm,
361+
renderer: { removeAttribute },
362+
} = vm;
363363
unlockAttribute(elm, name);
364364
removeAttribute(elm, name);
365365
lockAttribute(elm, name);
366366
},
367367

368368
removeAttributeNS(namespace: string | null, name: string): void {
369-
const { elm } = getAssociatedVM(this);
370-
369+
const {
370+
elm,
371+
renderer: { removeAttribute },
372+
} = getAssociatedVM(this);
371373
unlockAttribute(elm, name);
372374
removeAttribute(elm, name, namespace);
373375
lockAttribute(elm, name);
374376
},
375377

376378
getAttribute(name: string): string | null {
377-
const { elm } = getAssociatedVM(this);
379+
const vm = getAssociatedVM(this);
380+
const { elm } = vm;
381+
const { getAttribute } = vm.renderer;
378382
return getAttribute(elm, name);
379383
},
380384

381385
getAttributeNS(namespace: string | null, name: string): string | null {
382-
const { elm } = getAssociatedVM(this);
386+
const vm = getAssociatedVM(this);
387+
const { elm } = vm;
388+
const { getAttribute } = vm.renderer;
383389
return getAttribute(elm, name, namespace);
384390
},
385391

386392
setAttribute(name: string, value: string): void {
387393
const vm = getAssociatedVM(this);
388-
const { elm } = vm;
394+
const {
395+
elm,
396+
renderer: { setAttribute },
397+
} = vm;
389398

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

402411
setAttributeNS(namespace: string | null, name: string, value: string): void {
403412
const vm = getAssociatedVM(this);
404-
const { elm } = vm;
413+
const {
414+
elm,
415+
renderer: { setAttribute },
416+
} = vm;
405417

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

418430
getBoundingClientRect(): ClientRect {
419431
const vm = getAssociatedVM(this);
420-
const { elm } = vm;
432+
const {
433+
elm,
434+
renderer: { getBoundingClientRect },
435+
} = vm;
421436

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

429444
get isConnected(): boolean {
430-
const { elm } = getAssociatedVM(this);
445+
const vm = getAssociatedVM(this);
446+
const {
447+
elm,
448+
renderer: { isConnected },
449+
} = vm;
431450
return isConnected(elm);
432451
},
433452

434453
get classList(): DOMTokenList {
435454
const vm = getAssociatedVM(this);
436-
const { elm } = vm;
455+
const {
456+
elm,
457+
renderer: { getClassList },
458+
} = vm;
437459

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

492+
get children() {
493+
const vm = getAssociatedVM(this);
494+
const renderer = vm.renderer;
495+
if (process.env.NODE_ENV !== 'production') {
496+
warnIfInvokedDuringConstruction(vm, 'children');
497+
}
498+
return renderer.getChildren(vm.elm);
499+
},
500+
501+
get childNodes() {
502+
const vm = getAssociatedVM(this);
503+
const renderer = vm.renderer;
504+
if (process.env.NODE_ENV !== 'production') {
505+
warnIfInvokedDuringConstruction(vm, 'childNodes');
506+
}
507+
return renderer.getChildNodes(vm.elm);
508+
},
509+
510+
get firstChild() {
511+
const vm = getAssociatedVM(this);
512+
const renderer = vm.renderer;
513+
if (process.env.NODE_ENV !== 'production') {
514+
warnIfInvokedDuringConstruction(vm, 'firstChild');
515+
}
516+
return renderer.getFirstChild(vm.elm);
517+
},
518+
519+
get firstElementChild() {
520+
const vm = getAssociatedVM(this);
521+
const renderer = vm.renderer;
522+
if (process.env.NODE_ENV !== 'production') {
523+
warnIfInvokedDuringConstruction(vm, 'firstElementChild');
524+
}
525+
return renderer.getFirstElementChild(vm.elm);
526+
},
527+
528+
get lastChild() {
529+
const vm = getAssociatedVM(this);
530+
const renderer = vm.renderer;
531+
if (process.env.NODE_ENV !== 'production') {
532+
warnIfInvokedDuringConstruction(vm, 'lastChild');
533+
}
534+
return renderer.getLastChild(vm.elm);
535+
},
536+
537+
get lastElementChild() {
538+
const vm = getAssociatedVM(this);
539+
const renderer = vm.renderer;
540+
if (process.env.NODE_ENV !== 'production') {
541+
warnIfInvokedDuringConstruction(vm, 'lastElementChild');
542+
}
543+
return renderer.getLastElementChild(vm.elm);
544+
},
545+
470546
render(): Template {
471547
const vm = getAssociatedVM(this);
472548
return vm.def.template;
@@ -480,84 +556,25 @@ LightningElement.prototype = {
480556

481557
const queryAndChildGetterDescriptors: PropertyDescriptorMap = create(null);
482558

483-
// The reason we don't just call `import * as renderer from '../renderer'` here is that the bundle size
484-
// is smaller if we reference each function individually. Otherwise Rollup will create one big frozen
485-
// object representing the renderer, with a lot of methods we don't actually need.
486-
const childGetters = [
487-
'children',
488-
'childNodes',
489-
'firstChild',
490-
'firstElementChild',
491-
'lastChild',
492-
'lastElementChild',
493-
] as const;
494-
495-
function getChildGetter(methodName: typeof childGetters[number]) {
496-
switch (methodName) {
497-
case 'children':
498-
return getChildren;
499-
case 'childNodes':
500-
return getChildNodes;
501-
case 'firstChild':
502-
return getFirstChild;
503-
case 'firstElementChild':
504-
return getFirstElementChild;
505-
case 'lastChild':
506-
return getLastChild;
507-
case 'lastElementChild':
508-
return getLastElementChild;
509-
}
510-
}
511-
512-
// Generic passthrough for child getters on HTMLElement to the relevant Renderer APIs
513-
for (const childGetter of childGetters) {
514-
queryAndChildGetterDescriptors[childGetter] = {
515-
get(this: LightningElement) {
516-
const vm = getAssociatedVM(this);
517-
const { elm } = vm;
518-
519-
if (process.env.NODE_ENV !== 'production') {
520-
warnIfInvokedDuringConstruction(vm, childGetter);
521-
}
522-
523-
return getChildGetter(childGetter)(elm);
524-
},
525-
configurable: true,
526-
enumerable: true,
527-
};
528-
}
529-
530559
const queryMethods = [
531560
'getElementsByClassName',
532561
'getElementsByTagName',
533562
'querySelector',
534563
'querySelectorAll',
535564
] as const;
536-
function getQueryMethod(methodName: typeof queryMethods[number]) {
537-
switch (methodName) {
538-
case 'getElementsByClassName':
539-
return getElementsByClassName;
540-
case 'getElementsByTagName':
541-
return getElementsByTagName;
542-
case 'querySelector':
543-
return querySelector;
544-
case 'querySelectorAll':
545-
return querySelectorAll;
546-
}
547-
}
548565

549566
// Generic passthrough for query APIs on HTMLElement to the relevant Renderer APIs
550567
for (const queryMethod of queryMethods) {
551568
queryAndChildGetterDescriptors[queryMethod] = {
552569
value(this: LightningElement, arg: string) {
553570
const vm = getAssociatedVM(this);
554-
const { elm } = vm;
571+
const { elm, renderer } = vm;
555572

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

560-
return getQueryMethod(queryMethod)(elm, arg);
577+
return renderer[queryMethod](elm, arg);
561578
},
562579
configurable: true,
563580
enumerable: true,

0 commit comments

Comments
 (0)