Search code examples
javascriptweb-componentcustom-element

Async issues when wrapping a WebAssembly module into a Web Component


I have a WebAssembly module that renders some stuff on an HTML5 WebGL canvas. Everything works as it should. However, I would like to wrap everything up in a Web Component to simplify the usage of that module.

The WebAssembly module is quite large and hence takes a couple of seconds to download. There is a JavaScript callback function that is invoked once the download of the WebAssembly module is done.

The give some optical feedback to the user that something is going on while the module download is in prograss I'm drawing an animation to the canvas while the module is being downloaded. That animation is stopped in the callback function and the module is instantiated there.

Conceptually, I'm struggling to combine that JavaScript callback function with the class that represents the Web Component. Here's what I have so far:

// Code that starts downloading the WebAssembly module (asynchronous)
...

var downloadAnimation;

// The class that represents the custom Web Component
class MyWebComponent extends HTMLCanvasElement {
  constructor() {
    super();
    // Initialization
    ...
  }

  connectedCallback() {
    // Start the animation indicating to the user that a download is in progress
    downloadAnimation = new DownloadAnimation(this);
    downloadAnimation.start();
  }

  // custom functions and properties enabling the usage of the WebAssembly module
  ...
}

// Register the custom Web Component so that instances of it can be created.
// If this were to be done in the onModuleReady callback function there would
// not be an animation that runs while the download is in progress.
if(!customElements.get("MyWebComponent")) {
  customElements.define("MyWebComponent", MyWebComponent, { extends: "canvas" });
}

// Called once the WebAssembly module is downloaded, i.e. when we're ready to instantiate it
function onModuleReady() {
  // First, the download animation is stopped
  downloadAnimation.stop();

  // Next, I could instantiate the WebAssembly module as shown below.
  // But how do I connect that instance with the instance of the Web Component
  // that is created via HTML?
  let myWebAssemblyInstance = new Module.MyCustomClass();
}

To be able to run the download animation the Web Component must be created before the WebAssembly module is downloaded. Therefore, I cannot simply execute customElements.define in the onModuleReady callback function. That would be too late to start the animation.

Once the module is downloaded I can start creating instances of the it. But I don't see how I can connect such instances with the Web Component instance that gets created via HTML. Because I'm outside of the class that represents the Web Component, I don't know how to access it.

Is there a way to access Web Component instances outside of the class that represents those instances, e.g. from within onModuleReady?

Or, more generally, is anyone seeing a better approach to handle this?


Solution

  • Make your component listen (at root level) for an Event.

    onModuleReady then dispatches the Event, which bubbles up.

    You probably want a CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent

    And beware you need composed: true for Events to 'escape' shadowRoots

    Conceptually no different than the standard onload or DomContentLoaded events

    Notes:

    • you are not restricted to DataTypes in the CustomEvent details payload.
      You can add Function References
      So a Receiver can execute Methods in/from the Element that dispatched the Event

    • Events are processed synchronous

    • For indepth Event handling see: What the heck is the Event Loop
      https://www.youtube.com/watch?v=8aGhZQkoFbQ


    Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/