Lit-element: Hot Module Loading Support

Created on 9 May 2019  路  12Comments  路  Source: Polymer/lit-element

Hello all, so I have recently moved over to a webpack build pipeline for purposes of building, transpiling, and using this guy WorkerPlugin. All that being said, Hot Module Replacement does not work due to the fact that when it replaces the module, the browser sees this as an element re-registry...

The folks over at Skate JS baked in a solution

Same issue with Solution
Exact Issue
Final Change
-- where they have been able to make it to work.

I am looking to see if there is any way to build in a similar solution into the @customElement decorator.

Would love to know thoughts. This could affect a lot of people.

Medium Enhancement Improve ergonomics

Most helpful comment

Hot module loading is not, in general, a really workable system. All kinds of state will be confused by reloading modules, including module variables, event listeners, and the custom elements registry.

I don't think generating a new element name is useful, as other logic and CSS may depend on that name. More likely is that we can find a way to patch CustomElementRegistry.define so that we can redefine an element and re-render all instances.

All 12 comments

Might also be a good question for the folks over at @polymer slack open-wc
channel.

I am experiencing the same issue with fuse-box bundler HMR. I'm currently looking into writing custom HMR driver to resolve this.

Hot module loading is not, in general, a really workable system. All kinds of state will be confused by reloading modules, including module variables, event listeners, and the custom elements registry.

I don't think generating a new element name is useful, as other logic and CSS may depend on that name. More likely is that we can find a way to patch CustomElementRegistry.define so that we can redefine an element and re-render all instances.

We created custom elements hmr polyfill here:

This adds a proxy around CustomElementRegistry.define.
Yesterday we also found a workaround to observedAttributes by using mutationObserver. (not updated npm yet)

If you have suggestion of callbacks that would be useful to have then create issue.
Currently I use it with the fusebox bundler v4-(next).

@rictic could you take a look at custom-elements-hmr-polyfill?

The HMR solution I've been playing with looks for a static callback on custom element classes, called notifyOnHotModuleReload. If it's present, then when a custom element is redefined we assume that it's a hot reload, and call originalClassObject.notifyOnHotModuleReload(newClassObject, tagName).

It also patches LitElement to add this callback. The LitElement implementation of the callback recreates styles, copies values off of the new prototype back onto the old, and then walks the document for elements with the given tagName and asks them to rerender.

This way each custom element class (or base class) has the option to participate in hot reloading, and they can control how they rerender, rather than having a single strategy (which it looks like custom-elements-hmr-polyfill appears to do).

(I'd also hesitate to call a hot module reloading system a polyfill, as there's no standard that it's implementing. IMO that makes it a library, not a polyfill)

I'd like to clean up and release the hot module code I've been tinkering with. The main thing that I'm unsure of is a robust method for including code that should be compiled out of production builds and that will work both unmodified in browsers as well as in rollup, webpack, closure compiler, etc

(I'd also like to use proxies, as you do in custom-elements-hmr-polyfill, to ensure that element creation works as expected)

Hi @rictic

(I'd also hesitate to call a hot module reloading system a polyfill, as there's no standard that it's implementing. IMO that makes it a library, not a polyfill)

Called it polyfill since it implements a feature that browsers do not support. No its not a standard, but we would like it to be. But if you want to call it a library then that is ok 馃槃

This way each custom element class (or base class) has the option to participate in hot reloading, and they can control how they rerender, rather than having a single strategy (which it looks like custom-elements-hmr-polyfill appears to do).

It has only 1 "auto" strategy, if you want to use it or walk the DOM tree in callback is really up to you.
We did walk the DOM tree in the beginning, but figured it would be best to let user define how they wanted to do this. Manually replacing just part of the DOM can be very hard, since you never know what any of the parent element does. Or how the users bundler work on HMR event.
Some elements might also be dynamically loaded by parent element, like a router. And then that file might never run unless parent loads it again.

```js
import { applyPolyfill, ReflowStrategy, rerenderInnerHTML } from 'custom-elements-hmr-polyfill';

applyPolyfill(
/* no auto-reflow / ReflowStrategy.NONE,
/
ignored, because reflowing is disabled / 0,
/
gets called for every re-definition of a web component */
(elementName: string, impl: any, options: ElementDefinitionOptions) => {

    // manually reflow using rerenderInnerHTML strategy without any buffering
    rerenderInnerHTML(); //<- you could skip this and walk the dom tree, call something on a timeout

}

);
````

I'd like to clean up and release the hot module code I've been tinkering with. The main thing that I'm unsure of is a robust method for including code that should be compiled out of production builds and that will work both unmodified in browsers as well as in rollup, webpack, closure compiler, etc

I really do not know. there is a few bundler out there now. I use fuse-box :-)

(I'd also like to use proxies, as you do in custom-elements-hmr-polyfill, to ensure that element creation works as expected)

Then why not just use it as it is ? If you use ReflowStrategy.NONE, it wont really do anything before you recreate the element. 馃槃

I been using lit-html lately, re-rendering of the page is pretty fast and I really would not know if it was a node that got replaced or entire page (unless something is loaded with delay..)
But it will just work with most element if they have a callback attached or not.

If you have any idea on other callback we should add that could help you then please create a issue.

I'm currently working on a PR for HMR in system js https://github.com/systemjs/systemjs/pull/2014 and integrating it into es-dev-server.

I've been testing it with LitElement and https://github.com/vegarringdal/custom-elements-hmr-polyfill, a lot of things work pretty well but I'm running into a few issues such as styles not being correctly updated. @rictic I'm very interested in how you tackled this if you're able to share it

@LarsDenBakker sure thing, I pushed a draft of what I have so far to https://github.com/Polymer/lit-element/pull/802

(If I get it ready to publish for reals I'd put hot_elements into its own npm package totally separate from LitElement)

@rictic btw, the ability to call a custom event handler like notifyOnHotModuleReload on each web component instance is only a few commits and a push away. It could be impl. as another ReflowStrategy of our polyfill. e.g. ReflowStrategy.CustomEventHandler

@LarsDenBakker would that be of any use for you?

Yeah, I think it will be good to settle on a common callback for handling reloads.

For me it's all just experimentation right now, there is a lot to figure out regarding workflows and which states to reset/maintain among modules.

We will need to see when it is actually faster VS just refreshing and relying on 304s from a web server.

@LarsDenBakker With ReflowStrategy.CustomEventHandler, we'd call this callback on every instance of a Web Component. You could hook this callback in lit-element for sure - but also components could implement it and decide for themselves what they want to do on HMR. In case developers want to reset some states in component A but not in B, they can do so and decide on their own.

I like this approach because taking general decisions on state reset seems likely to become problematic given the fact that we have no convention on where the states are defined. Also, states could point to DOM elements which might be lost after a HMR event occurred. These might be conditions only the developer of a component may know.

Was this page helpful?
0 / 5 - 0 ratings