DEV Community

Bryan Ollendyke
Bryan Ollendyke

Posted on

replace-tag with="high-performance"

In our "performance book" we'll have examples of things you could readily implement. While you could do so with replace-tag, I realize that perhaps this is more of an idea jog than direct implementation. Consider this a creative use-case that you could reverse engineer, or adopt directly from npm if it meets your needs!

Background reading: The magic script (read the whole series) is great for developer workflow and integration is simple, but it seems like it could be even more performant.

I was reading a blog post about the idea of querying a phone for it's connection speed and went down the rabbit hole to this idea: combining the two. You probably have to watch this to understand where I'm going but consider the following:

  • A single tag could have dozens of dependencies
  • You might not see it till going down the page
  • But you've still had to trace the import ESM chain of files even if it's not rendering
  • Some devices (old, slow, low power) might not even be in a user context to WANT what you are making them download (just to make it visible when swiping down to reveal)

The code powering replace-tag

What replace-tag looks like

<replace-tag with="meme-maker"
  top-text="Web components"
  bottom-text="Its the platform"
  image-url="https://media2.giphy.com/media/3cB7aOM6347PW/giphy.gif"
></replace-tag>
Enter fullscreen mode Exit fullscreen mode

replace-tag works with the wc-registry.json file produced in the unbundled-webcomponents build routine.

Decision tree

replace-tag has the following checks in place:

  • Am I visible? NOW you can import my definition, then replace the replace-tag with whatever is listed in with=""
  • If I have the import-only attribute, then import() and
  • Am I a low performance device? If import-method="view" then still activate based on visibility; otherwise, don't import and replace until the user specifically clicks me to do so.

Performance benefits of this

  • low import chain of files (faster initial paint)
  • intersection still the primary driver of importing that chain of files
  • device detection can help save on data and battery in critical UX moments (user away from power source trying to access critical data of your site / app, but losing power fast)
  • When 1 tag gets imported (definition wise) then it detects and imports all others

Possible issues

  • Things that require being there at run-time
  • Certain event timing that depends on being on page, affixing to window level events
  • Potential FOUC / layout thrashing if you don't account for this as far as tag-name:not(:defined) but also then handling replace-tag[with="tag-name"]

What is a low performance device?

By our definition, it's any of the following being true:

  • Less than 1 gig of memory
  • Less than 4 core processor
  • 2g or 3g connection speed
  • user saying they want to save data
  • user's device having less than 25% power

Here's the cool block of code where we're able to do these detections using a series of navigator calls - PerformanceDetect.js with this specific block:

async updateDetails() {
    let details = {
      lowMemory: false,
      lowProcessor: false,
      lowBattery: false,
      poorConnection: false,
      dataSaver: false,
    };
    if (navigator) {
      // if less than a gig we know its bad
      if (navigator.deviceMemory && navigator.deviceMemory < 1) {
        details.lowMemory = true;
      }
      // even phones have multi-core processors so another sign
      if (navigator.hardwareConcurrency && navigator.hardwareConcurrency < 2) {
        details.lowProcessor = true;
      }
      // some platforms support getting the battery status
      if (navigator.getBattery) {
        navigator.getBattery().then(function (battery) {
          // if we are not charging AND we have under 25% be kind
          if (!battery.charging && battery.level < 0.25) {
            details.lowBattery = true;
          }
        });
      }
      // some things report the "type" of internet connection speed
      // for terrible connections lets save frustration
      if (
        navigator.connection &&
        navigator.connection.effectiveType &&
        ["slow-2g", "2g", "3g"].includes(navigator.connection.effectiveType)
      ) {
        details.poorConnection = true;
      }
      // see if they said "hey, save me data"
      if (navigator.connection && navigator.connection.saveData) {
        details.dataSaver = true;
      }
    }
    return details;
  }
Enter fullscreen mode Exit fullscreen mode

This function has to be async because getBattery() (which is not in all platforms) is a Promise().

Video version

Discussion (0)