PurePWA — A Radical U-Turn in Web Development

Someone had to pull the Emergency Brake — and Lead us all Back to the Purity of the Web Standards

Marc van Neerven
CTO-as-a-Service
6 min readJan 31, 2024

--

Pure PWA demo on Mobile

Ever since its birth, people have been building stuff on top of web standards, to ease development efforts.

There was a time where each browser had its own interpretation of nearly everything — thankfully, that era is long behind us.

Some of the frameworks and libraries have in fact led to the evolution of the Web Standards themselves, such as jQuery and React.

Kudos for that. Really.

It’s now time to reflect, and take a closer look at what we’ve done.

Ever more abstractions, ever more complex build- and bundling strategies, and ever more megabytes of NPM packages for everything.

Bundle splitting, tree shaking, lazy-load bundle parts, virtual DOM, synthetic events, the list goes on.

Have we gone mad?

Back to Simplicity

Actual Web Standards? We’ve kind of lost touch with them.

Recently, the “State of HTML” survey has shown that many so-called web development seniors are not even juniors when it comes to basic understanding of web standards.

The survey put me in the top 1% with a score or 107/131, and someone I know reported he was in the top 25% with only around 80/131.

This adds proof to what I’ve been saying all along: these ‘seniors’ rely on their framework (or a framework-specific package) to get things done, and don’t even bother to find out a direct/native way. 🤯

While you were away, the Web has grown up into this bright, responsible adult, who’s perfectly capable of doing everything you were doing, but in a more lean, easy, lightweight, independent, and sustainable way.

Time for some Detoxing

So here’s what I did, just as an experiment: I built an Ultra-Lean, Web Components enhanced, no-build, no-dependencies boilerplate for PWAs, using only the Modern Web.

Go fetch it at https://github.com/mvneerven/pure-pwa

GitHub — mvneerven/pure-pwa

The Art of Zen Meditation

The experience of putting this together has been (and is) like a Zen Meditation off-site: it’s sobering, cleansing, purifying, and enlightening.

It really is a great idea to not start with a framework or any other big dependency, because it forces you to think about what you want to achieve and get it working, before enhancing it with what’s needed. I can advise everyone to do the same.

Too often, developers start with a Framework, then start thinking how to get what they want rendered by it, and finding ways in which the framework allows them to do it.

I’ve found out that The Modern Web is a beautiful place. Get out of your framework-induced rabbit holes and come and see!

I mean this very literally:

  • Start with the HTML structure. Try as much as you can to make it represent what you want it to. Don’t try to make it pretty yet, instead focus on meaning:
<label>
<span data-label>Animation</span>
<input type="range" min="0" max="1" step="1" name="animation" />
</label>

Note that I enclose inputs in a <label>. This avoids both the ‘id’ attribute in the input and the ‘for’ attribute in the label, and is fully supported by every browser.

  • You can already see that, semantically, the control expresses a toggle between two values: ‘0’ and ‘1’.
  • Only when you’re happy with the base structure, try to make it work without scripting.
    In this case, a submit would already post ‘1’ or ‘0’ here.
  • Now, enhance it with scripting if needed, but do so with a (semantically appropriate) custom tag, and write the enhancing code in the attached Web Component.

An example: a range element with binary values, acting as a ‘switch’:

<range-switch>
<label data-form-element="switch" data-selected="@useAnimations">
<span data-label>Animation</span>
<input type="range" min="0" max="1" name="animation" />
</label>
</range-switch>

The script:

import { CustomElement, enQueue} from "../../common.js";

customElements.define(
"range-switch",
/**
* Turns an input[type="range"] into a Switch Control
*/
class RangeSwitch extends CustomElement {
static get observedAttributes() {
return ["value"];
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === "value" && newValue) {
this.querySelector("input").value = parseInt("0" + newValue);
}
}

connectedCallback() {
const condition = () => {
return range.value === "1";
};

const range = this.querySelector('input[type="range"]');

range.addEventListener("input", (e) => {
this.setAttribute("value", condition() ? 1 : 0);
});

enQueue(() => {
this.setAttribute("value", condition() ? 1 : 0);
});
}
}
);

💡The CustomElement class is discussed in detail in the GitHub repo readme.

  • Now make it pretty:
range-switch {
input[type="range"] {
appearance: none;
width: 3rem;
height: 1.2rem;
background: var(--color-surface-mixed-100);
outline: none;
border-radius: 50px;
cursor: pointer;
transition: .2s ease-in-out;
padding: 0;

&:focus {
outline: 2px solid var(--color-accent);
}

&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 1.4rem;
height: 1.4rem;
background: var(--color-accent);
filter: saturate(0);
border-radius: 50%;
cursor: pointer;
transition: .2s ease-in-out;
}
}

&[value="1"] {
input[type="range"] {
&::-webkit-slider-thumb {
filter: none
}
}
}
}

PurePWA Principles

💡 Remember: this is a radical experiment. I’m not religious about the principles I applied. I only applied them to prove one point: you don’t need more than the Pure Web Standards to develop great Web Apps/PWAs.

👍🏼 Yes

  • Semantic HTML
  • Full Progressive Web App
  • Web Components
  • Progressive Enhancement
  • 100% Scores in all Lighthouse Categories
  • Runtime ES module importing
  • Icons in SVG Sprite Sheet
  • ‘Native’ Look & Feel

👎🏼 No

  • NPM
  • Build System
  • Polyfills ¹
  • Bundling
  • TypeScript. Just 100% Vanilla JavaScript
  • Dependencies

Output

Apps created with this approach don’t suffer from the missing complexity.

In fact, end users will not spot the differences.

What they might spot, is the sheer speed, and the lightness of the initial loading experience.

Also, in this mobile era, I have put a lot of focus on getting the ‘wow’ effect, and assisting UX with so-called micro-animations.

PurePWA on Desktop & Mobile

Start with the End User in Mind

It’s always a good think to work our way backwards from what we know the end user wants.

As I have been saying, the Web Development community has been focused too much on the Developer Experience, and too little on the End User one.

We all tend to forget that end users don’t have nearly the same high end devices as we do, nor the internet bandwidth that we’re so used to, so when we install and navigate our apps, we tend to think it’s all good, whereas someone on an older Android phone with poor Wifi or worse, 3G/4G, would have to wait seconds to get anything in, and longer to get to full interactive.

Read Infrequently Noted (Alex Russel) for more information about this topic.

Lighthouse scores for PurePWA

Next Steps

This experiment has been a lot of fun, but, as it goes, is never finished.

I had intended not to use any polyfills, but a not-so-modern browser called ‘Safari‘ is forcing me to rethink some plumbing.

[EDIT]

¹ PolyFills added for Navigation: navigate event and Document: startViewTransition() method.

💡 Follow the #purepwa hashtag and the GitHub repo to see next moves.

--

--

Marc van Neerven
CTO-as-a-Service

Transformational (fractional) CTO, Board Advisor, Cloud & SaaS Expert, Code Ninja, Web Standards Advocate, Social Impact Lover