Web technologies are fast paced and it’s often very hard not to re-invent the wheel every three months. So I tried to avoid the re-inventing-wheel problematic and head over to the how to enable the power of the latest tools enrolled in frontend technologies to make our wheel faster and better using the new tools like IntersectionObserver or Webpack’s Dynamic Import.

The problem with the computation and payload waste

Nowadays an average page rendering looks like this:

Browser: “Hey server, give me the content for https://example.com”

Server: “Yes, Sir. Here it is — btw. it’s an HTML Document.”

Browser: “Thanks. Just saw it, I need a main.css and a bundle.js next. Do you find it somewhere?”

Server: “Here they are, the main.css file is a Stylesheet weight 89kb while the heavy duty comes now with a bundle.js weight 654kb. Hope you can use them.”

Browser: “Thanks mate. It’s what I needed, but honestly I think they contain unnecessary selectors and features. Anyway — see you soon.”

Sure, the communication between client and server never actually happen like this in reality, but there is some truth in it for a lot of websites:

Assets are very often bundled into a single file for the whole project

Bundled JavaScript or CSS files mostly contains the code for every page of the entire website / application no matter if it is used on this page or not

So we usually load functionalities and styling rules that are unused on the actual page and much more often a user don’t see because he doesn’t scroll all the way down. (Nice read: UX Myth — People don’t scroll)

What if a website would only load features and styles within the viewport?

In fact, this would be the most effective way and reduces the payload waste to a minimum. But this is hard to achieve manually. And how shall a developer know exactly which components are loaded on the website? Impossible to have a 100% guarantee there.

Let us think about this track and what’s necessary to make this a success story:

Code must be splitted into small pieces for each feature.

See: Webpack’s code splitting approach. An element must be able to tell that it appears in the DOM and in the Viewport.

See: MutationObserver and IntersectionObserver There must be some kind of a registry that knows which element needs which assets to be loaded.

See: Gluebert.js and Dynamic Import A critical path css should be loaded from within the HTML-Document to avoid a FOUC (Flash of unstyled content)

process visualization of lazy loaded web components

What’s the benefit of such an approach

The user experience increase massively by improving the perceptual speed index since the code size is reduced to a minimum and lazy load everything when needed (like the common lazy load approach for images — but for whole components).

That way we will significantly improve the time until the first meaningful paint, first Interactive and consistently Interactive over a classical approach. All of them are key performance indexes in Googles Lighthouse audit.

Ok, you made me curious, how to start?

First, let’s create a simple Webpack setup with the following settings:

The necessary part above is just the chunkFilename property at output. So Webpack will create a single chunk file for every feature you import using dynamic imports in your code.

Everything else in the example above can be overwritten by your regular Webpack configuration.

Next we need to install Gluebert.js. Gluebert enables this whole flow and makes a hassle free usage of MutationObserver and IntersectionObservers as well as module binding, when a component appears in the viewport.

The npm-package can be installed easily like:

npm install gluebert

Next in our src/app.js entry point we start bootstrapping the app like:

See: https://github.com/wildhaber/gluebert-getting-started/blob/master/src/app.js

The module signatures are the essential part, that interlinks actual DOM components with the application. A module signature could look like:

Once this module is registered in the main instance, when an element with a matching selector to .c-lazy-img appears within the viewport, content from ./lazy-img.controller.js as well as the stylesheets ./lazy-img.styles.scss are loaded on demand.

I don’t want to go too much into detail of gluebert.js-detail. You can find everything and a getting-started in the following repositories:

To see this approach in the wild, just open your terminal when while browsing through https://gluebert.com

mascott of gluebert.js

So, we have found a universal cure?

Well, obviously not — but it’s at least one approach worth challenging. Looking forward to get your thoughts about the approach above.