Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simplify UpgradableConstructor use of native lifecycle callbacks #3862

Merged
merged 2 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions packages/@lwc/engine-core/src/framework/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,8 @@ export interface RendererAPI {
isConnected: (node: N) => boolean;
insertStylesheet: (content: string, target?: ShadowRoot) => void;
assertInstanceOfHTMLElement: (elm: any, msg: string) => void;
createCustomElement: (
tagName: string,
upgradeCallback: LifecycleCallback,
connectedCallback?: LifecycleCallback,
disconnectedCallback?: LifecycleCallback,
formAssociatedCallback?: LifecycleCallback,
formDisabledCallback?: LifecycleCallback,
formResetCallback?: LifecycleCallback,
formStateRestoreCallback?: LifecycleCallback
) => E;
defineCustomElement: (
tagName: string,
connectedCallback?: LifecycleCallback,
disconnectedCallback?: LifecycleCallback
) => void;
createCustomElement: (tagName: string, upgradeCallback: LifecycleCallback) => E;
defineCustomElement: (tagName: string) => void;
ownerDocument(elm: E): Document;
registerContextConsumer: (
element: E,
Expand Down
47 changes: 2 additions & 45 deletions packages/@lwc/engine-core/src/framework/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,20 @@ import {

import { logError } from '../shared/logger';
import { getComponentTag } from '../shared/format';
import { LifecycleCallback, RendererAPI } from './renderer';
import { RendererAPI } from './renderer';
import { EmptyArray } from './utils';
import { markComponentAsDirty } from './component';
import { getScopeTokenClass } from './stylesheet';
import { lockDomMutation, patchElementWithRestrictions, unlockDomMutation } from './restrictions';
import {
appendVM,
connectRootElement,
createVM,
disconnectRootElement,
getAssociatedVMIfPresent,
LwcDomMode,
removeVM,
RenderMode,
rerenderVM,
runConnectedCallback,
runFormAssociatedCallback,
runFormDisabledCallback,
runFormResetCallback,
runFormStateRestoreCallback,
ShadowMode,
VM,
VMState,
Expand Down Expand Up @@ -329,49 +323,12 @@ function mountCustomElement(
vm = createViewModelHook(elm, vnode, renderer);
};

let connectedCallback: LifecycleCallback | undefined;
let disconnectedCallback: LifecycleCallback | undefined;
let formAssociatedCallback: LifecycleCallback | undefined;
let formDisabledCallback: LifecycleCallback | undefined;
let formResetCallback: LifecycleCallback | undefined;
let formStateRestoreCallback: LifecycleCallback | undefined;

if (lwcRuntimeFlags.ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE) {
connectedCallback = (elm: HTMLElement) => {
connectRootElement(elm);
};
disconnectedCallback = (elm: HTMLElement) => {
disconnectRootElement(elm);
};
formAssociatedCallback = (elm: HTMLElement) => {
runFormAssociatedCallback(elm);
};
formDisabledCallback = (elm: HTMLElement) => {
runFormDisabledCallback(elm);
};
formResetCallback = (elm: HTMLElement) => {
runFormResetCallback(elm);
};
formStateRestoreCallback = (elm: HTMLElement) => {
runFormStateRestoreCallback(elm);
};
}

// Should never get a tag with upper case letter at this point; the compiler
// should produce only tags with lowercase letters. However, the Java
// compiler may generate tagnames with uppercase letters so - for backwards
// compatibility, we lower case the tagname here.
const normalizedTagname = sel.toLowerCase();
const elm = createCustomElement(
normalizedTagname,
upgradeCallback,
connectedCallback,
disconnectedCallback,
formAssociatedCallback,
formDisabledCallback,
formResetCallback,
formStateRestoreCallback
);
const elm = createCustomElement(normalizedTagname, upgradeCallback);

vnode.elm = elm;
vnode.vm = vm;
Expand Down
44 changes: 1 addition & 43 deletions packages/@lwc/engine-dom/src/apis/create-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ import {
connectRootElement,
disconnectRootElement,
LightningElement,
LifecycleCallback,
runFormAssociatedCallback,
runFormDisabledCallback,
runFormResetCallback,
runFormStateRestoreCallback,
} from '@lwc/engine-core';
import { renderer } from '../renderer';

Expand Down Expand Up @@ -135,43 +130,6 @@ export function createElement(
}
};

let connectedCallback: LifecycleCallback | undefined;
let disconnectedCallback: LifecycleCallback | undefined;
let formAssociatedCallback: LifecycleCallback | undefined;
let formDisabledCallback: LifecycleCallback | undefined;
let formResetCallback: LifecycleCallback | undefined;
let formStateRestoreCallback: LifecycleCallback | undefined;

if (lwcRuntimeFlags.ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE) {
connectedCallback = (elm: HTMLElement) => {
connectRootElement(elm);
};
disconnectedCallback = (elm: HTMLElement) => {
disconnectRootElement(elm);
};
formAssociatedCallback = (elm: HTMLElement) => {
runFormAssociatedCallback(elm);
};
formDisabledCallback = (elm: HTMLElement) => {
runFormDisabledCallback(elm);
};
formResetCallback = (elm: HTMLElement) => {
runFormResetCallback(elm);
};
formStateRestoreCallback = (elm: HTMLElement) => {
runFormStateRestoreCallback(elm);
};
}

const element = createCustomElement(
tagName,
upgradeCallback,
connectedCallback,
disconnectedCallback,
formAssociatedCallback,
formDisabledCallback,
formResetCallback,
formStateRestoreCallback
);
const element = createCustomElement(tagName, upgradeCallback);
return element;
}
142 changes: 36 additions & 106 deletions packages/@lwc/engine-dom/src/custom-elements/create-custom-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,37 @@
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { isUndefined } from '@lwc/shared';
import type { LifecycleCallback } from '@lwc/engine-core';

import { isUndefined, entries } from '@lwc/shared';
import {
LifecycleCallback,
connectRootElement,
disconnectRootElement,
runFormAssociatedCallback,
runFormDisabledCallback,
runFormResetCallback,
runFormStateRestoreCallback,
} from '@lwc/engine-core';
const cachedConstructors = new Map<string, CustomElementConstructor>();
const elementsUpgradedOutsideLWC = new WeakSet<HTMLElement>();
let elementBeingUpgradedByLWC = false;

let formAssociatedCallbackToUse: LifecycleCallback | undefined;
let formDisabledCallbackToUse: LifecycleCallback | undefined;
let formResetCallbackToUse: LifecycleCallback | undefined;
let formStateRestoreCallbackToUse: LifecycleCallback | undefined;

const instancesToFormAssociatedCallbacks = new WeakMap<HTMLElement, LifecycleCallback>();
const instancesToFormDisabledCallbacks = new WeakMap<HTMLElement, LifecycleCallback>();
const instancesToFormResetCallbacks = new WeakMap<HTMLElement, LifecycleCallback>();
const instancesToFormStateRestoreCallbacks = new WeakMap<HTMLElement, LifecycleCallback>();
const lifecycleCallbacks = lwcRuntimeFlags.ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE
? {
connectedCallback: connectRootElement,
disconnectedCallback: disconnectRootElement,
formAssociatedCallback: runFormAssociatedCallback,
formDisabledCallback: runFormDisabledCallback,
formResetCallback: runFormResetCallback,
formStateRestoreCallback: runFormStateRestoreCallback,
}
: undefined;

// Creates a constructor that is intended to be used directly as a custom element, except that the upgradeCallback is
// passed in to the constructor so LWC can reuse the same custom element constructor for multiple components.
// Another benefit is that only LWC can create components that actually do anything – if you do
// `customElements.define('x-foo')`, then you don't have access to the upgradeCallback, so it's a dummy custom element.
// This class should be created once per tag name.
const createUpgradableConstructor = (
connectedCallback?: LifecycleCallback,
disconnectedCallback?: LifecycleCallback
) => {
const hasConnectedCallback = !isUndefined(connectedCallback);
const hasDisconnectedCallback = !isUndefined(disconnectedCallback);

const createUpgradableConstructor = () => {
// TODO [#2972]: this class should expose observedAttributes as necessary
class UpgradableConstructor extends HTMLElement {
static formAssociated = true;
Expand All @@ -42,14 +44,9 @@ const createUpgradableConstructor = (
// If the element is not created using lwc.createElement(), e.g. `document.createElement('x-foo')`,
// then elementBeingUpgraded will be false
if (elementBeingUpgradedByLWC) {
instancesToFormAssociatedCallbacks.set(this, formAssociatedCallbackToUse!);
instancesToFormDisabledCallbacks.set(this, formDisabledCallbackToUse!);
instancesToFormResetCallbacks.set(this, formResetCallbackToUse!);
instancesToFormStateRestoreCallbacks.set(this, formStateRestoreCallbackToUse!);

upgradeCallback(this);
} else if (hasConnectedCallback || hasDisconnectedCallback) {
// If this element has connected or disconnected callbacks, then we need to keep track of
} else if (!isUndefined(lifecycleCallbacks)) {
// If this element has any lifecycle callbacks, then we need to keep track of
// instances that were created outside LWC (i.e. not created by `lwc.createElement()`).
// If the element has no connected or disconnected callbacks, then we don't need to track this.
elementsUpgradedOutsideLWC.add(this);
Expand All @@ -58,66 +55,24 @@ const createUpgradableConstructor = (
// Do we want to support this? Throw an error? Currently for backwards compat it's a no-op.
}
}

formAssociatedCallback() {
const formAssociatedCallback = instancesToFormAssociatedCallbacks.get(this);
// if element was upgraded outside LWC, this will be undefined
if (!isUndefined(formAssociatedCallback)) {
formAssociatedCallback(this);
}
}

formResetCallback() {
const formResetCallback = instancesToFormResetCallbacks.get(this);
// if element was upgraded outside LWC, this will be undefined
if (!isUndefined(formResetCallback)) {
formResetCallback(this);
}
}

formDisabledCallback() {
const formDisabledCallback = instancesToFormDisabledCallbacks.get(this);
// if element was upgraded outside LWC, this will be undefined
if (!isUndefined(formDisabledCallback)) {
formDisabledCallback(this);
}
}

formStateRestoreCallback() {
const formStateRestoreCallback = instancesToFormStateRestoreCallbacks.get(this);
// if element was upgraded outside LWC, this will be undefined
if (!isUndefined(formStateRestoreCallback)) {
formStateRestoreCallback(this);
}
}
}

// Do not unnecessarily add a connectedCallback/disconnectedCallback, as it introduces perf overhead
// Do not unnecessarily add a connectedCallback/disconnectedCallback/etc., as it introduces perf overhead
// See: https://github.com/salesforce/lwc/pull/3162#issuecomment-1311851174
if (hasConnectedCallback) {
(UpgradableConstructor.prototype as any).connectedCallback = function () {
if (!elementsUpgradedOutsideLWC.has(this)) {
connectedCallback(this);
}
};
}

if (hasDisconnectedCallback) {
(UpgradableConstructor.prototype as any).disconnectedCallback = function () {
if (!elementsUpgradedOutsideLWC.has(this)) {
disconnectedCallback(this);
}
};
if (!isUndefined(lifecycleCallbacks)) {
for (const [propName, callback] of entries(lifecycleCallbacks)) {
(UpgradableConstructor.prototype as any)[propName] = function () {
if (!elementsUpgradedOutsideLWC.has(this)) {
callback(this);
}
};
}
}

return UpgradableConstructor;
};

export function getUpgradableConstructor(
tagName: string,
connectedCallback?: LifecycleCallback,
disconnectedCallback?: LifecycleCallback
) {
export function getUpgradableConstructor(tagName: string) {
let UpgradableConstructor = cachedConstructors.get(tagName);

if (isUndefined(UpgradableConstructor)) {
Expand All @@ -126,45 +81,20 @@ export function getUpgradableConstructor(
`Unexpected tag name "${tagName}". This name is a registered custom element, preventing LWC to upgrade the element.`
);
}
UpgradableConstructor = createUpgradableConstructor(
connectedCallback,
disconnectedCallback
);
UpgradableConstructor = createUpgradableConstructor();
customElements.define(tagName, UpgradableConstructor);
cachedConstructors.set(tagName, UpgradableConstructor);
}
return UpgradableConstructor;
}

export const createCustomElement = (
tagName: string,
upgradeCallback: LifecycleCallback,
connectedCallback?: LifecycleCallback,
disconnectedCallback?: LifecycleCallback,
formAssociatedCallback?: LifecycleCallback,
formDisabledCallback?: LifecycleCallback,
formResetCallback?: LifecycleCallback,
formStateRestoreCallback?: LifecycleCallback
) => {
const UpgradableConstructor = getUpgradableConstructor(
tagName,
connectedCallback,
disconnectedCallback
);

formAssociatedCallbackToUse = formAssociatedCallback;
formDisabledCallbackToUse = formDisabledCallback;
formResetCallbackToUse = formResetCallback;
formStateRestoreCallbackToUse = formStateRestoreCallback;
export const createCustomElement = (tagName: string, upgradeCallback: LifecycleCallback) => {
const UpgradableConstructor = getUpgradableConstructor(tagName);

elementBeingUpgradedByLWC = true;
try {
return new UpgradableConstructor(upgradeCallback);
} finally {
elementBeingUpgradedByLWC = false;
formAssociatedCallbackToUse = undefined;
formDisabledCallbackToUse = undefined;
formResetCallbackToUse = undefined;
formStateRestoreCallbackToUse = undefined;
}
};
1 change: 0 additions & 1 deletion packages/@lwc/engine-dom/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
getUpgradableConstructor,
} from './custom-elements/create-custom-element';
import { rendererFactory } from './renderer-factory';

import type { RendererAPI } from '@lwc/engine-core';

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/@lwc/shared/src/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
create,
defineProperties,
defineProperty,
entries,
freeze,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
Expand Down Expand Up @@ -97,6 +98,7 @@ export {
create,
defineProperties,
defineProperty,
entries,
forEach,
freeze,
getOwnPropertyDescriptor,
Expand Down