Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hot module replacement API #6

Open
Tracked by #29
43081j opened this issue Oct 28, 2020 · 6 comments
Open
Tracked by #29

Hot module replacement API #6

43081j opened this issue Oct 28, 2020 · 6 comments

Comments

@43081j
Copy link

43081j commented Oct 28, 2020

Currently it seems a fair amount of projects are working towards implementing HMR support.

A couple of existing implementations related to webcomponents/ESM:

These are only a couple, there will be more. However, there is no consistent API right now across these.


There are three parts to HMR as far as I can see:

  • Server-side (basically a file watcher which notifies the client when a module changes)
  • Client-side (an API to communicate with these server updates)
  • Framework/library specific (an integration of the client-side API into a specific ecosystem like lit-element)

Server-side

The server-side implementation should be as simple as a web socket service which emits messages of the following types:

  • update - a message specifying that a particular module needs reloading
  • reload - a message specifying that the page must reload as a whole

Client-side

An API should be made available at import.meta.hot which can have methods for the following:

  • Accept updates (notify the server this module can handle updates, via an accept message)
  • Refuse updates (notify the server this module cannot handle updates)
  • Invalidate the current module (if something went wrong, force a full reload)
  • Disposer (handle teardown of the module before a new version is loaded)

The client-side implementation should primarily exist to handle the server-side messages, though it should also emit its own message:

  • accept - a message specifying that the current module supports HMR

Handling of the server-side messages could look like this:

  • update - dynamically import the specified module and execute a user-supplied callback for dealing with the update
  • reload - call window.location.reload i suppose

Example implementation

Within the modernweb repo I wrote the following message types:

// emitted by the server
export interface HmrReloadMessage {
  type: 'hmr:reload';
}

// emitted by the server
export interface HmrUpdateMessage {
  type: 'hmr:update';
  url: string;
}

// emitted by the client
export interface HmrAcceptMessage {
  type: 'hmr:accept';
  id: string;
}

Note that the message types are prefixed here because we already had a web socket open and didn't want to have a second just to specify the protocol. Though it could be argued a protocol is better here than a prefixed set of types.

Meanwhile, i used snowpack as inspiration to write a client API which looks like this:

// at import.meta.hot

{
  accept(callback);
  accept(deps[], callback);
  dispose(callback);
  decline();
  invalidate();
}

However i'm not such a fan of it even though i did it. As confusion can quickly come about by weak naming.

I would suggest more like:

{
  acceptCallback(callback);
  acceptCallback(deps[], callback);
  disposeCallback(callback);
  decline();
  invalidate();
}

Framework/library specific

For example, the work being done to lit-element around HMR will produce an overridden customElements.define which then understands how to update an element when it is re-defined.

Peter's work in the lit branch has this:

static notifyOnHotModuleReload(tag, newClass)

Which i agree with, though maybe named with a Callback suffix like connectedCallback and such.

The idea here being every hmr-compatible web component would have this standard static method which the library or user must implement.

Summary

I think the most important thing to get right here is the client API available at import.meta.hot and the framework/library specific interface.

@LarsDenBakker
Copy link

I think es module HMR is something to "standardize" on outside of this platform, basically what has been started at https://github.com/snowpackjs/esm-hmr.

I think the main point of interest here is to try to standardize on an update pattern for web components, such as a standard callback to call.

@43081j
Copy link
Author

43081j commented Oct 28, 2020

You are right, i've updated the post to focus a bit more on the client and the web component stuff.

@abdonrd
Copy link

abdonrd commented Oct 30, 2020

Another reference from Salesforce from @diervo and @caridy: salesforce/lwc#2071

@diervo
Copy link

diervo commented Oct 30, 2020

Thanks @abdonrd for the heads up.

Given that I was implementing HMR recently and looking at other implementations, here are some observations:

In general I think many frameworks need the per module import.meta.hot described above, primarily because they need to observe and potentially revert or change some side-effects and swap bindings of things that they reference or hold.

However I believe there are also frameworks where they might be simple enough to have a centralize module swapping. For example:

// centralized hmr example  
import { updateModule } from 'lwc';
	
export function onModuleUpdate(oldUri, newUri) {

    const oldModule = await import(oldUri);
    const newModule = await import(newUri);

    updateModule({
        oldModule,
        newModule,
    });
}

On our LWC framework at Salesforce for example, we can swap any component js, css or html from a unique centralize place without issues with side-effects.

I know that this approach might not work for cases due to identity discontinuity or other odd side-effects, but I just wanted to put it here in case its worth exploring different angles and/or APIs.

While mostly its implementation framework-land details, if we could avoid for certain apps de need to prepend/append import.meta stuff will be interesting (it is observable nonetheless). However the proposed APIs seems a simple and complete way to go forward as well.

@LarsDenBakker
Copy link

Swapping an element's HTML/CSS is simple enough, but what about adding/removing/changing class methods and/or changes in other extended elements or applied mixins?

@LarsDenBakker
Copy link

LarsDenBakker commented Nov 23, 2020

I've started an approach for universal web component HMR at https://open-wc.org/docs/development/hot-module-replacement/

The idea is that the base class can implement a static and/or instance hotReplaceCallback, callback. This decides how to do the actual replacement.

I'm going through different base libraries to see if this approach works for them. So far lit-element and fast element works, and I got haunted working with some code changes.

The plugin I linked is based on es module HMR, I wasn't able to figure out yet how to use LWC with regular es modules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants