When COVID started spreading in China in January 2020, I took it upon myself to build The Coronavirus App. That simple PWA has since been used by more than 15 million individual users.
The problems I encountered while building that app then inspired me to create Progressier, a SaaS platform that makes it possible to add the entire block of functionality we call "Progressive Web App" to any domain without having to write any code.
In this article, I'll share some tips and tricks about what I've learnt developing PWAs from scratch with Vanilla JS.
Not so easy... but worth it
A PWA offers a sexy promise: only one code base to manage and your app works on all platforms. In practice, that means that you have to make that one code base do a lot more things than if you developed several standalone native apps.
The pros probably outweigh the cons most of the time. But it isn't black or white. Developers blindly promoting PWAs as a replacement for native apps simply haven't spent enough time developing PWAs. Native apps also have their place.
Don't go desktop-first... nor mobile-first
Until a decade ago, most websites were first and foremost optimized for desktop use. Then came mobile phones, and we started making websites responsive.
With PWAs, you can't think desktop-first. And you probably shouldn't think mobile-first either. A PWA created with Vanilla JS has to look and feel like a native app on mobile. But it also has to look like a proper desktop app on desktop.
These are two completely different UI paradigms — it's not just about the size of elements. For example, mobile UIs tend to display only one interactive element at a time while desktop UIs usually have many of them displayed simultaneously. Here are some concrete examples:
A standard dropdown menu on desktop becomes a bottom drawer with an overlay on mobile
Desktop accordion items become standalone full screen components on mobile
A side panel searchable list on desktop becomes a mobile search bar
As a rule of thumb, create one single HTML element and use CSS to style it. Often that will mean changing the element position
from relative
to fixed
or absolute
.
Sometimes, it isn't really possible. When developing a PWA with Vanilla JS, it's not uncommon to run into z-index
issues. An element has to appear on top of a sibling of their parent container when it's open on mobile, while the parent has to appear underneath the sibling when it's not. When that happens, you'll have to implement a few tricks to modify the z-index
of the parent dynamically with JavaScript.
When you design components for a PWA, start with the functionality, then design their mobile and desktop versions concurrently. And only then figure out what the right HTML structure should be.
Abstract away
Proponents of frameworks like React or Vue sometimes argue that Vanilla JS is too verbose and inefficient. They also argue that if you solve that by abstracting the browser APIs, you're essentially creating your own framework (aka "reinventing the wheel"). Below are two code snippets that do the exact same thing:
let onClick = function(e){console.log(e.target.value);}
let parentNode = document.querySelector('#parent')
//PURE VANILLA JAVASCRIPT
let input = document.createElement('input');
input.classList.add('cool');
input.addEventListener('click', onClick);
parentNode.appendChild(input);
//MY HOMEMADE VANILLA JAVASCRIPT ABSTRACTION
utils.node('input', 'cool', {parent: parentNode, click: onClick});
The benefits of my homemade abstraction are pretty obvious. 61 characters instead of 139 means you save time typing code and the browser saves time getting it from your server. Each HTML element being one line, your code also becomes easier to read and organize.
Yet both functions are semantically identical. They both create a DOM Node, add a class and an event listener to it, and append it to the DOM. Vanilla JS is about using the default browser APIs. Frameworks, on the other hand, are opiniated. They introduce their own preconceptions about how things should be done. Think how React uses JXS to create a hybrid HTML/JavaScript declarative style for example. Frameworks create different paradigms. Shortening the syntax of Vanilla JS doesn't fall into that category, in my humble opinion.
Ten years ago, jQuery was popular because it made things more consistent across browsers. Nowadays, most browser APIs are so well-built and documented that you probably don't need anything else. Another good example is Moment.js — dealing with dates and time used to be a pain in the ass. Now with the Date() interface, it's easy. And it's available in JavaScript.
So use Vanilla JS but build your own abstractions. Make it as simple as possible to write, understand, organize and modify your code. You're definitely going to need to be organized to make a PWA created from scratch with Vanilla JS work on all the platforms it has to support.
Design reusable components
Without a framework structuring your code for you, you have to be extra careful not turning your project into spaghetti code. What has worked well for me is create semantic silos / components. One component is a JavaScript function that contains everything that pertains to that component: the HTML, DOM nodes, event listeners, CSS, logic are all in the same place.
In addition to making your code more readable, it also makes it easier to iterate on your product. When you have to delete a component, you just remove the entire block of code. You can be sure it won't break anything else, and your code base never contains leftovers from previous iterations.
You don't really need to use frameworks to build components. In fact, you'd be surprised how easy it really is with Vanilla JS. And you don't even need the fancy class declaration either. Below is my basic structure for creating components.
function ComponentName(parent){
let that = this;
this.parent = parent;
this.componentId = "component-id";
this.styling = `
.`+that.componentId+`{position:relative;}
.`+that.componentId+`-inner{width:100%;}
`;
this.fetch = async function(){
//do whatever async operations I need to do
//e.g. query data of the component from DB
}
this.stylize = function(){
//create a <style> node and set its id to that.componentId
//set the content of the <style> node to that.styling
//then simply append it to the DOM
//(or overwrite the content of an existing <style> with the same ID)
}
this.create = function(){
//create a new container for the component
//append it to that.parent
//store it as that.element
}
this.render = function(){
//empty that.element and recreate its inner content
}
this.init = async function(){
await that.fetch();
that.stylize();
that.create();
that.render();
}
//this component self-initializes when created
this.init();
}
I can't recall ever needing more than that. It really does everything you might want it to do: create custom CSS, create a container, lets you wait for data from the network if needed, lets you re-render the container when data changes.
And because you're using Vanilla JS, you can structure each component slightly differently. For example, a different component may not be self-initializing like the one above. Or it may be invoked with completely different parameters, say data from another component.
Of course, there's probably a thousand other ways you could go about this that would work equally well.
Use the right tools for the job
Recommendation 1: Use libraries
Using Vanilla JS doesn't mean you can't utilize libraries that abstract some complex things that aren't available straight out of the box in the browser. The key is that these libraries should work in their own silo and not force you to rewrite your entire app around them. For example, don't build your own maps — use Leaflet.js. And don't build your own charts — instead, use Charts.js.
Recommendation 2: BrowserStack
You're going to have to spend a substantial amount of time testing your PWA in different browsers. Personally I'm a big fan of Browserstack. The platform allows you to test any web app or website on every browser/OS combination imaginable — even older devices. And these are real devices, not emulated devices. The $39 I pay every month for it are well worth it. I'm not affiliated with Browserstack in any way by the way.
Recommendation 3: MDN Web Docs
I absolutely love MDN. It's essentially a list of all APIs available to you in JavaScript. Each come with extremely comprehensive documentation. As a Vanilla JS developer, if you only allowed me access to a single site on the entire World Wide Web, that's the one I would pick.
Recommendation 4: Progressier
I built it so I'm obviously biased but I can't end the article without recommending Progressier. When building The Coronavirus App, it hit me that the entire block of functionality we call PWA (caching strategies, installability, push notifications) was unnecessarily annoying to implement. So I decided to build an abstraction for it — which you can add to your own app with a single line of code.
That's all folks!
Have you built a PWA with Vanilla Javascript yet? How was your experience with it? What other tools would you recommend using?
If this article helped you in any way, please consider leaving a comment below 🙏
Top comments (0)