diff --git a/packages/@lwc/engine-core/src/framework/base-lightning-element.ts b/packages/@lwc/engine-core/src/framework/base-lightning-element.ts index 0d6b6559cb..dae1e0f039 100644 --- a/packages/@lwc/engine-core/src/framework/base-lightning-element.ts +++ b/packages/@lwc/engine-core/src/framework/base-lightning-element.ts @@ -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'; @@ -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}.` @@ -270,6 +248,7 @@ function doAttachShadow(vm: VM): ShadowRoot { mode, shadowMode, def: { ctor }, + renderer: { attachShadow }, } = vm; const shadowRoot = attachShadow(elm, { @@ -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); }, @@ -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(); @@ -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( @@ -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( @@ -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()'); @@ -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 @@ -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; @@ -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, diff --git a/packages/@lwc/engine-core/src/framework/hydration.ts b/packages/@lwc/engine-core/src/framework/hydration.ts index d741e21548..627a34ae2f 100644 --- a/packages/@lwc/engine-core/src/framework/hydration.ts +++ b/packages/@lwc/engine-core/src/framework/hydration.ts @@ -7,16 +7,8 @@ import { isUndefined, ArrayJoin, assert, keys, isNull } from '@lwc/shared'; import { logError, logWarn } from '../shared/logger'; -import { - getAttribute, - getClassList, - setText, - getProperty, - setProperty, - nextSibling, - getFirstChild, -} from '../renderer'; +import { RendererAPI } from './renderer'; import { cloneAndOmitKey, parseStyleText } from './utils'; import { allocateChildren, mount, removeNode } from './rendering'; import { @@ -68,41 +60,44 @@ function hydrateVM(vm: VM) { const children = renderComponent(vm); vm.children = children; - const parentNode = vm.renderRoot; - + const { + renderRoot: parentNode, + renderer: { getFirstChild }, + } = vm; hydrateChildren(getFirstChild(parentNode), children, parentNode, vm); runRenderedCallback(vm); } -function hydrateNode(node: Node, vnode: VNode): Node | null { +function hydrateNode(node: Node, vnode: VNode, renderer: RendererAPI): Node | null { let hydratedNode; switch (vnode.type) { case VNodeType.Text: - hydratedNode = hydrateText(node, vnode); + // VText has no special capability, fallback to the owner's renderer + hydratedNode = hydrateText(node, vnode, renderer); break; case VNodeType.Comment: - hydratedNode = hydrateComment(node, vnode); + // VComment has no special capability, fallback to the owner's renderer + hydratedNode = hydrateComment(node, vnode, renderer); break; case VNodeType.Element: - hydratedNode = hydrateElement(node, vnode); + hydratedNode = hydrateElement(node, vnode, vnode.data.renderer ?? renderer); break; case VNodeType.CustomElement: - hydratedNode = hydrateCustomElement(node, vnode); + hydratedNode = hydrateCustomElement(node, vnode, vnode.data.renderer ?? renderer); break; } - - return nextSibling(hydratedNode); + return renderer.nextSibling(hydratedNode); } -function hydrateText(node: Node, vnode: VText): Node | null { - if (!hasCorrectNodeType(vnode, node, EnvNodeTypes.TEXT)) { - return handleMismatch(node, vnode); +function hydrateText(node: Node, vnode: VText, renderer: RendererAPI): Node | null { + if (!hasCorrectNodeType(vnode, node, EnvNodeTypes.TEXT, renderer)) { + return handleMismatch(node, vnode, renderer); } - if (process.env.NODE_ENV !== 'production') { + const { getProperty } = renderer; const nodeValue = getProperty(node, 'nodeValue'); if (nodeValue !== vnode.text && !(nodeValue === '\u200D' && vnode.text === '')) { @@ -112,19 +107,19 @@ function hydrateText(node: Node, vnode: VText): Node | null { ); } } - + const { setText } = renderer; setText(node, vnode.text ?? null); vnode.elm = node; return node; } -function hydrateComment(node: Node, vnode: VComment): Node | null { - if (!hasCorrectNodeType(vnode, node, EnvNodeTypes.COMMENT)) { - return handleMismatch(node, vnode); +function hydrateComment(node: Node, vnode: VComment, renderer: RendererAPI): Node | null { + if (!hasCorrectNodeType(vnode, node, EnvNodeTypes.COMMENT, renderer)) { + return handleMismatch(node, vnode, renderer); } - if (process.env.NODE_ENV !== 'production') { + const { getProperty } = renderer; const nodeValue = getProperty(node, 'nodeValue'); if (nodeValue !== vnode.text) { @@ -135,22 +130,24 @@ function hydrateComment(node: Node, vnode: VComment): Node | null { } } + const { setProperty } = renderer; setProperty(node, 'nodeValue', vnode.text ?? null); vnode.elm = node; return node; } -function hydrateElement(elm: Node, vnode: VElement): Node | null { +function hydrateElement(elm: Node, vnode: VElement, renderer: RendererAPI): Node | null { if ( - !hasCorrectNodeType(vnode, elm, EnvNodeTypes.ELEMENT) || - !isMatchingElement(vnode, elm) + !hasCorrectNodeType(vnode, elm, EnvNodeTypes.ELEMENT, renderer) || + !isMatchingElement(vnode, elm, renderer) ) { - return handleMismatch(elm, vnode); + return handleMismatch(elm, vnode, renderer); } vnode.elm = elm; + const { owner } = vnode; const { context } = vnode.data; const isDomManual = Boolean( !isUndefined(context) && !isUndefined(context.lwc) && context.lwc.dom === LwcDomMode.Manual @@ -159,7 +156,10 @@ function hydrateElement(elm: Node, vnode: VElement): Node | null { if (isDomManual) { // it may be that this element has lwc:inner-html, we need to diff and in case are the same, // remove the innerHTML from props so it reuses the existing dom elements. - const { props } = vnode.data; + const { + data: { props }, + } = vnode; + const { getProperty } = renderer; if (!isUndefined(props) && !isUndefined(props.innerHTML)) { if (getProperty(elm, 'innerHTML') === props.innerHTML) { // Do a shallow clone since VNodeData may be shared across VNodes due to hoist optimization @@ -174,33 +174,38 @@ function hydrateElement(elm: Node, vnode: VElement): Node | null { elm, 'tagName' ).toLowerCase()}>: innerHTML values do not match for element, will recover from the difference`, - vnode.owner + owner ); } } } } - patchElementPropsAndAttrs(vnode); + patchElementPropsAndAttrs(vnode, renderer); if (!isDomManual) { - hydrateChildren(getFirstChild(elm), vnode.children, elm, vnode.owner); + const { getFirstChild } = renderer; + hydrateChildren(getFirstChild(elm), vnode.children, elm, owner); } return elm; } -function hydrateCustomElement(elm: Node, vnode: VCustomElement): Node | null { +function hydrateCustomElement( + elm: Node, + vnode: VCustomElement, + renderer: RendererAPI +): Node | null { if ( - !hasCorrectNodeType(vnode, elm, EnvNodeTypes.ELEMENT) || - !isMatchingElement(vnode, elm) + !hasCorrectNodeType(vnode, elm, EnvNodeTypes.ELEMENT, renderer) || + !isMatchingElement(vnode, elm, renderer) ) { - return handleMismatch(elm, vnode); + return handleMismatch(elm, vnode, renderer); } const { sel, mode, ctor, owner } = vnode; - const vm = createVM(elm, ctor, { + const vm = createVM(elm, ctor, renderer, { mode, owner, tagName: sel, @@ -211,7 +216,7 @@ function hydrateCustomElement(elm: Node, vnode: VCustomElement): Node | null { vnode.vm = vm; allocateChildren(vnode, vm); - patchElementPropsAndAttrs(vnode); + patchElementPropsAndAttrs(vnode, renderer); // Insert hook section: if (process.env.NODE_ENV !== 'production') { @@ -220,6 +225,7 @@ function hydrateCustomElement(elm: Node, vnode: VCustomElement): Node | null { runConnectedCallback(vm); if (vm.renderMode !== RenderMode.Light) { + const { getFirstChild } = renderer; // VM is not rendering in Light DOM, we can proceed and hydrate the slotted content. // Note: for Light DOM, this is handled while hydrating the VM hydrateChildren(getFirstChild(elm), vnode.children, elm, vm); @@ -238,12 +244,13 @@ function hydrateChildren( let hasWarned = false; let nextNode: Node | null = node; let anchor: Node | null = null; + const { renderer } = owner; for (let i = 0; i < children.length; i++) { const childVnode = children[i]; if (!isNull(childVnode)) { if (nextNode) { - nextNode = hydrateNode(nextNode, childVnode); + nextNode = hydrateNode(nextNode, childVnode, renderer); anchor = childVnode.elm!; } else { hasMismatch = true; @@ -256,7 +263,7 @@ function hydrateChildren( ); } } - mount(childVnode, parentNode, anchor); + mount(childVnode, parentNode, renderer, anchor); anchor = childVnode.elm!; } } @@ -272,34 +279,41 @@ function hydrateChildren( ); } } + // nextSibling is mostly harmless, and since we don't have + // a good reference to what element to act upon, we instead + // rely on the vm's associated renderer for navigating to the + // next node in the list to be hydrated. + const { nextSibling } = renderer; do { const current = nextNode; nextNode = nextSibling(nextNode); - removeNode(current, parentNode); + removeNode(current, parentNode, renderer); } while (nextNode); } } -function handleMismatch(node: Node, vnode: VNode, msg?: string): Node | null { +function handleMismatch(node: Node, vnode: VNode, renderer: RendererAPI): Node | null { hasMismatch = true; - if (!isUndefined(msg)) { - if (process.env.NODE_ENV !== 'production') { - logError(msg, vnode.owner); - } - } + const { getProperty } = renderer; const parentNode = getProperty(node, 'parentNode'); - mount(vnode, parentNode, node); - removeNode(node, parentNode); + mount(vnode, parentNode, renderer, node); + removeNode(node, parentNode, renderer); return vnode.elm!; } -function patchElementPropsAndAttrs(vnode: VBaseElement) { - applyEventListeners(vnode); - patchProps(null, vnode); +function patchElementPropsAndAttrs(vnode: VBaseElement, renderer: RendererAPI) { + applyEventListeners(vnode, renderer); + patchProps(null, vnode, renderer); } -function hasCorrectNodeType(vnode: VNode, node: Node, nodeType: number): node is T { +function hasCorrectNodeType( + vnode: VNode, + node: Node, + nodeType: number, + renderer: RendererAPI +): node is T { + const { getProperty } = renderer; if (getProperty(node, 'nodeType') !== nodeType) { if (process.env.NODE_ENV !== 'production') { logError('Hydration mismatch: incorrect node type received', vnode.owner); @@ -310,7 +324,8 @@ function hasCorrectNodeType(vnode: VNode, node: Node, nodeType: return true; } -function isMatchingElement(vnode: VBaseElement, elm: Element) { +function isMatchingElement(vnode: VBaseElement, elm: Element, renderer: RendererAPI) { + const { getProperty } = renderer; if (vnode.sel.toLowerCase() !== getProperty(elm, 'tagName').toLowerCase()) { if (process.env.NODE_ENV !== 'production') { logError( @@ -325,14 +340,14 @@ function isMatchingElement(vnode: VBaseElement, elm: Element) { return false; } - const hasIncompatibleAttrs = validateAttrs(vnode, elm); - const hasIncompatibleClass = validateClassAttr(vnode, elm); - const hasIncompatibleStyle = validateStyleAttr(vnode, elm); + const hasIncompatibleAttrs = validateAttrs(vnode, elm, renderer); + const hasIncompatibleClass = validateClassAttr(vnode, elm, renderer); + const hasIncompatibleStyle = validateStyleAttr(vnode, elm, renderer); return hasIncompatibleAttrs && hasIncompatibleClass && hasIncompatibleStyle; } -function validateAttrs(vnode: VBaseElement, elm: Element): boolean { +function validateAttrs(vnode: VBaseElement, elm: Element, renderer: RendererAPI): boolean { const { data: { attrs = {} }, } = vnode; @@ -342,15 +357,18 @@ function validateAttrs(vnode: VBaseElement, elm: Element): boolean { // Validate attributes, though we could always recovery from those by running the update mods. // Note: intentionally ONLY matching vnodes.attrs to elm.attrs, in case SSR is adding extra attributes. for (const [attrName, attrValue] of Object.entries(attrs)) { + const { owner } = vnode; + const { getAttribute } = renderer; const elmAttrValue = getAttribute(elm, attrName); if (String(attrValue) !== elmAttrValue) { if (process.env.NODE_ENV !== 'production') { + const { getProperty } = renderer; logError( `Mismatch hydrating element <${getProperty( elm, 'tagName' ).toLowerCase()}>: attribute "${attrName}" has different values, expected "${attrValue}" but found "${elmAttrValue}"`, - vnode.owner + owner ); } nodesAreCompatible = false; @@ -360,11 +378,11 @@ function validateAttrs(vnode: VBaseElement, elm: Element): boolean { return nodesAreCompatible; } -function validateClassAttr(vnode: VBaseElement, elm: Element): boolean { +function validateClassAttr(vnode: VBaseElement, elm: Element, renderer: RendererAPI): boolean { const { data: { className, classMap }, } = vnode; - + const { getProperty, getClassList } = renderer; let nodesAreCompatible = true; let vnodeClassName; @@ -410,10 +428,11 @@ function validateClassAttr(vnode: VBaseElement, elm: Element): boolean { return nodesAreCompatible; } -function validateStyleAttr(vnode: VBaseElement, elm: Element): boolean { +function validateStyleAttr(vnode: VBaseElement, elm: Element, renderer: RendererAPI): boolean { const { data: { style, styleDecls }, } = vnode; + const { getAttribute } = renderer; const elmStyle = getAttribute(elm, 'style') || ''; let vnodeStyle; let nodesAreCompatible = true; @@ -449,6 +468,7 @@ function validateStyleAttr(vnode: VBaseElement, elm: Element): boolean { if (!nodesAreCompatible) { if (process.env.NODE_ENV !== 'production') { + const { getProperty } = renderer; logError( `Mismatch hydrating element <${getProperty( elm, diff --git a/packages/@lwc/engine-core/src/framework/main.ts b/packages/@lwc/engine-core/src/framework/main.ts index a4ff3815f4..b7f87ea1c1 100644 --- a/packages/@lwc/engine-core/src/framework/main.ts +++ b/packages/@lwc/engine-core/src/framework/main.ts @@ -55,46 +55,3 @@ export type { WireAdapterConstructor, WireAdapterSchemaValue, } from './wiring'; - -// Initialization APIs for the renderer, to be used by engine implementations ---------------------- -export { - setAssertInstanceOfHTMLElement, - setAttachShadow, - setCreateComment, - setCreateElement, - setCreateText, - setDefineCustomElement, - setDispatchEvent, - setGetAttribute, - setGetBoundingClientRect, - setGetChildNodes, - setGetChildren, - setGetClassList, - setGetCustomElement, - setGetElementsByClassName, - setGetElementsByTagName, - setGetFirstChild, - setGetFirstElementChild, - setGetLastChild, - setGetLastElementChild, - setGetProperty, - setHTMLElement, - setInsert, - setIsConnected, - setIsHydrating, - setIsNativeShadowDefined, - setIsSyntheticShadowDefined, - setNextSibling, - setQuerySelector, - setQuerySelectorAll, - setRemove, - setRemoveAttribute, - setRemoveEventListener, - setSetAttribute, - setSetCSSStyleProperty, - setSetProperty, - setSetText, - setSsr, - setAddEventListener, - setInsertStylesheet, -} from '../renderer'; diff --git a/packages/@lwc/engine-core/src/framework/modules/attrs.ts b/packages/@lwc/engine-core/src/framework/modules/attrs.ts index a8fe942a2f..4eb3d490ac 100644 --- a/packages/@lwc/engine-core/src/framework/modules/attrs.ts +++ b/packages/@lwc/engine-core/src/framework/modules/attrs.ts @@ -5,8 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ import { isNull, isUndefined, StringCharCodeAt, XML_NAMESPACE, XLINK_NAMESPACE } from '@lwc/shared'; - -import { setAttribute, removeAttribute } from '../../renderer'; +import { RendererAPI } from '../renderer'; import { unlockAttribute, lockAttribute } from '../attributes'; import { EmptyObject } from '../utils'; @@ -14,7 +13,11 @@ import { VBaseElement } from '../vnodes'; const ColonCharCode = 58; -export function patchAttributes(oldVnode: VBaseElement | null, vnode: VBaseElement) { +export function patchAttributes( + oldVnode: VBaseElement | null, + vnode: VBaseElement, + renderer: RendererAPI +) { const { attrs } = vnode.data; if (isUndefined(attrs)) { return; @@ -26,6 +29,7 @@ export function patchAttributes(oldVnode: VBaseElement | null, vnode: VBaseEleme } const { elm } = vnode; + const { setAttribute, removeAttribute } = renderer; for (const key in attrs) { const cur = attrs[key]; const old = oldAttrs[key]; diff --git a/packages/@lwc/engine-core/src/framework/modules/computed-class-attr.ts b/packages/@lwc/engine-core/src/framework/modules/computed-class-attr.ts index 365a4a15f9..c5cd6667b7 100644 --- a/packages/@lwc/engine-core/src/framework/modules/computed-class-attr.ts +++ b/packages/@lwc/engine-core/src/framework/modules/computed-class-attr.ts @@ -13,8 +13,7 @@ import { StringCharCodeAt, StringSlice, } from '@lwc/shared'; - -import { getClassList } from '../../renderer'; +import { RendererAPI } from '../renderer'; import { EmptyObject, SPACE_CHAR } from '../utils'; import { VBaseElement } from '../vnodes'; @@ -57,7 +56,11 @@ function getMapFromClassName(className: string | undefined): Record boolean; + insert: (node: N, parent: E, anchor: N | null) => void; + remove: (node: N, parent: E) => void; + createElement: (tagName: string, namespace?: string) => E; + createText: (content: string) => N; + createComment: (content: string) => N; + nextSibling: (node: N) => N | null; + attachShadow: (element: E, options: ShadowRootInit) => N; + getProperty: (node: N, key: string) => any; + setProperty: (node: N, key: string, value: any) => void; + setText: (node: N, content: string) => void; + getAttribute: (element: E, name: string, namespace?: string | null) => string | null; + setAttribute: (element: E, name: string, value: string, namespace?: string | null) => void; + removeAttribute: (element: E, name: string, namespace?: string | null) => void; + addEventListener: ( + target: N, + type: string, + callback: (event: Event) => any, + options?: AddEventListenerOptions | boolean + ) => void; + removeEventListener: ( + target: N, + type: string, + callback: (event: Event) => any, + options?: EventListenerOptions | boolean + ) => void; + dispatchEvent: (target: N, event: Event) => boolean; + getClassList: (element: E) => DOMTokenList; + setCSSStyleProperty: (element: E, name: string, value: string, important: boolean) => void; + getBoundingClientRect: (element: E) => ClientRect; + querySelector: (element: E, selectors: string) => E | null; + querySelectorAll: (element: E, selectors: string) => NodeList; + getElementsByTagName: (element: E, tagNameOrWildCard: string) => HTMLCollection; + getElementsByClassName: (element: E, names: string) => HTMLCollection; + getChildren: (element: E) => HTMLCollection; + getChildNodes: (element: E) => NodeList; + getFirstChild: (element: E) => N | null; + getFirstElementChild: (element: E) => E | null; + getLastChild: (element: E) => N | null; + getLastElementChild: (element: E) => E | null; + isConnected: (node: N) => boolean; + insertStylesheet: (content: string, target?: ShadowRoot) => void; + assertInstanceOfHTMLElement: (elm: any, msg: string) => void; + defineCustomElement: ( + name: string, + constructor: CustomElementConstructor, + options?: ElementDefinitionOptions + ) => void; + getCustomElement: (name: string) => CustomElementConstructor | undefined; +} diff --git a/packages/@lwc/engine-core/src/framework/rendering.ts b/packages/@lwc/engine-core/src/framework/rendering.ts index 5c30f34a55..ed6da6915e 100644 --- a/packages/@lwc/engine-core/src/framework/rendering.ts +++ b/packages/@lwc/engine-core/src/framework/rendering.ts @@ -18,18 +18,7 @@ import { KEY__SHADOW_RESOLVER, } from '@lwc/shared'; -import { - remove, - insert, - nextSibling, - createElement, - createText, - setText, - createComment, - getClassList, - isSyntheticShadowDefined, -} from '../renderer'; - +import { RendererAPI } from './renderer'; import { EmptyArray } from './utils'; import { markComponentAsDirty } from './component'; import { getUpgradableConstructor } from './upgradable-element'; @@ -69,15 +58,20 @@ import { applyEventListeners } from './modules/events'; import { applyStaticClassAttribute } from './modules/static-class-attr'; import { applyStaticStyleAttribute } from './modules/static-style-attr'; -export function patchChildren(c1: VNodes, c2: VNodes, parent: ParentNode): void { +export function patchChildren( + c1: VNodes, + c2: VNodes, + parent: ParentNode, + renderer: RendererAPI +): void { if (hasDynamicChildren(c2)) { - updateDynamicChildren(c1, c2, parent); + updateDynamicChildren(c1, c2, parent, renderer); } else { - updateStaticChildren(c1, c2, parent); + updateStaticChildren(c1, c2, parent, renderer); } } -function patch(n1: VNode, n2: VNode) { +function patch(n1: VNode, n2: VNode, renderer: RendererAPI) { if (n1 === n2) { return; } @@ -95,110 +89,133 @@ function patch(n1: VNode, n2: VNode) { switch (n2.type) { case VNodeType.Text: - patchText(n1 as VText, n2); + // VText has no special capability, fallback to the owner's renderer + patchText(n1 as VText, n2, renderer); break; case VNodeType.Comment: - patchComment(n1 as VComment, n2); + // VComment has no special capability, fallback to the owner's renderer + patchComment(n1 as VComment, n2, renderer); break; case VNodeType.Element: - patchElement(n1 as VElement, n2); + patchElement(n1 as VElement, n2, n2.data.renderer ?? renderer); break; case VNodeType.CustomElement: - patchCustomElement(n1 as VCustomElement, n2); + patchCustomElement(n1 as VCustomElement, n2, n2.data.renderer ?? renderer); break; } } -export function mount(node: VNode, parent: ParentNode, anchor: Node | null) { +export function mount(node: VNode, parent: ParentNode, renderer: RendererAPI, anchor: Node | null) { switch (node.type) { case VNodeType.Text: - mountText(node, parent, anchor); + // VText has no special capability, fallback to the owner's renderer + mountText(node, parent, anchor, renderer); break; case VNodeType.Comment: - mountComment(node, parent, anchor); + // VComment has no special capability, fallback to the owner's renderer + mountComment(node, parent, anchor, renderer); break; case VNodeType.Element: - mountElement(node, parent, anchor); + // If the vnode data has a renderer override use it, else fallback to owner's renderer + mountElement(node, parent, anchor, node.data.renderer ?? renderer); break; case VNodeType.CustomElement: - mountCustomElement(node, parent, anchor); + // If the vnode data has a renderer override use it, else fallback to owner's renderer + mountCustomElement(node, parent, anchor, node.data.renderer ?? renderer); break; } } -function patchText(n1: VText, n2: VText) { +function patchText(n1: VText, n2: VText, renderer: RendererAPI) { n2.elm = n1.elm; if (n2.text !== n1.text) { - updateTextContent(n2); + updateTextContent(n2, renderer); } } -function mountText(node: VText, parent: ParentNode, anchor: Node | null) { - const { owner } = node; +function mountText(vnode: VText, parent: ParentNode, anchor: Node | null, renderer: RendererAPI) { + const { owner } = vnode; + const { createText } = renderer; - const textNode = (node.elm = createText(node.text)); - linkNodeToShadow(textNode, owner); + const textNode = (vnode.elm = createText(vnode.text)); + linkNodeToShadow(textNode, owner, renderer); - insertNode(textNode, parent, anchor); + insertNode(textNode, parent, anchor, renderer); } -function patchComment(n1: VComment, n2: VComment) { +function patchComment(n1: VComment, n2: VComment, renderer: RendererAPI) { n2.elm = n1.elm; // FIXME: Comment nodes should be static, we shouldn't need to diff them together. However // it is the case today. if (n2.text !== n1.text) { - updateTextContent(n2); + updateTextContent(n2, renderer); } } -function mountComment(node: VComment, parent: ParentNode, anchor: Node | null) { - const { owner } = node; +function mountComment( + vnode: VComment, + parent: ParentNode, + anchor: Node | null, + renderer: RendererAPI +) { + const { owner } = vnode; + const { createComment } = renderer; - const commentNode = (node.elm = createComment(node.text)); - linkNodeToShadow(commentNode, owner); + const commentNode = (vnode.elm = createComment(vnode.text)); + linkNodeToShadow(commentNode, owner, renderer); - insertNode(commentNode, parent, anchor); + insertNode(commentNode, parent, anchor, renderer); } -function mountElement(vnode: VElement, parent: ParentNode, anchor: Node | null) { +function mountElement( + vnode: VElement, + parent: ParentNode, + anchor: Node | null, + renderer: RendererAPI +) { const { sel, owner, data: { svg }, } = vnode; + const { createElement } = renderer; const namespace = isTrue(svg) ? SVG_NAMESPACE : undefined; const elm = createElement(sel, namespace); - linkNodeToShadow(elm, owner); + linkNodeToShadow(elm, owner, renderer); - fallbackElmHook(elm, vnode); + fallbackElmHook(elm, vnode, renderer); vnode.elm = elm; - patchElementPropsAndAttrs(null, vnode); + patchElementPropsAndAttrs(null, vnode, renderer); - insertNode(elm, parent, anchor); - mountVNodes(vnode.children, elm, null); + insertNode(elm, parent, anchor, renderer); + mountVNodes(vnode.children, elm, renderer, null); } -function patchElement(n1: VElement, n2: VElement) { +function patchElement(n1: VElement, n2: VElement, renderer: RendererAPI) { const elm = (n2.elm = n1.elm!); - patchElementPropsAndAttrs(n1, n2); - patchChildren(n1.children, n2.children, elm); + patchElementPropsAndAttrs(n1, n2, renderer); + patchChildren(n1.children, n2.children, elm, renderer); } -function mountCustomElement(vnode: VCustomElement, parent: ParentNode, anchor: Node | null) { +function mountCustomElement( + vnode: VCustomElement, + parent: ParentNode, + anchor: Node | null, + renderer: RendererAPI +) { const { sel, owner } = vnode; - - const UpgradableConstructor = getUpgradableConstructor(sel); + const UpgradableConstructor = getUpgradableConstructor(sel, renderer); /** * Note: if the upgradable constructor does not expect, or throw when we new it * with a callback as the first argument, we could implement a more advanced @@ -208,10 +225,10 @@ function mountCustomElement(vnode: VCustomElement, parent: ParentNode, anchor: N let vm: VM | undefined; const elm = new UpgradableConstructor((elm: HTMLElement) => { // the custom element from the registry is expecting an upgrade callback - vm = createViewModelHook(elm, vnode); + vm = createViewModelHook(elm, vnode, renderer); }); - linkNodeToShadow(elm, owner); + linkNodeToShadow(elm, owner, renderer); vnode.elm = elm; vnode.vm = vm; @@ -221,8 +238,8 @@ function mountCustomElement(vnode: VCustomElement, parent: ParentNode, anchor: N throw new TypeError(`Incorrect Component Constructor`); } - patchElementPropsAndAttrs(null, vnode); - insertNode(elm, parent, anchor); + patchElementPropsAndAttrs(null, vnode, renderer); + insertNode(elm, parent, anchor, renderer); if (vm) { if (process.env.NODE_ENV !== 'production') { @@ -231,18 +248,18 @@ function mountCustomElement(vnode: VCustomElement, parent: ParentNode, anchor: N runConnectedCallback(vm); } - mountVNodes(vnode.children, elm, null); + mountVNodes(vnode.children, elm, renderer, null); if (vm) { appendVM(vm); } } -function patchCustomElement(n1: VCustomElement, n2: VCustomElement) { +function patchCustomElement(n1: VCustomElement, n2: VCustomElement, renderer: RendererAPI) { const elm = (n2.elm = n1.elm!); const vm = (n2.vm = n1.vm); - patchElementPropsAndAttrs(n1, n2); + patchElementPropsAndAttrs(n1, n2, renderer); if (!isUndefined(vm)) { // in fallback mode, the allocation will always set children to // empty and delegate the real allocation to the slot elements @@ -251,7 +268,7 @@ function patchCustomElement(n1: VCustomElement, n2: VCustomElement) { // in fallback mode, the children will be always empty, so, nothing // will happen, but in native, it does allocate the light dom - patchChildren(n1.children, n2.children, elm); + patchChildren(n1.children, n2.children, elm, renderer); if (!isUndefined(vm)) { // this will probably update the shadowRoot, but only if the vm is in a dirty state @@ -263,6 +280,7 @@ function patchCustomElement(n1: VCustomElement, n2: VCustomElement) { function mountVNodes( vnodes: VNodes, parent: ParentNode, + renderer: RendererAPI, anchor: Node | null, start: number = 0, end: number = vnodes.length @@ -270,27 +288,34 @@ function mountVNodes( for (; start < end; ++start) { const vnode = vnodes[start]; if (isVNode(vnode)) { - mount(vnode, parent, anchor); + mount(vnode, parent, renderer, anchor); } } } -function unmount(vnode: VNode, parent: ParentNode, doRemove: boolean = false) { +function unmount( + vnode: VNode, + parent: ParentNode, + renderer: RendererAPI, + doRemove: boolean = false +) { const { type, elm, sel } = vnode; // When unmounting a VNode subtree not all the elements have to removed from the DOM. The // subtree root, is the only element worth unmounting from the subtree. if (doRemove) { - removeNode(elm!, parent); + // The vnode might or might not have a data.renderer associated to it + // but the removal used here is from the owner instead. + removeNode(elm!, parent, renderer); } switch (type) { case VNodeType.Element: { // Slot content is removed to trigger slotchange event when removing slot. // Only required for synthetic shadow. - const removeChildren = + const shouldRemoveChildren = sel === 'slot' && vnode.owner.shadowMode === ShadowMode.Synthetic; - unmountVNodes(vnode.children, elm as ParentNode, removeChildren); + unmountVNodes(vnode.children, elm as ParentNode, renderer, shouldRemoveChildren); break; } @@ -309,6 +334,7 @@ function unmount(vnode: VNode, parent: ParentNode, doRemove: boolean = false) { function unmountVNodes( vnodes: VNodes, parent: ParentNode, + renderer: RendererAPI, doRemove: boolean = false, start: number = 0, end: number = vnodes.length @@ -316,7 +342,7 @@ function unmountVNodes( for (; start < end; ++start) { const ch = vnodes[start]; if (isVNode(ch)) { - unmount(ch, parent, doRemove); + unmount(ch, parent, renderer, doRemove); } } } @@ -334,17 +360,20 @@ function setElementShadowToken(elm: Element, token: string) { } // Set the scope token class for *.scoped.css styles -function setScopeTokenClassIfNecessary(elm: Element, owner: VM) { +function setScopeTokenClassIfNecessary(elm: Element, owner: VM, renderer: RendererAPI) { const { cmpTemplate, context } = owner; + const { getClassList } = renderer; const token = cmpTemplate?.stylesheetToken; if (!isUndefined(token) && context.hasScopedStyles) { + // TODO [#2762]: this dot notation with add is probably problematic + // probably we should have a renderer api for just the add operation getClassList(elm).add(token); } } -function linkNodeToShadow(elm: Node, owner: VM) { +function linkNodeToShadow(elm: Node, owner: VM, renderer: RendererAPI) { const { renderRoot, renderMode, shadowMode } = owner; - + const { isSyntheticShadowDefined } = renderer; // TODO [#1164]: this should eventually be done by the polyfill directly if (isSyntheticShadowDefined) { if (shadowMode === ShadowMode.Synthetic || renderMode === RenderMode.Light) { @@ -353,8 +382,9 @@ function linkNodeToShadow(elm: Node, owner: VM) { } } -function updateTextContent(vnode: VText | VComment) { +function updateTextContent(vnode: VText | VComment, renderer: RendererAPI) { const { elm, text } = vnode; + const { setText } = renderer; if (process.env.NODE_ENV !== 'production') { unlockDomMutation(); @@ -365,44 +395,48 @@ function updateTextContent(vnode: VText | VComment) { } } -function insertNode(node: Node, parent: Node, anchor: Node | null) { +function insertNode(node: Node, parent: Node, anchor: Node | null, renderer: RendererAPI) { if (process.env.NODE_ENV !== 'production') { unlockDomMutation(); } - insert(node, parent, anchor); + renderer.insert(node, parent, anchor); if (process.env.NODE_ENV !== 'production') { lockDomMutation(); } } -export function removeNode(node: Node, parent: ParentNode) { +export function removeNode(node: Node, parent: ParentNode, renderer: RendererAPI) { if (process.env.NODE_ENV !== 'production') { unlockDomMutation(); } - remove(node, parent); + renderer.remove(node, parent); if (process.env.NODE_ENV !== 'production') { lockDomMutation(); } } -function patchElementPropsAndAttrs(oldVnode: VBaseElement | null, vnode: VBaseElement) { +function patchElementPropsAndAttrs( + oldVnode: VBaseElement | null, + vnode: VBaseElement, + renderer: RendererAPI +) { if (isNull(oldVnode)) { - applyEventListeners(vnode); - applyStaticClassAttribute(vnode); - applyStaticStyleAttribute(vnode); + applyEventListeners(vnode, renderer); + applyStaticClassAttribute(vnode, renderer); + applyStaticStyleAttribute(vnode, renderer); } // Attrs need to be applied to element before props IE11 will wipe out value on radio inputs if // value is set before type=radio. - patchClassAttribute(oldVnode, vnode); - patchStyleAttribute(oldVnode, vnode); - patchAttributes(oldVnode, vnode); - patchProps(oldVnode, vnode); + patchClassAttribute(oldVnode, vnode, renderer); + patchStyleAttribute(oldVnode, vnode, renderer); + patchAttributes(oldVnode, vnode, renderer); + patchProps(oldVnode, vnode, renderer); } -function fallbackElmHook(elm: Element, vnode: VBaseElement) { +function fallbackElmHook(elm: Element, vnode: VBaseElement, renderer: RendererAPI) { const { owner } = vnode; - setScopeTokenClassIfNecessary(elm, owner); + setScopeTokenClassIfNecessary(elm, owner, renderer); if (owner.shadowMode === ShadowMode.Synthetic) { const { data: { context }, @@ -461,7 +495,7 @@ export function allocateChildren(vnode: VCustomElement, vm: VM) { } } -function createViewModelHook(elm: HTMLElement, vnode: VCustomElement): VM { +function createViewModelHook(elm: HTMLElement, vnode: VCustomElement, renderer: RendererAPI): VM { let vm = getAssociatedVMIfPresent(elm); // There is a possibility that a custom element is registered under tagName, in which case, the @@ -473,7 +507,7 @@ function createViewModelHook(elm: HTMLElement, vnode: VCustomElement): VM { const { sel, mode, ctor, owner } = vnode; - setScopeTokenClassIfNecessary(elm, owner); + setScopeTokenClassIfNecessary(elm, owner, renderer); if (owner.shadowMode === ShadowMode.Synthetic) { const { stylesheetToken } = owner.context; // when running in synthetic shadow mode, we need to set the shadowToken value @@ -483,7 +517,7 @@ function createViewModelHook(elm: HTMLElement, vnode: VCustomElement): VM { } } - vm = createVM(elm, ctor, { + vm = createVM(elm, ctor, renderer, { mode, owner, tagName: sel, @@ -575,7 +609,12 @@ function createKeyToOldIdx( return map; } -function updateDynamicChildren(oldCh: VNodes, newCh: VNodes, parent: ParentNode) { +function updateDynamicChildren( + oldCh: VNodes, + newCh: VNodes, + parent: ParentNode, + renderer: RendererAPI +) { let oldStartIdx = 0; let newStartIdx = 0; let oldEndIdx = oldCh.length - 1; @@ -600,23 +639,28 @@ function updateDynamicChildren(oldCh: VNodes, newCh: VNodes, parent: ParentNode) } else if (!isVNode(newEndVnode)) { newEndVnode = newCh[--newEndIdx]; } else if (isSameVnode(oldStartVnode, newStartVnode)) { - patch(oldStartVnode, newStartVnode); + patch(oldStartVnode, newStartVnode, renderer); oldStartVnode = oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; } else if (isSameVnode(oldEndVnode, newEndVnode)) { - patch(oldEndVnode, newEndVnode); + patch(oldEndVnode, newEndVnode, renderer); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; } else if (isSameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right - patch(oldStartVnode, newEndVnode); - insertNode(oldStartVnode.elm!, parent, nextSibling(oldEndVnode.elm!)); + patch(oldStartVnode, newEndVnode, renderer); + insertNode( + oldStartVnode.elm!, + parent, + renderer.nextSibling(oldEndVnode.elm!), + renderer + ); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; } else if (isSameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left - patch(oldEndVnode, newStartVnode); - insertNode(newStartVnode.elm!, parent, oldStartVnode.elm!); + patch(oldEndVnode, newStartVnode, renderer); + insertNode(newStartVnode.elm!, parent, oldStartVnode.elm!, renderer); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { @@ -626,16 +670,16 @@ function updateDynamicChildren(oldCh: VNodes, newCh: VNodes, parent: ParentNode) idxInOld = oldKeyToIdx[newStartVnode.key!]; if (isUndefined(idxInOld)) { // New element - mount(newStartVnode, parent, oldStartVnode.elm!); + mount(newStartVnode, parent, renderer, oldStartVnode.elm!); newStartVnode = newCh[++newStartIdx]; } else { elmToMove = oldCh[idxInOld]; if (isVNode(elmToMove)) { if (elmToMove.sel !== newStartVnode.sel) { // New element - mount(newStartVnode, parent, oldStartVnode.elm!); + mount(newStartVnode, parent, renderer, oldStartVnode.elm!); } else { - patch(elmToMove, newStartVnode); + patch(elmToMove, newStartVnode, renderer); // Delete the old child, but copy the array since it is read-only. // The `oldCh` will be GC'ed after `updateDynamicChildren` is complete, // so we only care about the `oldCh` object inside this function. @@ -648,7 +692,7 @@ function updateDynamicChildren(oldCh: VNodes, newCh: VNodes, parent: ParentNode) // We've already cloned at least once, so it's no longer read-only (oldCh as any[])[idxInOld] = undefined; - insertNode(elmToMove.elm!, parent, oldStartVnode.elm!); + insertNode(elmToMove.elm!, parent, oldStartVnode.elm!, renderer); } } newStartVnode = newCh[++newStartIdx]; @@ -665,27 +709,27 @@ function updateDynamicChildren(oldCh: VNodes, newCh: VNodes, parent: ParentNode) n = newCh[++i]; } while (!isVNode(n) && i < newChEnd); before = isVNode(n) ? n.elm : null; - mountVNodes(newCh, parent, before, newStartIdx, newEndIdx + 1); + mountVNodes(newCh, parent, renderer, before, newStartIdx, newEndIdx + 1); } else { - unmountVNodes(oldCh, parent, true, oldStartIdx, oldEndIdx + 1); + unmountVNodes(oldCh, parent, renderer, true, oldStartIdx, oldEndIdx + 1); } } } -function updateStaticChildren(c1: VNodes, c2: VNodes, parent: ParentNode) { +function updateStaticChildren(c1: VNodes, c2: VNodes, parent: ParentNode, renderer: RendererAPI) { const c1Length = c1.length; const c2Length = c2.length; if (c1Length === 0) { // the old list is empty, we can directly insert anything new - mountVNodes(c2, parent, null); + mountVNodes(c2, parent, renderer, null); return; } if (c2Length === 0) { // the old list is nonempty and the new list is empty so we can directly remove all old nodes // this is the case in which the dynamic children of an if-directive should be removed - unmountVNodes(c1, parent, true); + unmountVNodes(c1, parent, renderer, true); return; } @@ -700,14 +744,14 @@ function updateStaticChildren(c1: VNodes, c2: VNodes, parent: ParentNode) { if (isVNode(n1)) { if (isVNode(n2)) { // both vnodes are equivalent, and we just need to patch them - patch(n1, n2); + patch(n1, n2, renderer); anchor = n2.elm!; } else { // removing the old vnode since the new one is null - unmount(n1, parent, true); + unmount(n1, parent, renderer, true); } } else if (isVNode(n2)) { - mount(n2, parent, anchor); + mount(n2, parent, renderer, anchor); anchor = n2.elm!; } } diff --git a/packages/@lwc/engine-core/src/framework/stylesheet.ts b/packages/@lwc/engine-core/src/framework/stylesheet.ts index 270ef25dde..5fe85e2dce 100644 --- a/packages/@lwc/engine-core/src/framework/stylesheet.ts +++ b/packages/@lwc/engine-core/src/framework/stylesheet.ts @@ -6,8 +6,6 @@ */ import { ArrayJoin, ArrayPush, isArray, isNull, isUndefined, KEY__SCOPED_CSS } from '@lwc/shared'; -import { getClassList, removeAttribute, setAttribute, ssr, insertStylesheet } from '../renderer'; - import api from './api'; import { RenderMode, ShadowMode, VM } from './vm'; import { Template } from './template'; @@ -50,7 +48,13 @@ function createInlineStyleVNode(content: string): VNode { } export function updateStylesheetToken(vm: VM, template: Template) { - const { elm, context, renderMode, shadowMode } = vm; + const { + elm, + context, + renderMode, + shadowMode, + renderer: { getClassList, removeAttribute, setAttribute }, + } = vm; const { stylesheets: newStylesheets, stylesheetToken: newStylesheetToken } = template; const isSyntheticShadow = renderMode === RenderMode.Shadow && shadowMode === ShadowMode.Synthetic; @@ -196,7 +200,11 @@ function getNearestNativeShadowComponent(vm: VM): VM | null { } export function createStylesheet(vm: VM, stylesheets: string[]): VNode | null { - const { renderMode, shadowMode } = vm; + const { + renderMode, + shadowMode, + renderer: { ssr, insertStylesheet }, + } = vm; if (renderMode === RenderMode.Shadow && shadowMode === ShadowMode.Synthetic) { for (let i = 0; i < stylesheets.length; i++) { insertStylesheet(stylesheets[i]); diff --git a/packages/@lwc/engine-core/src/framework/upgradable-element.ts b/packages/@lwc/engine-core/src/framework/upgradable-element.ts index da601571b6..6ae78395f7 100644 --- a/packages/@lwc/engine-core/src/framework/upgradable-element.ts +++ b/packages/@lwc/engine-core/src/framework/upgradable-element.ts @@ -5,11 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ import { isUndefined, isFunction } from '@lwc/shared'; -import { - getCustomElement, - defineCustomElement, - HTMLElement as RendererHTMLElement, -} from '../renderer'; +import type { RendererAPI } from './renderer'; type UpgradeCallback = (elm: HTMLElement) => void; @@ -18,8 +14,14 @@ interface UpgradableCustomElementConstructor extends CustomElementConstructor { } export function getUpgradableConstructor( - tagName: string + tagName: string, + renderer: RendererAPI ): CustomElementConstructor | UpgradableCustomElementConstructor { + const { + getCustomElement, + HTMLElementExported: RendererHTMLElement, + defineCustomElement, + } = renderer; // Should never get a tag with upper case letter at this point, the compiler should // produce only tags with lowercase letters // But, for backwards compatibility, we will lower case the tagName diff --git a/packages/@lwc/engine-core/src/framework/vm.ts b/packages/@lwc/engine-core/src/framework/vm.ts index f41fe339d7..ee9f43f09a 100644 --- a/packages/@lwc/engine-core/src/framework/vm.ts +++ b/packages/@lwc/engine-core/src/framework/vm.ts @@ -19,9 +19,9 @@ import { isUndefined, } from '@lwc/shared'; -import { isSyntheticShadowDefined, ssr, remove, isNativeShadowDefined } from '../renderer'; import { addErrorComponentStack } from '../shared/error'; +import { HostNode, HostElement, RendererAPI } from './renderer'; import { renderComponent, markComponentAsDirty, getTemplateReactiveObserver } from './component'; import { addCallbackToNextTick, EmptyArray, EmptyObject } from './utils'; import { invokeServiceHook, Services } from './services'; @@ -43,8 +43,6 @@ import { AccessorReactiveObserver } from './decorators/api'; import { removeActiveVM } from './hot-swaps'; import { VNodes, VCustomElement, VNode, VNodeType } from './vnodes'; -import type { HostNode, HostElement } from '../renderer'; - type ShadowRootMode = 'open' | 'closed'; export interface TemplateCache { @@ -164,6 +162,9 @@ export interface VM { /** Hook invoked whenever a method is called on the component (life-cycle hooks, public * properties and event handlers). This hook is used by Locker. */ callHook: (cmp: LightningElement | undefined, fn: (...args: any[]) => any, args?: any[]) => any; + /** + * Renderer API */ + renderer: RendererAPI; } type VMAssociable = HostNode | LightningElement; @@ -266,6 +267,7 @@ function getNearestShadowAncestor(vm: VM): VM | null { export function createVM( elm: HostElement, ctor: LightningElementConstructor, + renderer: RendererAPI, options: { mode: ShadowRootMode; owner: VM | null; @@ -297,7 +299,6 @@ export function createVM( hydrated: Boolean(hydrated), renderMode: def.renderMode, - context: { stylesheetToken: undefined, hasTokenInClass: undefined, @@ -321,9 +322,11 @@ export function createVM( callHook, setHook, getHook, + + renderer, }; - vm.shadowMode = computeShadowMode(vm); + vm.shadowMode = computeShadowMode(vm, renderer); vm.tro = getTemplateReactiveObserver(vm); if (process.env.NODE_ENV !== 'production') { @@ -346,8 +349,9 @@ export function createVM( return vm; } -function computeShadowMode(vm: VM) { +function computeShadowMode(vm: VM, renderer: RendererAPI) { const { def } = vm; + const { isSyntheticShadowDefined, isNativeShadowDefined } = renderer; let shadowMode; if (isSyntheticShadowDefined) { @@ -431,7 +435,7 @@ function rehydrate(vm: VM) { } function patchShadowRoot(vm: VM, newCh: VNodes) { - const { renderRoot, children: oldCh } = vm; + const { renderRoot, children: oldCh, renderer } = vm; // caching the new children collection vm.children = newCh; @@ -449,7 +453,7 @@ function patchShadowRoot(vm: VM, newCh: VNodes) { }, () => { // job - patchChildren(oldCh, newCh, renderRoot); + patchChildren(oldCh, newCh, renderRoot, renderer); }, () => { // post @@ -471,6 +475,7 @@ function patchShadowRoot(vm: VM, newCh: VNodes) { export function runRenderedCallback(vm: VM) { const { def: { renderedCallback }, + renderer: { ssr }, } = vm; if (isTrue(ssr)) { @@ -649,7 +654,11 @@ function recursivelyDisconnectChildren(vnodes: VNodes) { // into snabbdom. Especially useful when the reset is a consequence of an error, in which case the // children VNodes might not be representing the current state of the DOM. export function resetComponentRoot(vm: VM) { - const { children, renderRoot } = vm; + const { + children, + renderRoot, + renderer: { remove }, + } = vm; for (let i = 0, len = children.length; i < len; i++) { const child = children[i]; @@ -665,6 +674,9 @@ export function resetComponentRoot(vm: VM) { } export function scheduleRehydration(vm: VM) { + const { + renderer: { ssr }, + } = vm; if (isTrue(ssr) || isTrue(vm.isScheduled)) { return; } diff --git a/packages/@lwc/engine-core/src/framework/vnodes.ts b/packages/@lwc/engine-core/src/framework/vnodes.ts index 0a886d3fc9..e93fc611f7 100644 --- a/packages/@lwc/engine-core/src/framework/vnodes.ts +++ b/packages/@lwc/engine-core/src/framework/vnodes.ts @@ -5,7 +5,8 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import { VM } from './vm'; +import type { VM } from './vm'; +import type { RendererAPI } from './renderer'; export type Key = string | number; @@ -74,6 +75,7 @@ export interface VNodeData { readonly context?: Readonly>>>; readonly on?: Readonly any>>; readonly svg?: boolean; + readonly renderer?: RendererAPI; } export interface VElementData extends VNodeData { diff --git a/packages/@lwc/engine-core/src/framework/wiring.ts b/packages/@lwc/engine-core/src/framework/wiring.ts index 11bd0c6862..0e85b1abc7 100644 --- a/packages/@lwc/engine-core/src/framework/wiring.ts +++ b/packages/@lwc/engine-core/src/framework/wiring.ts @@ -13,7 +13,6 @@ import { noop, } from '@lwc/shared'; import featureFlags from '@lwc/features'; -import { dispatchEvent } from '../renderer'; import { LightningElement } from './base-lightning-element'; import { componentValueMutated, ReactiveObserver } from './mutation-tracker'; import { runWithBoundaryProtection, VMState, VM } from './vm'; @@ -128,6 +127,7 @@ function createContextWatcher( const { elm, context: { wiredConnecting, wiredDisconnecting }, + renderer: { dispatchEvent }, } = vm; // waiting for the component to be connected to formally request the context via the token ArrayPush.call(wiredConnecting, () => { diff --git a/packages/@lwc/engine-core/src/renderer.ts b/packages/@lwc/engine-core/src/renderer.ts deleted file mode 100644 index 002d4df0c3..0000000000 --- a/packages/@lwc/engine-core/src/renderer.ts +++ /dev/null @@ -1,276 +0,0 @@ -/* - * 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 - */ -export type HostNode = any; -export type HostElement = any; - -type N = HostNode; -type E = HostElement; - -// -// Primitives -// - -export let ssr: boolean; -export function setSsr(ssrImpl: boolean) { - ssr = ssrImpl; -} - -export let isNativeShadowDefined: boolean; -export function setIsNativeShadowDefined(isNativeShadowDefinedImpl: boolean) { - isNativeShadowDefined = isNativeShadowDefinedImpl; -} - -export let isSyntheticShadowDefined: boolean; -export function setIsSyntheticShadowDefined(isSyntheticShadowDefinedImpl: boolean) { - isSyntheticShadowDefined = isSyntheticShadowDefinedImpl; -} - -type HTMLElementType = typeof HTMLElement; -let HTMLElementExported: HTMLElementType; -export { HTMLElementExported as HTMLElement }; -export function setHTMLElement(HTMLElementImpl: HTMLElementType) { - HTMLElementExported = HTMLElementImpl; -} - -// -// Functions -// - -type isHydratingFunc = () => boolean; -export let isHydrating: isHydratingFunc; -export function setIsHydrating(isHydratingImpl: isHydratingFunc) { - isHydrating = isHydratingImpl; -} - -type insertFunc = (node: N, parent: E, anchor: N | null) => void; -export let insert: insertFunc; -export function setInsert(insertImpl: insertFunc) { - insert = insertImpl; -} - -type removeFunc = (node: N, parent: E) => void; -export let remove: removeFunc; -export function setRemove(removeImpl: removeFunc) { - remove = removeImpl; -} - -type createElementFunc = (tagName: string, namespace?: string) => E; -export let createElement: createElementFunc; -export function setCreateElement(createElementImpl: createElementFunc) { - createElement = createElementImpl; -} - -type createTextFunc = (content: string) => N; -export let createText: createTextFunc; -export function setCreateText(createTextImpl: createTextFunc) { - createText = createTextImpl; -} - -type createCommentFunc = (content: string) => N; -export let createComment: createCommentFunc; -export function setCreateComment(createCommentImpl: createCommentFunc) { - createComment = createCommentImpl; -} - -type nextSiblingFunc = (node: N) => N | null; -export let nextSibling: nextSiblingFunc; -export function setNextSibling(nextSiblingImpl: nextSiblingFunc) { - nextSibling = nextSiblingImpl; -} - -type attachShadowFunc = (element: E, options: ShadowRootInit) => N; -export let attachShadow: attachShadowFunc; -export function setAttachShadow(attachShadowImpl: attachShadowFunc) { - attachShadow = attachShadowImpl; -} - -type getPropertyFunc = (node: N, key: string) => any; -export let getProperty: getPropertyFunc; -export function setGetProperty(getPropertyImpl: getPropertyFunc) { - getProperty = getPropertyImpl; -} - -type setPropertyFunc = (node: N, key: string, value: any) => void; -export let setProperty: setPropertyFunc; -export function setSetProperty(setPropertyImpl: setPropertyFunc) { - setProperty = setPropertyImpl; -} - -type setTextFunc = (node: N, content: string) => void; -export let setText: setTextFunc; -export function setSetText(setTextImpl: setTextFunc) { - setText = setTextImpl; -} - -type getAttributeFunc = (element: E, name: string, namespace?: string | null) => string | null; -export let getAttribute: getAttributeFunc; -export function setGetAttribute(getAttributeImpl: getAttributeFunc) { - getAttribute = getAttributeImpl; -} - -type setAttributeFunc = ( - element: E, - name: string, - value: string, - namespace?: string | null -) => void; -export let setAttribute: setAttributeFunc; -export function setSetAttribute(setAttributeImpl: setAttributeFunc) { - setAttribute = setAttributeImpl; -} - -type removeAttributeFunc = (element: E, name: string, namespace?: string | null) => void; -export let removeAttribute: removeAttributeFunc; -export function setRemoveAttribute(removeAttributeImpl: removeAttributeFunc) { - removeAttribute = removeAttributeImpl; -} - -type addEventListenerFunc = ( - target: N, - type: string, - callback: (event: Event) => any, - options?: AddEventListenerOptions | boolean -) => void; -export let addEventListener: addEventListenerFunc; -export function setAddEventListener(addEventListenerImpl: addEventListenerFunc) { - addEventListener = addEventListenerImpl; -} - -type removeEventListenerFunc = ( - target: N, - type: string, - callback: (event: Event) => any, - options?: EventListenerOptions | boolean -) => void; -export let removeEventListener: removeEventListenerFunc; -export function setRemoveEventListener(removeEventListenerImpl: removeEventListenerFunc) { - removeEventListener = removeEventListenerImpl; -} - -type dispatchEventFunc = (target: N, event: Event) => boolean; -export let dispatchEvent: dispatchEventFunc; -export function setDispatchEvent(dispatchEventImpl: dispatchEventFunc) { - dispatchEvent = dispatchEventImpl; -} - -type getClassListFunc = (element: E) => DOMTokenList; -export let getClassList: getClassListFunc; -export function setGetClassList(getClassListImpl: getClassListFunc) { - getClassList = getClassListImpl; -} - -type setCSSStylePropertyFunc = ( - element: E, - name: string, - value: string, - important: boolean -) => void; -export let setCSSStyleProperty: setCSSStylePropertyFunc; -export function setSetCSSStyleProperty(setCSSStylePropertyImpl: setCSSStylePropertyFunc) { - setCSSStyleProperty = setCSSStylePropertyImpl; -} - -type getBoundingClientRectFunc = (element: E) => ClientRect; -export let getBoundingClientRect: getBoundingClientRectFunc; -export function setGetBoundingClientRect(getBoundingClientRectImpl: getBoundingClientRectFunc) { - getBoundingClientRect = getBoundingClientRectImpl; -} - -type querySelectorFunc = (element: E, selectors: string) => E | null; -export let querySelector: querySelectorFunc; -export function setQuerySelector(querySelectorImpl: querySelectorFunc) { - querySelector = querySelectorImpl; -} - -type querySelectorAllFunc = (element: E, selectors: string) => NodeList; -export let querySelectorAll: querySelectorAllFunc; -export function setQuerySelectorAll(querySelectorAllImpl: querySelectorAllFunc) { - querySelectorAll = querySelectorAllImpl; -} - -type getElementsByTagNameFunc = (element: E, tagNameOrWildCard: string) => HTMLCollection; -export let getElementsByTagName: getElementsByTagNameFunc; -export function setGetElementsByTagName(getElementsByTagNameImpl: getElementsByTagNameFunc) { - getElementsByTagName = getElementsByTagNameImpl; -} - -type getElementsByClassNameFunc = (element: E, names: string) => HTMLCollection; -export let getElementsByClassName: getElementsByClassNameFunc; -export function setGetElementsByClassName(getElementsByClassNameImpl: getElementsByClassNameFunc) { - getElementsByClassName = getElementsByClassNameImpl; -} - -type getChildrenFunc = (element: E) => HTMLCollection; -export let getChildren: getChildrenFunc; -export function setGetChildren(getChildrenImpl: getChildrenFunc) { - getChildren = getChildrenImpl; -} - -type getChildNodesFunc = (element: E) => NodeList; -export let getChildNodes: getChildNodesFunc; -export function setGetChildNodes(getChildNodesImpl: getChildNodesFunc) { - getChildNodes = getChildNodesImpl; -} - -type getFirstChildFunc = (element: E) => N | null; -export let getFirstChild: getFirstChildFunc; -export function setGetFirstChild(getFirstChildImpl: getFirstChildFunc) { - getFirstChild = getFirstChildImpl; -} - -type getFirstElementChildFunc = (element: E) => E | null; -export let getFirstElementChild: getFirstElementChildFunc; -export function setGetFirstElementChild(getFirstElementChildImpl: getFirstElementChildFunc) { - getFirstElementChild = getFirstElementChildImpl; -} - -type getLastChildFunc = (element: E) => N | null; -export let getLastChild: getLastChildFunc; -export function setGetLastChild(getLastChildImpl: getLastChildFunc) { - getLastChild = getLastChildImpl; -} - -type getLastElementChildFunc = (element: E) => E | null; -export let getLastElementChild: getLastElementChildFunc; -export function setGetLastElementChild(getLastElementChildImpl: getLastElementChildFunc) { - getLastElementChild = getLastElementChildImpl; -} - -type isConnectedFunc = (node: N) => boolean; -export let isConnected: isConnectedFunc; -export function setIsConnected(isConnectedImpl: isConnectedFunc) { - isConnected = isConnectedImpl; -} -type insertStylesheetFunc = (content: string, target?: ShadowRoot) => void; -export let insertStylesheet: insertStylesheetFunc; -export function setInsertStylesheet(insertStylesheetImpl: insertStylesheetFunc) { - insertStylesheet = insertStylesheetImpl; -} - -type assertInstanceOfHTMLElementFunc = (elm: any, msg: string) => void; -export let assertInstanceOfHTMLElement: assertInstanceOfHTMLElementFunc; -export function setAssertInstanceOfHTMLElement( - assertInstanceOfHTMLElementImpl: assertInstanceOfHTMLElementFunc -) { - assertInstanceOfHTMLElement = assertInstanceOfHTMLElementImpl; -} - -type defineCustomElementFunc = ( - name: string, - constructor: CustomElementConstructor, - options?: ElementDefinitionOptions -) => void; -export let defineCustomElement: defineCustomElementFunc; -export function setDefineCustomElement(defineCustomElementImpl: defineCustomElementFunc) { - defineCustomElement = defineCustomElementImpl; -} - -type getCustomElementFunc = (name: string) => CustomElementConstructor | undefined; -export let getCustomElement: getCustomElementFunc; -export function setGetCustomElement(getCustomElementImpl: getCustomElementFunc) { - getCustomElement = getCustomElementImpl; -} diff --git a/packages/@lwc/engine-dom/src/apis/build-custom-element-constructor.ts b/packages/@lwc/engine-dom/src/apis/build-custom-element-constructor.ts index f3851fb123..f814501c38 100644 --- a/packages/@lwc/engine-dom/src/apis/build-custom-element-constructor.ts +++ b/packages/@lwc/engine-dom/src/apis/build-custom-element-constructor.ts @@ -12,6 +12,7 @@ import { getComponentHtmlPrototype, LightningElement, } from '@lwc/engine-core'; +import { renderer } from '../renderer'; import { hydrateComponent } from './hydrate-component'; type ComponentConstructor = typeof LightningElement; @@ -62,7 +63,7 @@ export function buildCustomElementConstructor(Ctor: ComponentConstructor): HTMLE hydrateComponent(this, Ctor, {}); hydratedCustomElements.add(this); } else { - createVM(this, Ctor, { + createVM(this, Ctor, renderer, { mode: 'open', owner: null, tagName: this.tagName, diff --git a/packages/@lwc/engine-dom/src/apis/create-element.ts b/packages/@lwc/engine-dom/src/apis/create-element.ts index bdfbde0154..3c249c5c7d 100644 --- a/packages/@lwc/engine-dom/src/apis/create-element.ts +++ b/packages/@lwc/engine-dom/src/apis/create-element.ts @@ -12,6 +12,7 @@ import { LightningElement, getUpgradableConstructor, } from '@lwc/engine-core'; +import { renderer } from '../renderer'; // TODO [#2472]: Remove this workaround when appropriate. // eslint-disable-next-line lwc-internal/no-global-node @@ -93,7 +94,7 @@ export function createElement( ); } - const UpgradableConstructor = getUpgradableConstructor(sel); + const UpgradableConstructor = getUpgradableConstructor(sel, renderer); let wasComponentUpgraded: boolean = false; // the custom element from the registry is expecting an upgrade callback /** @@ -103,7 +104,7 @@ export function createElement( * an upgradable custom element. */ const element = new UpgradableConstructor((elm: HTMLElement) => { - createVM(elm, Ctor, { + createVM(elm, Ctor, renderer, { tagName: sel, mode: options.mode !== 'closed' ? 'open' : 'closed', owner: null, diff --git a/packages/@lwc/engine-dom/src/apis/hydrate-component.ts b/packages/@lwc/engine-dom/src/apis/hydrate-component.ts index 335ebc0aba..100ca4932b 100644 --- a/packages/@lwc/engine-dom/src/apis/hydrate-component.ts +++ b/packages/@lwc/engine-dom/src/apis/hydrate-component.ts @@ -13,6 +13,7 @@ import { getAssociatedVMIfPresent, } from '@lwc/engine-core'; import { isFunction, isNull, isObject } from '@lwc/shared'; +import { renderer } from '../renderer'; import { setIsHydrating } from '../renderer'; function resetShadowRootAndLightDom(element: Element, Ctor: typeof LightningElement) { @@ -32,7 +33,7 @@ function resetShadowRootAndLightDom(element: Element, Ctor: typeof LightningElem } function createVMWithProps(element: Element, Ctor: typeof LightningElement, props: object) { - const vm = createVM(element, Ctor, { + const vm = createVM(element, Ctor, renderer, { mode: 'open', owner: null, tagName: element.tagName.toLowerCase(), diff --git a/packages/@lwc/engine-dom/src/index.ts b/packages/@lwc/engine-dom/src/index.ts index f06f545b6e..fec06f876c 100644 --- a/packages/@lwc/engine-dom/src/index.ts +++ b/packages/@lwc/engine-dom/src/index.ts @@ -8,9 +8,6 @@ // Polyfills --------------------------------------------------------------------------------------- import './polyfills/aria-properties/main'; -// Renderer initialization ------------------------------------------------------------------------- -import './initializeRenderer'; - // Tests ------------------------------------------------------------------------------------------- import './testFeatureFlag.ts'; @@ -46,3 +43,4 @@ export { deprecatedBuildCustomElementConstructor as buildCustomElementConstructo export { createElement } from './apis/create-element'; export { isNodeFromTemplate } from './apis/is-node-from-template'; export { LightningElement } from './apis/lightning-element'; +export { renderer } from './renderer'; diff --git a/packages/@lwc/engine-dom/src/initializeRenderer.ts b/packages/@lwc/engine-dom/src/initializeRenderer.ts deleted file mode 100644 index 0629aad941..0000000000 --- a/packages/@lwc/engine-dom/src/initializeRenderer.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2020, 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 { - setAssertInstanceOfHTMLElement, - setAttachShadow, - setCreateComment, - setCreateElement, - setCreateText, - setDefineCustomElement, - setDispatchEvent, - setGetAttribute, - setGetBoundingClientRect, - setGetChildNodes, - setGetChildren, - setGetClassList, - setGetCustomElement, - setGetElementsByClassName, - setGetElementsByTagName, - setGetFirstChild, - setGetFirstElementChild, - setGetLastChild, - setGetLastElementChild, - setGetProperty, - setHTMLElement, - setInsert, - setIsConnected, - setIsHydrating, - setIsNativeShadowDefined, - setIsSyntheticShadowDefined, - setNextSibling, - setQuerySelector, - setQuerySelectorAll, - setRemove, - setRemoveAttribute, - setRemoveEventListener, - setSetAttribute, - setSetCSSStyleProperty, - setSetProperty, - setSetText, - setSsr, - setAddEventListener, - setInsertStylesheet, -} from '@lwc/engine-core'; - -import { - assertInstanceOfHTMLElement, - attachShadow, - createComment, - createElement, - createText, - defineCustomElement, - dispatchEvent, - getAttribute, - getBoundingClientRect, - getChildNodes, - getChildren, - getClassList, - getCustomElement, - getElementsByClassName, - getElementsByTagName, - getFirstChild, - getFirstElementChild, - getLastChild, - getLastElementChild, - getProperty, - HTMLElement, - insert, - isConnected, - isHydrating, - isNativeShadowDefined, - isSyntheticShadowDefined, - nextSibling, - querySelector, - querySelectorAll, - remove, - removeAttribute, - removeEventListener, - setAttribute, - setCSSStyleProperty, - setProperty, - setText, - ssr, - addEventListener, - insertStylesheet, -} from './renderer'; - -setAssertInstanceOfHTMLElement(assertInstanceOfHTMLElement); -setAttachShadow(attachShadow); -setCreateComment(createComment); -setCreateElement(createElement); -setCreateText(createText); -setDefineCustomElement(defineCustomElement); -setDispatchEvent(dispatchEvent); -setGetAttribute(getAttribute); -setGetBoundingClientRect(getBoundingClientRect); -setGetChildNodes(getChildNodes); -setGetChildren(getChildren); -setGetClassList(getClassList); -setGetCustomElement(getCustomElement); -setGetElementsByClassName(getElementsByClassName); -setGetElementsByTagName(getElementsByTagName); -setGetFirstChild(getFirstChild); -setGetFirstElementChild(getFirstElementChild); -setGetLastChild(getLastChild); -setGetLastElementChild(getLastElementChild); -setGetProperty(getProperty); -setHTMLElement(HTMLElement); -setInsert(insert); -setIsConnected(isConnected); -setIsHydrating(isHydrating); -setIsNativeShadowDefined(isNativeShadowDefined); -setIsSyntheticShadowDefined(isSyntheticShadowDefined); -setNextSibling(nextSibling); -setQuerySelector(querySelector); -setQuerySelectorAll(querySelectorAll); -setRemove(remove); -setRemoveAttribute(removeAttribute); -setRemoveEventListener(removeEventListener); -setSetAttribute(setAttribute); -setSetCSSStyleProperty(setCSSStyleProperty); -setSetProperty(setProperty); -setSetText(setText); -setSsr(ssr); -setAddEventListener(addEventListener); -setInsertStylesheet(insertStylesheet); diff --git a/packages/@lwc/engine-dom/src/renderer.ts b/packages/@lwc/engine-dom/src/renderer.ts index d2b947692a..313d642e1d 100644 --- a/packages/@lwc/engine-dom/src/renderer.ts +++ b/packages/@lwc/engine-dom/src/renderer.ts @@ -17,10 +17,10 @@ import { setPrototypeOf, StringToLowerCase, } from '@lwc/shared'; -export { insertStylesheet } from './styles'; +import { insertStylesheet } from './styles'; -export let getCustomElement: any; -export let defineCustomElement: any; +let getCustomElement: any; +let defineCustomElement: any; let HTMLElementConstructor; function isCustomElementRegistryAvailable() { @@ -30,7 +30,7 @@ function isCustomElementRegistryAvailable() { try { // dereference HTMLElement global because babel wraps globals in compat mode with a // _wrapNativeSuper() - // This is a problem because LWCUpgradableElement extends renderer.HTMLElement which does not + // This is a problem because LWCUpgradableElement extends renderer.HTMLElementExported which does not // get wrapped by babel. const HTMLElementAlias = HTMLElement; // In case we use compat mode with a modern browser, the compat mode transformation @@ -88,60 +88,60 @@ export function setIsHydrating(value: boolean) { hydrating = value; } -export const ssr: boolean = false; +const ssr: boolean = false; -export function isHydrating(): boolean { +function isHydrating(): boolean { return hydrating; } -export const isNativeShadowDefined: boolean = globalThis[KEY__IS_NATIVE_SHADOW_ROOT_DEFINED]; +const isNativeShadowDefined: boolean = globalThis[KEY__IS_NATIVE_SHADOW_ROOT_DEFINED]; export const isSyntheticShadowDefined: boolean = hasOwnProperty.call( Element.prototype, KEY__SHADOW_TOKEN ); -export function createElement(tagName: string, namespace?: string): Element { +function createElement(tagName: string, namespace?: string): Element { return isUndefined(namespace) ? document.createElement(tagName) : document.createElementNS(namespace, tagName); } -export function createText(content: string): Node { +function createText(content: string): Node { return document.createTextNode(content); } -export function createComment(content: string): Node { +function createComment(content: string): Node { return document.createComment(content); } -export function insert(node: Node, parent: Node, anchor: Node): void { +function insert(node: Node, parent: Node, anchor: Node): void { parent.insertBefore(node, anchor); } -export function remove(node: Node, parent: Node): void { +function remove(node: Node, parent: Node): void { parent.removeChild(node); } -export function nextSibling(node: Node): Node | null { +function nextSibling(node: Node): Node | null { return node.nextSibling; } -export function attachShadow(element: Element, options: ShadowRootInit): ShadowRoot { +function attachShadow(element: Element, options: ShadowRootInit): ShadowRoot { if (hydrating) { return element.shadowRoot!; } return element.attachShadow(options); } -export function setText(node: Node, content: string): void { +function setText(node: Node, content: string): void { node.nodeValue = content; } -export function getProperty(node: Node, key: string): any { +function getProperty(node: Node, key: string): any { return (node as any)[key]; } -export function setProperty(node: Node, key: string, value: any): void { +function setProperty(node: Node, key: string, value: any): void { if (process.env.NODE_ENV !== 'production') { if (node instanceof Element && !(key in node)) { // TODO [#1297]: Move this validation to the compiler @@ -158,17 +158,13 @@ export function setProperty(node: Node, key: string, value: any): void { (node as any)[key] = value; } -export function getAttribute( - element: Element, - name: string, - namespace?: string | null -): string | null { +function getAttribute(element: Element, name: string, namespace?: string | null): string | null { return isUndefined(namespace) ? element.getAttribute(name) : element.getAttributeNS(namespace, name); } -export function setAttribute( +function setAttribute( element: Element, name: string, value: string, @@ -179,7 +175,7 @@ export function setAttribute( : element.setAttributeNS(namespace, name, value); } -export function removeAttribute(element: Element, name: string, namespace?: string | null): void { +function removeAttribute(element: Element, name: string, namespace?: string | null): void { if (isUndefined(namespace)) { element.removeAttribute(name); } else { @@ -187,7 +183,7 @@ export function removeAttribute(element: Element, name: string, namespace?: stri } } -export function addEventListener( +function addEventListener( target: Node, type: string, callback: EventListener, @@ -196,7 +192,7 @@ export function addEventListener( target.addEventListener(type, callback, options); } -export function removeEventListener( +function removeEventListener( target: Node, type: string, callback: EventListener, @@ -205,15 +201,15 @@ export function removeEventListener( target.removeEventListener(type, callback, options); } -export function dispatchEvent(target: Node, event: Event): boolean { +function dispatchEvent(target: Node, event: Event): boolean { return target.dispatchEvent(event); } -export function getClassList(element: Element): DOMTokenList { +function getClassList(element: Element): DOMTokenList { return element.classList; } -export function setCSSStyleProperty( +function setCSSStyleProperty( element: Element, name: string, value: string, @@ -228,57 +224,98 @@ export function setCSSStyleProperty( ); } -export function getBoundingClientRect(element: Element): DOMRect { +function getBoundingClientRect(element: Element): DOMRect { return element.getBoundingClientRect(); } -export function querySelector(element: Element, selectors: string): Element | null { +function querySelector(element: Element, selectors: string): Element | null { return element.querySelector(selectors); } -export function querySelectorAll(element: Element, selectors: string): NodeList { +function querySelectorAll(element: Element, selectors: string): NodeList { return element.querySelectorAll(selectors); } -export function getElementsByTagName(element: Element, tagNameOrWildCard: string): HTMLCollection { +function getElementsByTagName(element: Element, tagNameOrWildCard: string): HTMLCollection { return element.getElementsByTagName(tagNameOrWildCard); } -export function getElementsByClassName(element: Element, names: string): HTMLCollection { +function getElementsByClassName(element: Element, names: string): HTMLCollection { return element.getElementsByClassName(names); } -export function getChildren(element: Element): HTMLCollection { +function getChildren(element: Element): HTMLCollection { return element.children; } -export function getChildNodes(element: Element): NodeList { +function getChildNodes(element: Element): NodeList { return element.childNodes; } -export function getFirstChild(element: Element): Node | null { +function getFirstChild(element: Element): Node | null { return element.firstChild; } -export function getFirstElementChild(element: Element): Element | null { +function getFirstElementChild(element: Element): Element | null { return element.firstElementChild; } -export function getLastChild(element: Element): Node | null { +function getLastChild(element: Element): Node | null { return element.lastChild; } -export function getLastElementChild(element: Element): Element | null { +function getLastElementChild(element: Element): Element | null { return element.lastElementChild; } -export function isConnected(node: Node): boolean { +function isConnected(node: Node): boolean { return node.isConnected; } -export function assertInstanceOfHTMLElement(elm: any, msg: string) { +function assertInstanceOfHTMLElement(elm: any, msg: string) { assert.invariant(elm instanceof HTMLElement, msg); } const HTMLElementExported = HTMLElementConstructor as typeof HTMLElement; -export { HTMLElementExported as HTMLElement }; + +export const renderer = { + ssr, + isNativeShadowDefined, + isSyntheticShadowDefined, + HTMLElementExported, + isHydrating, + insert, + remove, + createElement, + createText, + createComment, + nextSibling, + attachShadow, + getProperty, + setProperty, + setText, + getAttribute, + setAttribute, + removeAttribute, + addEventListener, + removeEventListener, + dispatchEvent, + getClassList, + setCSSStyleProperty, + getBoundingClientRect, + querySelector, + querySelectorAll, + getElementsByTagName, + getElementsByClassName, + getChildren, + getChildNodes, + getFirstChild, + getFirstElementChild, + getLastChild, + getLastElementChild, + isConnected, + insertStylesheet, + assertInstanceOfHTMLElement, + defineCustomElement, + getCustomElement, +}; diff --git a/packages/@lwc/engine-server/src/apis/render-component.ts b/packages/@lwc/engine-server/src/apis/render-component.ts index c8e1492eab..15046f693d 100644 --- a/packages/@lwc/engine-server/src/apis/render-component.ts +++ b/packages/@lwc/engine-server/src/apis/render-component.ts @@ -7,7 +7,7 @@ import { createVM, connectRootElement, LightningElement } from '@lwc/engine-core'; import { isString, isFunction, isObject, isNull } from '@lwc/shared'; -import { createElement } from '../renderer'; +import { renderer } from '../renderer'; import { serializeElement } from '../serializer'; import { HostElement, HostNodeType } from '../types'; @@ -44,8 +44,8 @@ export function renderComponent( ); } - const element = createElement(tagName); - createVM(element, Ctor, { + const element = renderer.createElement(tagName); + createVM(element, Ctor, renderer, { mode: 'open', owner: null, tagName, diff --git a/packages/@lwc/engine-server/src/index.ts b/packages/@lwc/engine-server/src/index.ts index 0c2df78fd3..2456635f70 100644 --- a/packages/@lwc/engine-server/src/index.ts +++ b/packages/@lwc/engine-server/src/index.ts @@ -7,9 +7,6 @@ // Polyfills --------------------------------------------------------------------------------------- import './polyfills'; -// Renderer initialization ------------------------------------------------------------------------- -import './initializeRenderer'; - // Engine-core public APIs ------------------------------------------------------------------------- export { createContextProvider, @@ -34,3 +31,4 @@ export { // Engine-server public APIs ----------------------------------------------------------------------- export { renderComponent } from './apis/render-component'; export { LightningElement } from './apis/lightning-element'; +export { renderer } from './renderer'; diff --git a/packages/@lwc/engine-server/src/initializeRenderer.ts b/packages/@lwc/engine-server/src/initializeRenderer.ts deleted file mode 100644 index 0629aad941..0000000000 --- a/packages/@lwc/engine-server/src/initializeRenderer.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2020, 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 { - setAssertInstanceOfHTMLElement, - setAttachShadow, - setCreateComment, - setCreateElement, - setCreateText, - setDefineCustomElement, - setDispatchEvent, - setGetAttribute, - setGetBoundingClientRect, - setGetChildNodes, - setGetChildren, - setGetClassList, - setGetCustomElement, - setGetElementsByClassName, - setGetElementsByTagName, - setGetFirstChild, - setGetFirstElementChild, - setGetLastChild, - setGetLastElementChild, - setGetProperty, - setHTMLElement, - setInsert, - setIsConnected, - setIsHydrating, - setIsNativeShadowDefined, - setIsSyntheticShadowDefined, - setNextSibling, - setQuerySelector, - setQuerySelectorAll, - setRemove, - setRemoveAttribute, - setRemoveEventListener, - setSetAttribute, - setSetCSSStyleProperty, - setSetProperty, - setSetText, - setSsr, - setAddEventListener, - setInsertStylesheet, -} from '@lwc/engine-core'; - -import { - assertInstanceOfHTMLElement, - attachShadow, - createComment, - createElement, - createText, - defineCustomElement, - dispatchEvent, - getAttribute, - getBoundingClientRect, - getChildNodes, - getChildren, - getClassList, - getCustomElement, - getElementsByClassName, - getElementsByTagName, - getFirstChild, - getFirstElementChild, - getLastChild, - getLastElementChild, - getProperty, - HTMLElement, - insert, - isConnected, - isHydrating, - isNativeShadowDefined, - isSyntheticShadowDefined, - nextSibling, - querySelector, - querySelectorAll, - remove, - removeAttribute, - removeEventListener, - setAttribute, - setCSSStyleProperty, - setProperty, - setText, - ssr, - addEventListener, - insertStylesheet, -} from './renderer'; - -setAssertInstanceOfHTMLElement(assertInstanceOfHTMLElement); -setAttachShadow(attachShadow); -setCreateComment(createComment); -setCreateElement(createElement); -setCreateText(createText); -setDefineCustomElement(defineCustomElement); -setDispatchEvent(dispatchEvent); -setGetAttribute(getAttribute); -setGetBoundingClientRect(getBoundingClientRect); -setGetChildNodes(getChildNodes); -setGetChildren(getChildren); -setGetClassList(getClassList); -setGetCustomElement(getCustomElement); -setGetElementsByClassName(getElementsByClassName); -setGetElementsByTagName(getElementsByTagName); -setGetFirstChild(getFirstChild); -setGetFirstElementChild(getFirstElementChild); -setGetLastChild(getLastChild); -setGetLastElementChild(getLastElementChild); -setGetProperty(getProperty); -setHTMLElement(HTMLElement); -setInsert(insert); -setIsConnected(isConnected); -setIsHydrating(isHydrating); -setIsNativeShadowDefined(isNativeShadowDefined); -setIsSyntheticShadowDefined(isSyntheticShadowDefined); -setNextSibling(nextSibling); -setQuerySelector(querySelector); -setQuerySelectorAll(querySelectorAll); -setRemove(remove); -setRemoveAttribute(removeAttribute); -setRemoveEventListener(removeEventListener); -setSetAttribute(setAttribute); -setSetCSSStyleProperty(setCSSStyleProperty); -setSetProperty(setProperty); -setSetText(setText); -setSsr(ssr); -setAddEventListener(addEventListener); -setInsertStylesheet(insertStylesheet); diff --git a/packages/@lwc/engine-server/src/renderer.ts b/packages/@lwc/engine-server/src/renderer.ts index 28c5401e3e..0ed560880b 100644 --- a/packages/@lwc/engine-server/src/renderer.ts +++ b/packages/@lwc/engine-server/src/renderer.ts @@ -59,23 +59,19 @@ class HTMLElementImpl { } } -export const ssr: boolean = true; +const ssr: boolean = true; -export function setIsHydrating(_value: boolean) { - /* No-op in SSR */ -} - -export function isHydrating(): boolean { +function isHydrating(): boolean { return false; } -export const isNativeShadowDefined: boolean = false; -export const isSyntheticShadowDefined: boolean = false; +const isNativeShadowDefined: boolean = false; +const isSyntheticShadowDefined: boolean = false; type N = HostNode; type E = HostElement; -export function insert(node: N, parent: E, anchor: N | null) { +function insert(node: N, parent: E, anchor: N | null) { if (node.parent !== null && node.parent !== parent) { const nodeIndex = node.parent.children.indexOf(node); node.parent.children.splice(nodeIndex, 1); @@ -91,14 +87,12 @@ export function insert(node: N, parent: E, anchor: N | null) { } } -export function remove(node: N, parent: E) { +function remove(node: N, parent: E) { const nodeIndex = parent.children.indexOf(node); parent.children.splice(nodeIndex, 1); } -export { createElement }; - -export function createText(content: string): HostNode { +function createText(content: string): HostNode { return { type: HostNodeType.Text, value: String(content), @@ -106,7 +100,7 @@ export function createText(content: string): HostNode { }; } -export function createComment(content: string): HostNode { +function createComment(content: string): HostNode { return { type: HostNodeType.Comment, value: content, @@ -114,7 +108,7 @@ export function createComment(content: string): HostNode { }; } -export function nextSibling(node: N) { +function nextSibling(node: N) { const { parent } = node; if (isNull(parent)) { @@ -125,7 +119,7 @@ export function nextSibling(node: N) { return (parent.children[nodeIndex + 1] as HostNode) || null; } -export function attachShadow(element: E, config: ShadowRootInit) { +function attachShadow(element: E, config: ShadowRootInit) { element.shadowRoot = { type: HostNodeType.ShadowRoot, children: [], @@ -136,7 +130,7 @@ export function attachShadow(element: E, config: ShadowRootInit) { return element.shadowRoot as any; } -export function getProperty(node: N, key: string) { +function getProperty(node: N, key: string) { if (key in node) { return (node as any)[key]; } @@ -167,7 +161,7 @@ export function getProperty(node: N, key: string) { } } -export function setProperty(node: N, key: string, value: any): void { +function setProperty(node: N, key: string, value: any): void { if (key in node) { return ((node as any)[key] = value); } @@ -213,7 +207,7 @@ export function setProperty(node: N, key: string, value: any): void { } } -export function setText(node: N, content: string) { +function setText(node: N, content: string) { if (node.type === HostNodeType.Text) { node.value = content; } else if (node.type === HostNodeType.Element) { @@ -227,19 +221,14 @@ export function setText(node: N, content: string) { } } -export function getAttribute(element: E, name: string, namespace: string | null = null) { +function getAttribute(element: E, name: string, namespace: string | null = null) { const attribute = element.attributes.find( (attr) => attr.name === name && attr.namespace === namespace ); return attribute ? attribute.value : null; } -export function setAttribute( - element: E, - name: string, - value: string, - namespace: string | null = null -) { +function setAttribute(element: E, name: string, value: string, namespace: string | null = null) { const attribute = element.attributes.find( (attr) => attr.name === name && attr.namespace === namespace ); @@ -259,13 +248,13 @@ export function setAttribute( } } -export function removeAttribute(element: E, name: string, namespace?: string | null) { +function removeAttribute(element: E, name: string, namespace?: string | null) { element.attributes = element.attributes.filter( (attr) => attr.name !== name && attr.namespace !== namespace ); } -export function getClassList(element: E) { +function getClassList(element: E) { function getClassAttribute(): HostAttribute { let classAttribute = element.attributes.find( (attr) => attr.name === 'class' && isNull(attr.namespace) @@ -301,7 +290,7 @@ export function getClassList(element: E) { } as DOMTokenList; } -export function setCSSStyleProperty(element: E, name: string, value: string, important: boolean) { +function setCSSStyleProperty(element: E, name: string, value: string, important: boolean) { const styleAttribute = element.attributes.find( (attr) => attr.name === 'style' && isNull(attr.namespace) ); @@ -319,16 +308,16 @@ export function setCSSStyleProperty(element: E, name: string, value: string, imp } } -export function isConnected(node: HostNode) { +function isConnected(node: HostNode) { return !isNull(node.parent); } // Noop on SSR (for now). This need to be reevaluated whenever we will implement support for // synthetic shadow. -export const insertStylesheet = noop as (content: string, target: any) => void; +const insertStylesheet = noop as (content: string, target: any) => void; // Noop on SSR. -export const addEventListener = noop as ( +const addEventListener = noop as ( target: HostNode, type: string, callback: EventListener, @@ -336,56 +325,47 @@ export const addEventListener = noop as ( ) => void; // Noop on SSR. -export const removeEventListener = noop as ( +const removeEventListener = noop as ( target: HostNode, type: string, callback: EventListener, options?: AddEventListenerOptions | boolean ) => void; -export const dispatchEvent = unsupportedMethod('dispatchEvent') as ( - target: any, - event: Event -) => boolean; -export const getBoundingClientRect = unsupportedMethod('getBoundingClientRect') as ( +const dispatchEvent = unsupportedMethod('dispatchEvent') as (target: any, event: Event) => boolean; +const getBoundingClientRect = unsupportedMethod('getBoundingClientRect') as ( element: HostElement ) => DOMRect; -export const querySelector = unsupportedMethod('querySelector') as ( +const querySelector = unsupportedMethod('querySelector') as ( element: HostElement, selectors: string ) => Element | null; -export const querySelectorAll = unsupportedMethod('querySelectorAll') as ( +const querySelectorAll = unsupportedMethod('querySelectorAll') as ( element: HostElement, selectors: string ) => NodeList; -export const getElementsByTagName = unsupportedMethod('getElementsByTagName') as ( +const getElementsByTagName = unsupportedMethod('getElementsByTagName') as ( element: HostElement, tagNameOrWildCard: string ) => HTMLCollection; -export const getElementsByClassName = unsupportedMethod('getElementsByClassName') as ( +const getElementsByClassName = unsupportedMethod('getElementsByClassName') as ( element: HostElement, names: string ) => HTMLCollection; -export const getChildren = unsupportedMethod('getChildren') as ( - element: HostElement -) => HTMLCollection; -export const getChildNodes = unsupportedMethod('getChildNodes') as ( - element: HostElement -) => NodeList; -export const getFirstChild = unsupportedMethod('getFirstChild') as ( +const getChildren = unsupportedMethod('getChildren') as (element: HostElement) => HTMLCollection; +const getChildNodes = unsupportedMethod('getChildNodes') as (element: HostElement) => NodeList; +const getFirstChild = unsupportedMethod('getFirstChild') as ( element: HostElement ) => HostNode | null; -export const getFirstElementChild = unsupportedMethod('getFirstElementChild') as ( +const getFirstElementChild = unsupportedMethod('getFirstElementChild') as ( element: HostElement ) => HostElement | null; -export const getLastChild = unsupportedMethod('getLastChild') as ( - element: HostElement -) => HostNode | null; -export const getLastElementChild = unsupportedMethod('getLastElementChild') as ( +const getLastChild = unsupportedMethod('getLastChild') as (element: HostElement) => HostNode | null; +const getLastElementChild = unsupportedMethod('getLastElementChild') as ( element: HostElement ) => HostElement | null; -export function defineCustomElement( +function defineCustomElement( name: string, constructor: CustomElementConstructor, _options?: ElementDefinitionOptions @@ -393,12 +373,53 @@ export function defineCustomElement( registerCustomElement(name, constructor); } -export function getCustomElement(name: string): CustomElementConstructor | undefined { +function getCustomElement(name: string): CustomElementConstructor | undefined { return registry[name]; } const HTMLElementExported = HTMLElementImpl as typeof HTMLElement; -export { HTMLElementExported as HTMLElement }; /* noop */ -export const assertInstanceOfHTMLElement = noop as (elm: any, msg: string) => void; +const assertInstanceOfHTMLElement = noop as (elm: any, msg: string) => void; + +export const renderer = { + ssr, + isNativeShadowDefined, + isSyntheticShadowDefined, + HTMLElementExported, + isHydrating, + insert, + remove, + createElement, + createText, + createComment, + nextSibling, + attachShadow, + getProperty, + setProperty, + setText, + getAttribute, + setAttribute, + removeAttribute, + addEventListener, + removeEventListener, + dispatchEvent, + getClassList, + setCSSStyleProperty, + getBoundingClientRect, + querySelector, + querySelectorAll, + getElementsByTagName, + getElementsByClassName, + getChildren, + getChildNodes, + getFirstChild, + getFirstElementChild, + getLastChild, + getLastElementChild, + isConnected, + insertStylesheet, + assertInstanceOfHTMLElement, + defineCustomElement, + getCustomElement, +};