├── README.md ├── index.d.ts ├── index.js ├── lib └── register.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Hot Module Replaceable Custom Element 2 | 3 | Hot Module Replacement (HMR) is a feature commonly found in JS dev servers such as Webpack that allows a JS module to be reloaded dyanmically in the page without reloading the browser. It works well with most objects except for Custom Elements. Custom Elements need to be registered with the browsers `CustomElementRegistry` and cannot be reloaded or re-defined within it. Calling `customElements.define` on a tag that already exists will result in an error. This util, when HMR is enabled, will replace the prototypes of the existing instances of the custom element on the page and force a re-render. 4 | 5 | [Learn more about HMR](https://webpack.js.org/concepts/hot-module-replacement/) 6 | 7 | ## Usage 8 | 9 | ```js 10 | import { register } from '@polleverywhere/hmr-custom-element' 11 | 12 | class MyElement extends HTMLElement { 13 | } 14 | 15 | register(module, 'my-element', MyElement) 16 | ``` 17 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export function register(module: NodeModule, tag: String, clazz: CustomElementConstructor): void 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import register from './lib/register' 2 | 3 | export { register } 4 | -------------------------------------------------------------------------------- /lib/register.js: -------------------------------------------------------------------------------- 1 | export default function (module, tag, clazz) { 2 | // when not in a HMR env, we should be doing the correct thing 3 | // and let the browser throw an error because we're importing this 4 | // more than once for some reason 5 | if (!module.hot) { 6 | try { 7 | customElements.define(tag, clazz) 8 | } catch (e) { 9 | console.error(`Failed to register element: ${tag}`, e) 10 | } 11 | 12 | return 13 | } 14 | 15 | // hot module reload the elements 16 | // 17 | // if we're in a HMR env, we're going to be reloaded so 18 | // don't register the elements again 19 | if (!customElements.get(tag)) { 20 | try { 21 | customElements.define(tag, clazz) 22 | } catch (e) { 23 | console.error(`Failed to register element in HMR environment: ${tag}`, e) 24 | } 25 | } 26 | 27 | module.hot.accept((e) => { 28 | console.error('HMR error', e) 29 | }) 30 | 31 | if (module.hot.status && module.hot.status() === 'apply') { 32 | // find each instance and swap the prototype for the new one and re-render 33 | document.querySelectorAll(tag).forEach((node) => { 34 | // Swap prototype of instance with new one 35 | Object.setPrototypeOf(node, clazz.prototype) 36 | 37 | // re-render 38 | node.connectedCallback() 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polleverywhere/hmr-custom-element", 3 | "version": "1.0.2", 4 | "description": "Register custom elements with hot module replacement support", 5 | "main": "index.js", 6 | "repository": "git@github.com:polleverywhere/hmr-custom-element.git", 7 | "author": "Steel Fu ", 8 | "license": "MIT", 9 | "private": false 10 | } 11 | --------------------------------------------------------------------------------