matthewp / haunted

React's Hooks API implemented for web components 👻

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Hot module replacement

LarsDenBakker opened this issue · comments

Hi there! At open-wc we started working on hot module replacement with es module workflows: https://open-wc.org/docs/development/hot-module-replacement/

So far it works for class based components. We're now looking into how we can support this for function based components.

The idea is that the web component base class implements a static and/or instance hotReplaceCallback function which does the actual replacement. Since you don't want this code to end up in production, projects can make it available through a module which patches the base class.

Since haunted in the end creates a class, I was hoping to do the same here. However one issue I ran into is that the class is created in a closure, so I can't inject any code into it. Also, the renderer function is only available within the closure.

I made some modifications to the source code and the basic features are working, so that looks promising. I did run into some issues with preserving state between replacements, so that would be something to look into.

Is this something that would be interesting to support in haunted?

Hey Lars, thanks for starting the discussion. Is there a reason why you can't do something like this?

import { component } from 'haunted';
import hot from 'hot-web-components';

function App() {
 ...
}

const AppElement = hot(component(App));

ie, wrap the class that component() creates?

@matthewp I did some rework, and indeed that's how it works now.

I got it working now for Haunted using a small patch: https://github.com/open-wc/open-wc/blob/master/packages/dev-server-hmr/src/presets/haunted.js#L3

Ideally I would like to patch the hotReplacedCallback onto some base class, would it possible to expose this from within Haunted? I could patch it onto each haunted component individually, but a user might be using multiple component base classes so we'd have to detect that something is a haunted component which makes things more complicated.

A bigger problem is updating the scheduler with a new renderer. Would it be ok to expose the renderer as a static field on the class? Then I could grab it there. I was able to work around it by instantiating a temporary element and getting it from the instance.

Ideally I would like to patch the hotReplacedCallback onto some base class, would it possible to expose this from within Haunted?

Not sure I follow, what change are you suggesting?

A bigger problem is updating the scheduler with a new renderer. Would it be ok to expose the renderer as a static field on the class?

Yeah that's fine, but the scheduler is a privatish field at the moment. We should probably make it public if library are going to depend on it.

@LarsDenBakker Don't know if you are still looking into it. This is how I got esm hmr working in my system.

const tag = 'my-counter';
const Module = await import("/assets/my-counter.js");
document.querySelectorAll(tag).forEach((node) => {
  const Scheduler = class extends BaseScheduler {
      constructor(renderer, frag, host) {
          super(renderer, host || frag);
          this.frag = frag;
      }
      commit(result) {
          render(result, this.frag);
      }
  }
  node._scheduler = new Scheduler(Module.default, node);
  node.connectedCallback();
}