replace-tag with="high-performance"
Tags: webcomponents, haxtheweb
Published: 2021-05-12 19:57:11

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](https://www.npmjs.com/package/@lrnwebcomponents/replace-tag) if it meets your needs!

Background reading: The magic script (read the whole [series](https://dev.to/btopro/part-1-how-penn-state-unbundles-web-components-for-cdn-deployments-20di)) 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](https://github.com/elmsln/lrnwebcomponents/blob/master/elements/replace-tag/src/replace-tag.js)

## What replace-tag looks like
```html
<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>
```
`replace-tag` works with the `wc-registry.json` file produced in the [unbundled-webcomponents](https://github.com/elmsln/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](https://github.com/elmsln/lrnwebcomponents/blob/master/elements/replace-tag/lib/PerformanceDetect.js) with this specific block:

```js
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;
  }
```
This function has to be `async` because `getBattery()` (which is not in all platforms) is a `Promise()`.

## Video version
{% youtube 2n13zuXQDkA %}