From 62e08821f54e171ac3181be0466d60c32f92b0fd Mon Sep 17 00:00:00 2001 From: Will Harney Date: Fri, 27 Sep 2024 10:56:12 -0400 Subject: [PATCH 1/2] fix(ssr): add missing element methods --- packages/@lwc/ssr-runtime/src/index.ts | 164 ++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 6 deletions(-) diff --git a/packages/@lwc/ssr-runtime/src/index.ts b/packages/@lwc/ssr-runtime/src/index.ts index fd219c741e..c426ac210f 100644 --- a/packages/@lwc/ssr-runtime/src/index.ts +++ b/packages/@lwc/ssr-runtime/src/index.ts @@ -5,13 +5,36 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ +// To enable access to accurate DOM types in this file, uncomment the line below and comment out the +// type stubs. This also adds DOM globals (e.g. window, document), so be careful! +// Note: It's a "triple slash directive", must be exactly /// +// /// +type DOMTokenList = object; +type HTMLElement = Record; +type EventListenerOrEventListenerObject = unknown; +type AddEventListenerOptions = unknown; +type ElementInternals = unknown; +type DOMRect = unknown; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type HTMLCollectionOf = unknown; +type Element = unknown; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type NodeListOf = unknown; +type HTMLElementEventMap = unknown; +type EventListenerOptions = unknown; +type HTMLCollection = unknown; +type ChildNode = unknown; +type Document = unknown; +type ShadowRoot = unknown; +type CSSStyleDeclaration = unknown; + const MULTI_SPACE = /\s+/g; type Attributes = Record; type LightningElementConstructor = typeof LightningElement; -class ClassList { +class ClassList implements DOMTokenList { el: LightningElement; constructor(el: LightningElement) { @@ -66,23 +89,77 @@ class ClassList { this.el.className = Array.from(set).join(' '); return set.has(classNameToToggle); } + + get value(): string { + return this.el.className; + } + + toString(): string { + return this.el.className; + } + + // Stubs to satisfy DOMTokenList interface + [index: number]: never; // Can't implement arbitrary index getters without a proxy + item(_index: number): string | null { + throw new Error('Method "item" not implemented.'); + } + supports(_token: string): boolean { + throw new Error('Method "supports" not implemented.'); + } + forEach( + _callbackfn: (value: string, key: number, parent: DOMTokenList) => void, + _thisArg?: any + ): void { + throw new Error('Method "void" not implemented.'); + } + get length(): number { + throw new Error('Property "length" not implemented.'); + } +} + +interface PropsAvailableAtConstruction { + tagName: string; } -export class LightningElement { +export class LightningElement implements PropsAvailableAtConstruction { static renderMode?: 'light' | 'shadow'; isConnected = false; className = ''; // TODO [W-14977927]: protect internals from userland - __attrs?: Attributes; - __classList: ClassList | null = null; + private __attrs?: Attributes; + private __classList: ClassList | null = null; + // Using ! because it's assigned in the constructor via `Object.assign`, which TS can't detect + tagName!: string; - constructor(propsAvailableAtConstruction: Record) { + constructor( + propsAvailableAtConstruction: PropsAvailableAtConstruction & Record + ) { Object.assign(this, propsAvailableAtConstruction); } + // Props copied from HTMLElementTheGoodParts in @lwc/engine-core + accessKey?: string; + children?: HTMLCollection; + childNodes?: NodeListOf; + dir?: string; + draggable?: boolean; + firstChild?: ChildNode | null; + firstElementChild?: Element | null; + hidden?: boolean; + id?: string; + lang?: string; + lastChild?: ChildNode | null; + lastElementChild?: Element | null; + ownerDocument?: Document; + shadowRoot?: ShadowRoot | null; + spellcheck?: boolean; + tabIndex?: number; + title?: string; + style?: CSSStyleDeclaration; + // TODO [W-14977927]: protect internals from userland - __internal__setState( + private __internal__setState( props: Record, reflectedProps: string[], attrs: Record @@ -126,6 +203,81 @@ export class LightningElement { const value = this.__attrs?.[attrName]; return value === true ? '' : (value ?? null); } + + setAttribute(attrName: string, value: string): void { + // Not sure it's correct to initialize here if missing + if (!this.__attrs) this.__attrs = {}; + this.__attrs[attrName] = value; + } + + removeAttribute(attrName: string): void { + delete this.__attrs?.[attrName]; + } + + // -------------------------------------------------------------------------------- // + // Stubs to satisfy the HTMLElementTheGoodParts (from @lwc/engine-core) interface // + // The interface is not explicitly referenced here, so this may become outdated // + // -------------------------------------------------------------------------- // + + addEventListener( + _type: string, + _listener: EventListenerOrEventListenerObject, + _options?: boolean | AddEventListenerOptions + ): void { + throw new Error('Method "void" not implemented.'); + } + attachInternals(): ElementInternals { + throw new Error('Method "attachInternals" not implemented.'); + } + dispatchEvent(_event: Event): boolean { + throw new Error('Method "dispatchEvent" not implemented.'); + } + getAttributeNS(_namespace: string | null, _localName: string): string | null { + throw new Error('Method "getAttributeNS" not implemented.'); + } + getBoundingClientRect(): DOMRect { + throw new Error('Method "getBoundingClientRect" not implemented.'); + } + getElementsByClassName(_classNames: string): HTMLCollectionOf { + throw new Error('Method "getElementsByClassName" not implemented.'); + } + getElementsByTagName(_qualifiedName: unknown): HTMLCollectionOf { + throw new Error('Method "getElementsByTagName" not implemented.'); + } + hasAttribute(_qualifiedName: string): boolean { + throw new Error('Method "hasAttribute" not implemented.'); + } + hasAttributeNS(_namespace: string | null, _localName: string): boolean { + throw new Error('Method "hasAttributeNS" not implemented.'); + } + querySelector(_selectors: string): Element | null { + throw new Error('Method "querySelector" not implemented.'); + } + querySelectorAll(_selectors: string): NodeListOf { + throw new Error('Method "querySelectorAll" not implemented.'); + } + removeAttributeNS(_namespace: string | null, _localName: string): void { + throw new Error('Method "removeAttributeNS" not implemented.'); + } + removeEventListener( + type: K, + listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions + ): void; + removeEventListener(_type: unknown, _listener: unknown, _options?: unknown): void { + throw new Error('Method "removeEventListener" not implemented.'); + } + setAttributeNS(_namespace: string | null, _qualifiedName: string, _value: string): void { + throw new Error('Method "setAttributeNS" not implemented.'); + } + toString(): string { + throw new Error('Method "toString" not implemented.'); + } } const escapeAttrVal = (attrVal: string) => From 51b8e092c2006f9bdff0458b2e96ce24ecec351c Mon Sep 17 00:00:00 2001 From: Will Harney Date: Fri, 27 Sep 2024 11:40:13 -0400 Subject: [PATCH 2/2] chore: fix method names --- packages/@lwc/ssr-runtime/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@lwc/ssr-runtime/src/index.ts b/packages/@lwc/ssr-runtime/src/index.ts index c426ac210f..a53fd6676c 100644 --- a/packages/@lwc/ssr-runtime/src/index.ts +++ b/packages/@lwc/ssr-runtime/src/index.ts @@ -110,7 +110,7 @@ class ClassList implements DOMTokenList { _callbackfn: (value: string, key: number, parent: DOMTokenList) => void, _thisArg?: any ): void { - throw new Error('Method "void" not implemented.'); + throw new Error('Method "forEach" not implemented.'); } get length(): number { throw new Error('Property "length" not implemented.'); @@ -224,7 +224,7 @@ export class LightningElement implements PropsAvailableAtConstruction { _listener: EventListenerOrEventListenerObject, _options?: boolean | AddEventListenerOptions ): void { - throw new Error('Method "void" not implemented.'); + throw new Error('Method "addEventListener" not implemented.'); } attachInternals(): ElementInternals { throw new Error('Method "attachInternals" not implemented.');