Lazyloading Web Components
They say good developers are lazy. A tedious process is no match for a lazy developer. Below is an experiment in laziness. It’s a proof of concept for using Web Components without having to manually import each one.
I’m not sure exactly what to call it. It grazes the definitions of lazyloading, autoloading, and dependency injection, so I went with the laziest term.
Let’s set the scene, and find out if it’s a worthwhile developer convenience, or ease-of-use gone mad…
# Scene 1: Maximize Simplicity
You are the author of “Wootstrap”, a Bootstrap-like library of useful components like navigation, buttons, and alerts. Also like Bootstrap, your components are distributed as HTML snippets for users to copy, paste, and customize.
To improve ease of use, you encapsulate all that HTML into a custom element:
<ws-panel heading="I am a ws-panel">Some words.</ws-panel>
The essence of simplicity! One more thing, browsers don’t know what
<ws-panel>
is, so your users must import it:
<link rel="import" href="../bower_components/wootstrap/components/ws-panel.html">
One line, no big deal. But as your users add and remove components from their pages, managing imports becomes a finger-aching chore.
# Scene 2: Minimize Finger-Ache
Wouldn’t it be cushy if the components were imported when you use them, automatically?
# Live demo
# Demo code
<html>
<head>
<script defer src="src/webcomponents.min.js"></script>
<link rel="import" href="src/lazy-load/lazy-load.html">
</head>
<body>
<lazy-load>
<ws-panel>I am a ws-panel.</ws-panel>
<ws-btn>I am ws-button</ws-btn>
<ws-alert>I am a ws-alert</ws-alert>
</lazy-load>
</body>
</html>
The demo only imports lazy-load
, which encapsulates the importing of the rest of the elements so you don’t have to specify them by hand. Let’s look at how it works.
# Scene 3: Behind the Scenes
To lazyload, we need a list of all our component names and where to find them. I created a small “registry” object for the demo. In the real-world, this object could be auto-generated by the component library’s build system, or by the build system of the user’s app, including all available components.
var LazyComponents = {
names: "ws-panel,ws-btn,ws-alert",
paths: {
"ws-panel": "./src/ws-components/ws-panel/ws-panel.html",
"ws-btn": "./src/ws-components/ws-btn/ws-btn.html",
"ws-alert": "./src/ws-components/ws-alert/ws-alert.html"
}
};
First we have names
, a comma-separated list of component names which doubles
as a selector. It is passed into querySelectorAll() to find any
occurrences of our elements on the page. Second is paths
, a mapping from
each component’s name to its HTML template file.
# The lazy-load element
Here’s the Polymer definition for the lazy-load
element:
Polymer({
is: 'lazy-load',
attached: function lazyLoadAttached() {
this.lazyLoad();
},
lazyLoad: function lazyLoad() {
var els = this.querySelectorAll(LazyComponents.names);
for (var i = 0; i < els.length; ++i) {
var name = els[i].nodeName.toLowerCase();
this.importHref(LazyComponents.paths[name]);
}
},
});
When lazy-load
is attached to the DOM, it finds any children in
the names
list and imports their HTML templates (using Polymer’s handy
importHref() function).
Now any element in the registry object can be used without an explicit import.
# Tradeoffs
There are some things that may outweigh the convenience.
- Unvulcanized - Lazy loading is the opposite of Vulcanize. Vulcanize can still be used, but lazyloaded components won’t be inlined.
- Delayed loading - Explicit imports begin downloading before lazyloaded imports, since they don’t have to wait for
lazy-load
to be attached to the DOM. - Paths must be absolute - Imports typically use relative paths, but lazyloaded paths are defined in only one spot, not per-page, so they must be absolute, or relative to
lazy-load.html
. - Late to the party? - Any custom elements added to the page after
lazyLoad()
has run won’t be imported. That didn’t seem necessary for a proof of concept, but runninglazyLoad()
on each state change would be an easy solution. - HTTP/2 Server Push? - I haven’t tested this, but I suspect lazyloaded components won’t benefit from HTTP/2 Server Push.
I’m not sure yet which camp lazyloading will fall into: best practice, or antipattern. My hope is that developer tooling will be written that injects imports as you code, rather than at runtime.
Thanks to Kyle Buchanan for pointers and corrections!