Skip to content

Hot module replacement API #6

@43081j

Description

@43081j

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions