replace-tag: Dynamically load content based on device capabilities
Tags: webcomponents, haxtheweb, performance
Published: 2021-02-17 19:28:14

I had an idea the other day that quickly consumed me; The magic script (see 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.

[The code powering replace-tag](https://github.com/elmsln/lrnwebcomponents/blob/master/elements/replace-tag/src/replace-tag.js)

## Video version
{% youtube 2n13zuXQDkA %}

## 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.

## 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()`.