DEV Community

Bryan Ollendyke
Bryan Ollendyke

Posted on • Updated on

constructor() dynamic import()

Modern JavaScript via ES Module imports, provides us with two ways of handling modular JavaScript. There's import whatever from where-ever style, and then there's import(). While minor in syntax difference, did you know they have a direct impact on the loading of your application? Let's look:

// knights-who.js
import "./the-parrot-sketch.js";
// really important class that says..
class KnightsWho extends HTMLElement {
  constructor() {
    super();
    if (this.getAttribute("say") != null) {
      let sketchTag = document.createElement("the-parrot-sketch");
      sketchTag.innerHTML = this.getAttribute("say");
      this.appendChild(sketchTag);
    }
  }
}
customElements.define("knights-who", KnightsWho);
Enter fullscreen mode Exit fullscreen mode

Then your main.html document might reference this really important modular JavaScript as follows:

<script type="module" src="knights-who.js"></script>
<knights-who say="Nee"></knights-who>
Enter fullscreen mode Exit fullscreen mode

We want... a shrubbery!

In this syntax, the browser responds with the following data cascade timing...

  1. GET main.html, start parsing
  2. See script type="module" start requesting knights-who.js
  3. Reads file for additional import references, finds the-parrot-sketch.js and requests that
  4. Reads file for additional import references, endlessly until there are no more additional modular references
  5. Completes modular chain of code, executes all at once,
  6. knights-who tag will say Nee, wrapped in a <the-parrot-sketch> tag; horrifying.

This is how modular JavaScript works though, it spiders out looking for additional modular import references and then once all of them load, it executes all of them. This is great for developers to ship modular code, but what if you had ~100 references nested in other references?

"One Weird Trick" Dynamic import()

A dynamic import() could be leveraged in our constructor() to visually look similar, yet have a very different execution timing.. Let's look.

// knights-who.js
// really important class that says..
class KnightsWho extends HTMLElement {
  constructor() {
    super();
    if (this.getAttribute("say") != null) {
      let sketchTag = document.createElement("the-parrot-sketch");
      sketchTag.innerHTML = this.getAttribute("say");
      this.appendChild(sketchTag);
      setTimeout((e) => {
        import("./the-parrot-sketch.js");
      }, 0);
    }
  }
}
customElements.define("knights-who", KnightsWho);
Enter fullscreen mode Exit fullscreen mode

In this setup, we use import() inside of our constructor(). By doing this, we get the following timing on spin up.

  1. GET main.html, start parsing
  2. See script type="module" start requesting knights-who.js
  3. Reads file for additional import references, finds none.
  4. Completes modular chain of code, executes all at once,
  5. knights-who tag will say Nee, wrapped in a <the-parrot-sketch> tag (undefined). So it starts to paint while in the background, delayed one microtask, ./the-parrot-sketch.js read off endlessly until there are no more additional modular references, but the tag is imported on it's own schedule!

The key difference here is that we've started to paint potentially long before we would have otherwise by breaking our chain into multiple execution chains! While small in a single element, imagine building a whole application where every step of the way you handled info this way.

Here's a gif showing this happening at scale in HAXcms as loaded on haxtheweb.org. Loading has been throttled to 3G to demonstrate, but all the parts of the UI are web components and all parts load in via a series of broken up import() chains to optimize delivery.

haxtheweb.org loading throttled

Considerations

This breaks up timing so you could get a FOUC if there is a non-hydrated element that has spacing considerations (which is likely). In the .gif above a chunk was cut out that was just a white screen as we need to fix a our loading indicator's timing to avoid FOUC 😳. But, even with this, we don't actually flash unstyled content as we currently just have a loading bar that goes until the UI is ready. Individual UI elements then have sane sizing defaults using a css selector trick of :not(:defined) {} which helps w/ selecting web components that do not have a definition (yet).

The import() methodology is to speed up time to first paint (TTFP) and so you could use some sizing styles or css or stateful variables internal to the import in order to reduce FOUC. We'll go into dynamic import Promise later but here's a taste:

  connectedCallback() {
    this.setAttribute("hidden", "hidden");
    import("./what-ever.js").then((m) => { this.removeAttribute("hidden")});
  }
Enter fullscreen mode Exit fullscreen mode

While simplistic, this would allow the whole application / other elements to keep loading in the background while the user still obtains part of the experience. connectedCallback means we it is attached to the DOM and thus we can start setting attributes. This code would "paint" the element, then hide it, then when the internals of what-ever.js have loaded, it would reveal the entire element.

Discussion (4)

Collapse
dannyengelman profile image
Danny Engelman

Can you explain what sketchTag does in this code. And where does say come from in this.innerHTML = say;?

Collapse
btopro profile image
Bryan Ollendyke Author

lol. Why yes I can! By gaslighting you completely and saying "but it really says this.innerHTML = sketchTag;". In this, we can see that you have found a mistype and I hope that now the update makes more sense :).

The point of sketchTag was simply the idea of another tag providing the advanced functionality. aaaannnnnd then I didn't actually set it to innerHTML (a horribly simplistic method of making this load) :)

Thanks for the note. Updated both doc blocks.

Collapse
dannyengelman profile image
Danny Engelman

Dont gaslight me, i fart pure alcohol. Code is wrong... your trying to stuff dom elements into a string

Thread Thread
btopro profile image
Bryan Ollendyke Author

The following code was always there, under every comment that was made, and has never been modified from it's original, posted below.

      let sketchTag = document.createElement("the-parrot-sketch");
      sketchTag.innerHTML = this.getAttribute("say");
      this.appendChild(sketchTag);
Enter fullscreen mode Exit fullscreen mode

room explodes