├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src └── index.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "prettier"], 3 | "root": true, 4 | "env": { 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2020, 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "prettier" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Caridy Patiño 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redefine Custom Elements 2 | 3 | This experimental library patches the global custom elements registry to allow re-defining a custom element. Based on the spec, a custom element can be defined for a qualifying name only once, the class itself cannot be reused for a different name as well. These two restrictions often pushes developers away from using custom elements, or even supporting custom elements in their platforms, specifically, any framework that allows live refresh of the code without reloading the page will eventually encounter that these restrictions will prevent this from happening. 4 | 5 | ## Use Cases 6 | 7 | Allowing redefinition of a custom element via `customElements.define()` API can help with the following use-cases: 8 | 9 | * Hot module replacement. 10 | * Designing Custom Elements at runtime. 11 | * Testing and Automation. 12 | 13 | ## Usage 14 | 15 | Installation: 16 | 17 | ```bash 18 | npm i redefine-custom-elements 19 | ``` 20 | 21 | Then just import the library to patch the custom elements registry: 22 | 23 | ```js 24 | import "redefine-custom-elements"; 25 | customElements.define('x-foo', class Foo extends HTMLElement {}); 26 | customElements.define('x-foo', class Bar extends HTMLElement {}); 27 | ``` 28 | 29 | ## Design 30 | 31 | The concept of a pivot constructor is well proven now, it is used by the [scoped custom elements registry polyfill](https://github.com/webcomponents/polyfills/tree/master/packages/scoped-custom-element-registry/) as well, which inspire many parts of this library. This library relies on this concept to install a pivotal constructor into the custom elements registry, and whenever that pivot constructor is called by the engine, a corresponding (user-defined) constructor can be used to construct the final version of the instance before handing that over to user-land. To achieve this, we need to patch various methods of the `CustomElementRegistry.prototype`, and the `HTMLElement` constructor. 32 | 33 | ## Performance 34 | 35 | This library is intended to be used during the development. Nevertheless, when patching DOM APIs, you're often going to get performance degradation, and this library is not the exception. The good news is that it doesn't affect, in any way, the element after it is created. The main concern in terms of performance will be the time to create an instance. 36 | 37 | ## Side effects 38 | 39 | These patches are meant to be transparent, and should not be observed by user-land code, but this is hard to achieve when it comes to the DOM APIs. Execute this code first to avoid mismatches. 40 | 41 | ## Limitations 42 | 43 | 1. Any custom element defined before this library runs will not be replaceable. In this case, it will simply throw the built-in error. 44 | 2. If you rely on options when defining a custom element (e.g.: `customElements.define(name, constructor, options)`), non-equivalent options when redefining the element will not be recognized. 45 | 3. If it unclear how this can work with scoped custom elements, if that proposal moves forward. 46 | 47 | ## Browsers Support and Stats 48 | 49 | * Modern browsers with support for custom elements. 50 | * This library: <2kb gzip/minify (no external dependencies). 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redefine-custom-elements", 3 | "version": "0.1.3", 4 | "license": "MIT", 5 | "author": "Caridy Patiño ", 6 | "description": "Experimental library to allow re-defining custom elements", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/caridy/redefine-custom-elements.git" 10 | }, 11 | "module": "lib/index.js", 12 | "scripts": { 13 | "prepare": "npm run build", 14 | "clean": "rimraf lib", 15 | "build": "tsc" 16 | }, 17 | "files": [ 18 | "lib/" 19 | ], 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "devDependencies": { 24 | "rimraf": "3.0.2", 25 | "tslib": "2.3.1", 26 | "typescript": "4.6.2" 27 | }, 28 | "engines": { 29 | "node": ">=16" 30 | }, 31 | "engineStrict": true, 32 | "volta": { 33 | "node": "16.13.1", 34 | "yarn": "1.22.11" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | interface Definition { 2 | LatestCtor: CustomElementConstructor; 3 | PivotCtor?: CustomElementConstructor; 4 | connectedCallback: (() => void) | void; 5 | disconnectedCallback: (() => void) | void; 6 | adoptedCallback: (() => void) | void; 7 | attributeChangedCallback: ((name: string, oldValue: any, newValue: any) => void) | void; 8 | formAssociatedCallback: (() => void) | void; 9 | formDisabledCallback: (() => void) | void; 10 | formResetCallback: (() => void) | void; 11 | formStateRestoreCallback: (() => void) | void; 12 | observedAttributes: Set; 13 | formAssociated?: boolean; 14 | } 15 | 16 | const cer = customElements; 17 | const NativeHTMLElement = HTMLElement; 18 | 19 | const { 20 | hasAttribute: nativeHasAttribute, 21 | setAttribute: nativeSetAttribute, 22 | removeAttribute: nativeRemoveAttribute, 23 | getAttribute: nativeGetAttribute, 24 | } = NativeHTMLElement.prototype; 25 | 26 | const { apply: ReflectApply, setPrototypeOf: ReflectSetPrototypeOf, construct: ReflectConstruct } = Reflect; 27 | const { defineProperties: ObjectDefineProperties } = Object; 28 | 29 | const { 30 | get: nativeGet, 31 | define: nativeDefine, 32 | whenDefined: nativeWhenDefined, 33 | } = CustomElementRegistry.prototype; 34 | 35 | const nativeNodeIsConnectedGetter = Object.getOwnPropertyDescriptor(Node.prototype, 'isConnected')!.get!; 36 | 37 | function valueToString(value: any): string { 38 | try { 39 | return String(value); 40 | // eslint-disable-next-line no-empty 41 | } catch {} 42 | return ''; 43 | } 44 | 45 | function createDefinitionRecord(constructor: CustomElementConstructor): Definition { 46 | // Since observedAttributes can't change, we approximate it by patching 47 | // set/removeAttribute on the user's class 48 | const { 49 | connectedCallback, 50 | disconnectedCallback, 51 | adoptedCallback, 52 | attributeChangedCallback, 53 | formAssociatedCallback, 54 | formDisabledCallback, 55 | formResetCallback, 56 | formStateRestoreCallback, 57 | } = constructor.prototype; 58 | const observedAttributes = new Set((constructor as any).observedAttributes || []); 59 | const formAssociated = (constructor as any).formAssociated || false; 60 | return { 61 | LatestCtor: constructor, 62 | connectedCallback, 63 | disconnectedCallback, 64 | adoptedCallback, 65 | formAssociatedCallback, 66 | formDisabledCallback, 67 | formResetCallback, 68 | formStateRestoreCallback, 69 | attributeChangedCallback, 70 | observedAttributes, 71 | formAssociated 72 | }; 73 | } 74 | 75 | function getObservedAttributesOffset(originalDefinition: Definition, instancedDefinition: Definition) { 76 | // natively, the attributes observed by the registered definition are going to be taken 77 | // care of by the browser, only the difference between the two sets has to be taken 78 | // care by the patched version. 79 | return new Set( 80 | [...originalDefinition.observedAttributes].filter( 81 | (x) => !instancedDefinition.observedAttributes.has(x) 82 | ) 83 | ); 84 | } 85 | 86 | // Helper to patch CE class setAttribute/getAttribute to implement 87 | // attributeChangedCallback 88 | function patchAttributes( 89 | instance: HTMLElement, 90 | originalDefinition: Definition, 91 | instancedDefinition: Definition 92 | ) { 93 | const { observedAttributes, attributeChangedCallback } = instancedDefinition; 94 | if (observedAttributes.size === 0 || !attributeChangedCallback) { 95 | return; 96 | } 97 | const offset = getObservedAttributesOffset(originalDefinition, instancedDefinition); 98 | if (offset.size === 0) { 99 | return; 100 | } 101 | // instance level patches 102 | ObjectDefineProperties(instance, { 103 | setAttribute: { 104 | value: function setAttribute(name: string, value: any) { 105 | if (offset.has(name)) { 106 | const old = nativeGetAttribute.call(this, name); 107 | // maybe we want to call the super.setAttribute rather than the native one 108 | nativeSetAttribute.call(this, name, value); 109 | attributeChangedCallback.call(this, name, old, valueToString(value)); 110 | } else { 111 | nativeSetAttribute.call(this, name, value); 112 | } 113 | }, 114 | writable: true, 115 | enumerable: true, 116 | configurable: true, 117 | }, 118 | removeAttribute: { 119 | value: function removeAttribute(name: string) { 120 | if (offset.has(name)) { 121 | const old = nativeGetAttribute.call(this, name); 122 | // maybe we want to call the super.removeAttribute rather than the native one 123 | nativeRemoveAttribute.call(this, name); 124 | attributeChangedCallback.call(this, name, old, null); 125 | } else { 126 | nativeRemoveAttribute.call(this, name); 127 | } 128 | }, 129 | writable: true, 130 | enumerable: true, 131 | configurable: true, 132 | }, 133 | }); 134 | } 135 | 136 | // Helper to create stand-in element for each tagName registered that delegates 137 | // out to the internal registry for the given element 138 | function createPivotingClass(originalDefinition: Definition, tagName: string) { 139 | return class PivotCtor extends NativeHTMLElement { 140 | constructor(definition?: Definition, args?: any[]) { 141 | // This constructor can only be invoked by: 142 | // a) the browser instantiating an element from parsing or via document.createElement. 143 | // b) new UserClass. 144 | super(); 145 | 146 | // b) user's initiated instantiation via new Ctor() in a sandbox 147 | if (definition) { 148 | internalUpgrade(this, originalDefinition, definition, args); 149 | return this; 150 | } 151 | 152 | // Schedule or upgrade instance 153 | definition = definitionsByTag.get(tagName); 154 | if (definition) { 155 | // browser's initiated a controlled instantiation where we 156 | // were able to set up the internal registry and the definition. 157 | internalUpgrade(this, originalDefinition, definition); 158 | } else { 159 | // This is the case in which there is no definition yet, and 160 | // we need to add it to the pending queue just in case it eventually 161 | // gets defined locally. 162 | pendingRegistryForElement.set(this, originalDefinition); 163 | // We need to install the minimum HTMLElement prototype so that 164 | // this instance works like a regular element without a registered 165 | // definition; #internalUpgrade will eventually install the full CE prototype 166 | ReflectSetPrototypeOf(this, patchedHTMLElement.prototype); 167 | } 168 | } 169 | connectedCallback() { 170 | const definition = definitionForElement.get(this); 171 | if (definition) { 172 | // Delegate out to user callback 173 | definition.connectedCallback?.call(this); 174 | } else { 175 | // Register for upgrade when defined (only when connected, so we don't leak) 176 | let awaiting = awaitingUpgrade.get(tagName); 177 | if (!awaiting) { 178 | awaitingUpgrade.set(tagName, (awaiting = new Set())); 179 | } 180 | awaiting.add(this); 181 | } 182 | } 183 | disconnectedCallback() { 184 | const definition = definitionForElement.get(this); 185 | if (definition) { 186 | // Delegate out to user callback 187 | definition.disconnectedCallback?.call(this); 188 | } else { 189 | // Un-register for upgrade when defined (so we don't leak) 190 | const awaiting = awaitingUpgrade.get(tagName); 191 | if (awaiting) { 192 | awaiting.delete(this); 193 | } 194 | } 195 | } 196 | adoptedCallback() { 197 | // TODO: this needs more work 198 | const definition = definitionForElement.get(this); 199 | definition?.adoptedCallback?.call(this); 200 | } 201 | formAssociatedCallback() { 202 | const definition = definitionForElement.get(this); 203 | definition?.formAssociatedCallback?.apply(this, arguments); 204 | } 205 | formDisabledCallback() { 206 | const definition = definitionForElement.get(this); 207 | definition?.formDisabledCallback?.apply(this, arguments); 208 | } 209 | formResetCallback() { 210 | const definition = definitionForElement.get(this); 211 | definition?.formResetCallback?.apply(this, arguments); 212 | } 213 | formStateRestoreCallback() { 214 | const definition = definitionForElement.get(this); 215 | definition?.formStateRestoreCallback?.apply(this, arguments); 216 | } 217 | attributeChangedCallback(...args: [name: string, oldValue: any, newValue: any]) { 218 | const definition = definitionForElement.get(this); 219 | // if both definitions are the same, then the observedAttributes is the same, 220 | // but if they are different, only if the runtime definition has the attribute 221 | // marked as observed, then it should invoke attributeChangedCallback. 222 | if (originalDefinition === definition || definition?.observedAttributes.has(args[0])) { 223 | definition.attributeChangedCallback?.apply(this, args); 224 | } 225 | } 226 | 227 | static observedAttributes = originalDefinition.observedAttributes; 228 | static formAssociated = originalDefinition.formAssociated; 229 | }; 230 | } 231 | 232 | let upgradingInstance: HTMLElement | undefined; 233 | const definitionForElement = new WeakMap(); 234 | const pendingRegistryForElement = new WeakMap(); 235 | const definitionForConstructor = new WeakMap(); 236 | const pivotCtorByTag = new Map(); 237 | const definitionsByTag = new Map(); 238 | const definitionsByClass = new Map(); 239 | const definedPromises = new Map>(); 240 | const definedResolvers = new Map void>(); 241 | const awaitingUpgrade = new Map>(); 242 | 243 | // Helper to upgrade an instance with a CE definition using "constructor call trick" 244 | function internalUpgrade( 245 | instance: HTMLElement, 246 | originalDefinition: Definition, 247 | instancedDefinition: Definition, 248 | args?: any[] 249 | ) { 250 | ReflectSetPrototypeOf(instance, instancedDefinition.LatestCtor.prototype); 251 | definitionForElement.set(instance, instancedDefinition); 252 | // attributes patches when needed 253 | if (instancedDefinition !== originalDefinition) { 254 | patchAttributes(instance, originalDefinition, instancedDefinition); 255 | } 256 | // Tricking the construction path to believe that a new instance is being created, 257 | // that way it will execute the super initialization mechanism but the HTMLElement 258 | // constructor will reuse the instance by returning the upgradingInstance. 259 | // This is by far the most important piece of the puzzle 260 | upgradingInstance = instance; 261 | // TODO: do we need to provide a newTarget here as well? if yes, what should that be? 262 | ReflectConstruct(instancedDefinition.LatestCtor, args || []); 263 | 264 | const { observedAttributes, attributeChangedCallback } = instancedDefinition; 265 | if (observedAttributes.size > 0 && attributeChangedCallback) { 266 | const offset = getObservedAttributesOffset(originalDefinition, instancedDefinition); 267 | if (offset.size > 0) { 268 | // Approximate observedAttributes from the user class, but only for the offset attributes 269 | offset.forEach((name) => { 270 | if (nativeHasAttribute.call(instance, name)) { 271 | const newValue = nativeGetAttribute.call(instance, name); 272 | attributeChangedCallback.call(instance, name, null, newValue); 273 | } 274 | }); 275 | } 276 | } 277 | 278 | // connectedCallback retroactively invocation 279 | // TODO: I'm not sure this is really needed... 280 | if (ReflectApply(nativeNodeIsConnectedGetter, instance, [])) { 281 | instancedDefinition.disconnectedCallback?.call(instance); 282 | } 283 | } 284 | 285 | function getDefinitionForConstructor(constructor: CustomElementConstructor): Definition { 286 | if (!constructor || !constructor.prototype || typeof constructor.prototype !== 'object') { 287 | throw new TypeError(`The referenced constructor is not a constructor.`); 288 | } 289 | let definition = definitionForConstructor.get(constructor); 290 | if (!definition) { 291 | definition = createDefinitionRecord(constructor); 292 | definitionForConstructor.set(constructor, definition); 293 | } 294 | return definition; 295 | } 296 | 297 | const patchedHTMLElement = function HTMLElement(this: HTMLElement, ...args: any[]) { 298 | if (!new.target) { 299 | throw new TypeError( 300 | `Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.` 301 | ); 302 | } 303 | if (new.target === patchedHTMLElement) { 304 | throw new TypeError(`Illegal constructor`); 305 | } 306 | // This constructor is ONLY invoked when it is the user instantiating 307 | // an element via `new Ctor()` while `this.constructor` is a locally 308 | // registered constructor, otherwise it throws. 309 | // Upgrading case: the pivoting class constructor was run by the browser's 310 | // native custom elements and we're in the process of running the 311 | // "constructor-call trick" on the natively constructed instance, so just 312 | // return that here 313 | const pendingUpgradeInstance = upgradingInstance; 314 | if (pendingUpgradeInstance) { 315 | upgradingInstance = undefined; 316 | return pendingUpgradeInstance; 317 | } 318 | const { constructor } = this; 319 | // Construction case: we need to construct the pivoting instance and return it 320 | // This is possible when the user instantiate it via `new LatestCtor()`. 321 | const definition = definitionsByClass.get(constructor as CustomElementConstructor); 322 | if (!definition || !definition.PivotCtor) { 323 | throw new TypeError('Illegal constructor'); 324 | } 325 | // This constructor is ONLY invoked when it is the user instantiating 326 | // an element via new Ctor while Ctor is the latest registered constructor. 327 | return new definition.PivotCtor(definition, args); 328 | } 329 | patchedHTMLElement.prototype = NativeHTMLElement.prototype; 330 | 331 | // patching global registry associated to the global object 332 | Object.assign(CustomElementRegistry.prototype, { 333 | get( 334 | this: CustomElementRegistry, 335 | ...args: Parameters 336 | ): ReturnType { 337 | if (this !== cer) { 338 | // This is more restricted than native behavior because in native this is 339 | // going to leak constructors from another windows. But right now, I don't 340 | // know the implications yet, the safe bet is to throw here. 341 | // TODO: this could leak pivots from another document, that's the concern. 342 | throw new TypeError('Illegal invocation'); 343 | } 344 | const { 0: tagName } = args; 345 | return ( 346 | // SyntaxError if The provided name is not a valid custom element name. 347 | ReflectApply(nativeGet, this, args) && 348 | definitionsByTag.get(tagName)?.LatestCtor 349 | ); 350 | }, 351 | define( 352 | this: CustomElementRegistry, 353 | ...args: Parameters 354 | ): ReturnType { 355 | if (this !== cer) { 356 | // This is more restricted than native behavior because in native this is 357 | // normally a runtime error when attempting to define a class that inherit 358 | // from a constructor from another window. But right now, I don't know how 359 | // to do this runtime check, the safe bet is to throw here. 360 | throw new TypeError('Illegal invocation'); 361 | } 362 | const { 0: tagName, 1: constructor, 2: options } = args; 363 | // TODO: I think we can support this just fine... 364 | if (options && options.extends) { 365 | // eslint-disable-next-line @typescript-eslint/no-throw-literal 366 | throw new DOMException('NotSupportedError: '); 367 | } 368 | let PivotCtor = ReflectApply(nativeGet, this, [tagName]); // SyntaxError if The provided name is not a valid custom element name. 369 | // TODO: do we really need to lower case this? 370 | // tagName = tagName.toLowerCase(); 371 | if (PivotCtor && PivotCtor !== definitionsByTag.get(tagName)?.PivotCtor) { 372 | // This is just in case that there is something defined that is not a controlled pivot 373 | // eslint-disable-next-line @typescript-eslint/no-throw-literal 374 | throw new DOMException( 375 | `Failed to execute 'define' on 'CustomElementRegistry': the name "${tagName}" has already been used with this registry` 376 | ); 377 | } 378 | const definition = getDefinitionForConstructor(constructor); 379 | if (definitionsByClass.get(constructor)) { 380 | // eslint-disable-next-line @typescript-eslint/no-throw-literal 381 | throw new DOMException( 382 | `Failed to execute 'define' on 'CustomElementRegistry': this constructor has already been used with this registry` 383 | ); 384 | } 385 | definitionsByTag.set(tagName, definition); 386 | definitionsByClass.set(constructor, definition); 387 | PivotCtor = pivotCtorByTag.get(tagName); 388 | if (!PivotCtor) { 389 | PivotCtor = createPivotingClass( 390 | definition, 391 | tagName 392 | ); 393 | pivotCtorByTag.set(tagName, PivotCtor); 394 | // Register a pivoting class which will handle global registry initializations 395 | ReflectApply(nativeDefine, this, [tagName, PivotCtor]); 396 | } 397 | // For globally defined custom elements, the definition associated 398 | // to the UserCtor has a back-pointer to PivotCtor in case the user 399 | // new the UserCtor, so we know how to create the underlying element. 400 | definition.PivotCtor = PivotCtor; 401 | // Upgrade any elements created in this scope before define was called 402 | // which should be exhibit by LWC using a tagName (in a template) 403 | // before the same tagName is registered as a global, while others 404 | // are already created and waiting in the global context, that will 405 | // require immediate upgrade when the new global tagName is defined. 406 | const awaiting = awaitingUpgrade.get(tagName); 407 | if (awaiting) { 408 | awaitingUpgrade.delete(tagName); 409 | awaiting.forEach((element) => { 410 | const originalDefinition = pendingRegistryForElement.get(element); 411 | if (originalDefinition) { 412 | pendingRegistryForElement.delete(element); 413 | internalUpgrade(element, originalDefinition, definition); 414 | } 415 | }); 416 | } 417 | // Flush whenDefined callbacks 418 | const resolver = definedResolvers.get(tagName); 419 | if (resolver) { 420 | resolver(constructor); 421 | } 422 | }, 423 | whenDefined( 424 | this: CustomElementRegistry, 425 | ...args: Parameters 426 | ): ReturnType { 427 | if (this !== cer) { 428 | // This is more restricted than native behavior because in native this is 429 | // going to leak constructors from another windows when defined. But right 430 | // now, I don't know the implications yet, the safe bet is to throw here. 431 | // TODO: maybe returning a promise that will never fulfill is better. 432 | throw new TypeError('Illegal invocation'); 433 | } 434 | const { 0: tagName } = args; 435 | // TODO: the promise constructor could be leaked here when using multi-window 436 | // we probably need to do something here to return the proper type of promise 437 | // we need more investigation here. 438 | return ReflectApply(nativeWhenDefined, this, args).then(() => { 439 | let promise = definedPromises.get(tagName); 440 | if (!promise) { 441 | const definition = definitionsByTag.get(tagName); 442 | if (definition) { 443 | return Promise.resolve(definition.LatestCtor); 444 | } 445 | let resolve: (constructor: CustomElementConstructor) => void; 446 | promise = new Promise((r) => { 447 | resolve = r; 448 | }); 449 | definedPromises.set(tagName, promise); 450 | definedResolvers.set(tagName, resolve!); 451 | } 452 | return promise; 453 | }); 454 | }, 455 | constructor: patchedHTMLElement, 456 | }); 457 | 458 | // patching HTMLElement constructor associated to the global object 459 | // @ts-ignore 460 | window.HTMLElement = patchedHTMLElement; 461 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "lib": ["dom", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020"], 8 | "declaration": false, 9 | // Enhance Strictness 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "noImplicitReturns": true, 14 | "strictBindCallApply": false, 15 | "outDir": "lib/" 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ], 20 | "exclude": [ 21 | "src/**/*.spec.*" 22 | ] 23 | } 24 | --------------------------------------------------------------------------------