Backend developers use encapsulation to separate and organise code to prevent conflicts, but what can frontend developers use in the browser to achieve the same end?
As we build web applications that become more and more complex, we must scale our development practices to handle an increasing amount of technical debt. The code we write should be modular, reusable and encapsulated to prevent tightly coupled applications. Monoliths are time-consuming and cumbersome for any developer to work on.
Browser vendors are working on APIs that allow us to create better encapsulated components in the browser. If you're developing an application with a lot of features in a single page, or an application relying on a lot of third party code, then encapsulation can't come soon enough for you.
What's a component?
"The best thing that can be said of software is that it is too small." - James Halliday, from Code Collage at JSConfEU 2012
You might be asking yourself why our current approach using jQuery UI or ExtJS isn't enough. These are modular component libraries that have already solved our need for these reusable code building blocks. Unfortunately, they're lacking the one thing we need to prevent problems like namespace collisions and awkward CSS specificity as we introduce more and more complexity into our application.
These methods inject code into the page where needed, but that code is not encapsulated. Many well-written libraries have been implemented in such a way that it's not something you have had to worry about. But they're not foolproof. Introducing encapsulation in the DOM could make things even better.
Encapsulation: a review
Code-wise, an object is encapsulated when it exposes a limited public interface to interact with other code, while its data and implementation details are private. Allow me to explain by way of giant robots.
The one time I get to use giant robots as a technical metaphor
Anyone remember the Mighty Morphin' Power Rangers? They were masked superheroes that battled against evil forces with giant armoured robots. Each Power Ranger had its own robot with its own internal workings; its own pilot and its own internal state that only he or she knew about. To deliver a finishing blow to an enemy, the Power Rangers would combine all the robots into a much larger robot called the Megazord.
When coupled, the component robots could interface effectively through their pilots via a shared command station without exposing the inner workings of each robot to each other. Each ranger's robot could be said to be encapsulated; each one was a component of a greater whole called Megazord that would kick some serious butt.
The point in all this
In order to write easy-to-maintain applications, our frontend code should be broken down into small components that do just one small feature really well. Beyond that, we want to be able to use our code in a page without risking namespace collisions or CSS selector trampling. In order to build robust applications, we want our components to be encapsulated, and so far we haven't had very good ways of doing this with the DOM in the browser. Let's break down our options available for us as we begin 2013.
- <iframe src="http://hello-old-friend.com"></iframe>
Until recently, the only truly encapsulated elements that developers had at our disposal were iframes. There are a lot of problems with using iframes to make components: they don't expand to fit content, visual decorations of the contents can't overlap the frame - the list can go on. Also, iframes are slow.
The comment software written by Disqus uses iframes to display comments for a given document. If you sign in to Disqus in its iframe, the parent website cannot have access to any of your log information. Your handshake to leave a comment is between you and Disqus only, and has nothing to do with the parent web page that you're leaving a comment on. Go inspect a page with Disqus, and you'll see.
- Good for situations where you want more security for your users
- Implemented in all stable browser channels
- Can load content from a different domain than your application is hosted on
- Don't expand to fit content
- Hyperlinks can only navigate inside iframe, not parent context
- Visual decoration of elements within the iframe can't overlap iframe boundary
- IFrames are slow. One in a page, and you're fine, but any more than that spells trouble
IFrames can offer a degree of security and allow for loading off-domain content with ease, but beyond that, vanilla iframes are not going to be my first choice for making components.
- <iframe seamless src="http://parent.com"></iframe>
When I started exploring deeper into potential methods of DOM encapsulation, I became convinced that newer methods were going to trump iframes and that iframes would eventually wither away into eventual deprecation and obscurity.
Then the seamless attribute ended up on my radar and I've had to rethink my position. It seems like iframes may stick around as a means to an end in this problem domain.
This attribute changes how the iframe behaves in several major ways. CSS rules can cascade through the iframe, hyperlinks will automatically navigate the parent DOM context, and the iframe will automatically resize itself to fit the bounding box of content.
Sounds great, right? Let's ship it on production right away! Ah, except that this attribute is so new that no stable browser channel has implemented it. However, if you grab a copy of Chrome Canary, you can play with it. It might be a while before we see this attribute in a stable channel, but keep your eyes open for it.
- Great if you want to inline some functionality and want the features and styles of the parent scope to shuffle in
- HTML links function in parent context
- Resizable, inline nature makes for flexible styling
- Not implemented in any stable browser channel. You must have Chrome Canary in order to experiment.
- Getting behaviour just like it is possible but could take you some time to get right
While the reinvention of the iframe has been happening, a new challenger has appeared waiting at the gates: web components. The name sounds generic, but in this context it refers to a pair of W3C specifications to standardise browser behaviour for encapsulated components using Shadow DOM and Custom Elements.
The new Shadow DOM API gives developers the ability to insert a fully-fledged DOM inside of a host element. You should think of this basically like there is a tiny DOM hidden inside of an HTML element that you, the developer, decide to put it in. The rendering of a shadow DOM replaces the rendering of the host element's children. You can replace the rendering of the host entirely, but if it has children you can use a special HTML tag called <content> to insert them.
Grab a copy of Chrome Canary and try playing with the following below:
(Tip: Enable 'Show Shadow DOM' in the Web Inspector once you have Canary running!)
- <div>Just an ordinary markup, nothing to see here...</div>
- var innocentHost = document.querySelector('div');
- var deviousShadow = innocentHost.webkitCreateShadowRoot(); //When this line is executed and the shadow root is created, the innocentHost appears empty because it's rendering gets replaced with the rendering of deviousShadow
- deviousShadow.innerHTML = "<h1>Boo!</h1>"; //So we give deviousShadow some content to render. But what if we want the original text content to come back?
- deviousShadow.innerHTML = "<h1>Boo!</h1><content></content>"; //We use the <content> tag to tell the browser to insert the hosts's children
If you add a select attribute to the <content> tag you can even specify where specific host content appears using the classes or IDs of your host element's children.
Custom elements and HTML templates
Custom elements using the new <element> and <template> tags let us declare a component like so in its own HTML file:
- <element name="mobile-nav" extends="ul">
- <content select="some-host-child"></content>
Then, we can use the custom element by including an HTML template in our header and applying an attribute to the host element:
- <ul is="mobile-nav">
- <li>Navigation 1</li>
- <li>Navigation 2</li>
- <li>Contact Us</li>
- <li class="some-host-child">Home</li>
Whenever a <template> tag is rendered, a Shadow DOM is created and inserted so that the custom element's inner workings are encapsulated. The result is that the user will see the following rendered in place of the original host's children:
- Inserted by our template!
- Navigation 1
- Navigation 2
- Contact Us
Note that if you inspect this markup in the previous example with the Chrome Developer Tools in Chrome Canary, you will not see a shadow tree, I'm making a hypothetical example here for those who aren't reading this article in an edge release browser.
Whenever a <template> tag is rendered, a Shadow DOM is created and inserted so that the custom element's inner workings are encapsulated.
Shadow DOM + Custom Elements == Web Components, the dreamy browser API we need to make our goal of encapsulated components in the browser come true. Note that at the time of writing, the working groups responsible for agreeing on the implementation of this feature are currently debating whether custom elements will use the attribute method (detailed in this JSConf EU video), or whether develoeprs will create entirely custom tags something like this:
There are various merits and flaws with either implementation. For the gritty details and to stay up to date on the decision, follow this Bugzilla ticket. I tend to prefer the custom tags syntax and approach, for reasons I've detailed in the aforementioned Bugzilla bug.
If you want to go through web component examples like the ones in this section step-by-step, take a look at this video.
- Encapsulated components, just what we want!
- There's a polyfill so we can play with them, but we still need Chrome Canary too
- Easy to create for frontend developers
- Easy for designers to use declaratively with tags in HTML
- Not implemented in any stable browser channel. Experiments only work in Chrome Canary. Rumour is that Firefox will follow soon after collaborative agreement is met about its implementation
- Loading multiple components could cause overhead if you use lots of components as each one is an HTTP request (HTML templates can be concatenated together, though, to mitigate the issue)
X-Tags: the custom elements polylib
X-Tags is a component library and registry put together by some folks at Mozilla. It uses the custom HTML tag approach over the attribute method used as an example to introduce shadow DOM and web components.
The syntax to use a custom tag with this library can be as simple as:
- <x-panel src="/elements/demo/panel-content.html"></x-panel>
X-Tags boasts a nice registry of components available for you to use already. You'll need to familairise yourself with its very tiny API at x-tags.org to register your tag and handle events with it to get going.
This library will feel similar to other frontend UI component frameworks that you've worked with before, except this is not a framework at all: you're free to use the components in its registry in any way you would like without any complex hierarchy of dependencies; there are no extra features hiding that you don't need; there is no general-purpose library bloat because X-Tags is smart; it is itself a component because it just does one thing very well; and it lets you make custom components.
Eventually the library looks like it will integrate shadow DOM and web components as those specs finally solidify and make their way into major browser channels.
Eventual encapsulation? I hope so, because then this might find a spot comfortably in my developer toolchain.
- Will eventually use the web component and shadow DOM standard implementations when they are finally agreed upon
- Populated, organised eco-system of micro-modules available to use today
- Easy to create for frontend developers
- Easy for designers to use declaratively in HTML
- Heavy emphasis on cross-browser compatibility, especially mobile
- No true encapsulation yet. But it seems like the developers are implementing from the W3C specifications already and so we should expect shadow DOM to answer this problem in future
As web components and better encapsulation find their way into our workflow, I expect that we'll see the preference for client-side become the norm. While client-side templating is already a popular way to create apps full of features, offloading more of the work to the browser could mean that we have less code coming over the wire in order to accomplish our goals.
This pursuit is especially important considering the impact of downloading over unreliable and, sometimes, slower mobile connections on user experience. Nobody likes a slow web app.
I'm hoping that soon I'll have a component toolbox at my disposal made with custom tags. With the reusability and the simple declarative nature of custom elements, designers and UX professionals that are unfamiliar with programming but do know HTML will be able to rapidly prototype more sophisticated use cases by just using the custom tags that I've created.
I don't expect that each generic widget will solve every feature request right out of the box, but if we stick to our minimalist component philosophy then we can start with something simple and add only what we need.
This year, practice writing your code in a way that is more modular and follow the progress of these new browser features as they develop. You'll thank yourself later for being ahead of the curve.
- There's a video of my JSConf EU talk Inspector Web and the Mystery of the Shadow DOM to watch
- Shadow DOM 101 focuses on a key feature of shadow DOM: the ability to separate content from presentation
- The Web Components Explainer will give your more gritty details about web components with the least terse language compared with other W3C docs
- TJ Holowaychuck on what makes a good component that you should read (and subsequently launched a registry of components to be used with CommonJS)
- Here's that slide deck from developer Ben Vinegar on seamless iframes again in case you missed it