Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Add CSS Encapsulation to Your Favorite MV* Framework

DZone's Guide to

Add CSS Encapsulation to Your Favorite MV* Framework

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

CSS encapsulation is one of my favorite features coming to the Web platform. And though it is not supported by the majority of the browsers today, it is pretty easy to polyfil.

Example

Suppose we have a blog where we use some sort of comments component.

<p>One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin.</p>
<comments id="some-id"></comments>

Where the template of the comments component looks as follows:

<div ng-repeat="comment in comments">
  <span>{{comment.author}}</span>
  <p>{{comment.description}}</p>
  <button>Reply</button>
</div>

Since we are dealing with the browsers that do not support Shadow DOM and CSS Encapsulation, the DOM will end up looking like this

<p>One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin.</p>
<comments id="some-id">
  <div ng-repeat="comment in comments">
    <span>{{comment.author}}</span>
    <p>{{comment.description}}</p>
    <button>Reply</button>
  </div>
</comments>

I am using Angular-style syntax, but it not essential. Everything I will talk about can be applied to any framework that has a notion of a component.

Note that we use the p tag in both the comments component and the application, and we would like to style them differently.

/* application.css */
p {
    font-size: 12px;
}

/* comments.css */
p {
    font-size: 10px;
    background-color: LightCyan;
}

If we include the two style elements into the page, both the p tags will have the LightCyan background color.

Just Add Scoping

The easiest way to emulate CSS encapsulation is what everyone on the Web has been doing for years: scope the styles in comments.css. We just need to write a simple compiler that will compile

p {
    font-size: 10px;
    background-color: LightCyan;
}

into

comments p {
    font-size: 10px;
    background-color: LightCyan;
}

The compiler just adds the component name to every CSS selector. This solution works in simple cases, like the one above, but breaks apart when you start dealing with nested components.

Nested Components

Let’s complicate our example by introducing a new component called author.

<comments id="some-comments-id">
  <div ng-repeat="comment in comments">
    <author name="comment.author"></author>
    <p>{{comment.description}}</p>
    <button>Reply</button>
  </div>
</comments>

Where the template of the author component looks as follows:

<p>{{name}}</p>

The DOM:

<comments id="some-comments-id">
  <div ng-repeat="comment in comments">
    <author name="comment.author">
      <p>{{name}}</p>
    </author>
    <p>{{comment.description}}</p>
    <button>Reply</button>
  </div>
</comments>

Now we have a problem because the comments p selector matches the p in the author template. So adding scoping is not good enough — we need a different approach.

Tagging DOM Nodes

This technique is more complicated and requires us to compile not just the component’s CSS, but also its template.

First, let’s compile the template.

<comments id="some-comments-id">
  <div ng-repeat="comment in comments" comments="">
    <author comments="">{{comment.author}}</author>
    <p comments="">{{comment.description}}</p>
    <button comments="">Reply</button>
  </div>
</comments>

As you can see we have the added the comments attribute to all the elements in the template. If you use DOM-based templates, you can write a one-line function that will do this compilation by using querySelectAll("*").

Second, let’s compile the CSS.

p[comments] {
    font-size: 10px;
    background-color: LightCyan;
}

We’ve added an attribute suffix to the selector. Now, the background-color rule is applied only to the p tag in the comments template.

Custom Elements Are Not Required

The example uses custom elements, but you do not have to. Just use the same token when compiling the html and CSS. So if you have a Backbone app that semantically has components but uses regular divs to implement them, the compiled template can look like this

<div id="some-comments-id">
  <div ng-repeat="comment in comments" CommentsComponent="">
    <div CommentsComponent="">{{comment.author}}</div>
    <p CommentsComponent="">{{comment.description}}</p>
    <button CommentsComponent="">Reply</button>
  </div>
</div>

p[CommentsComponent] {
    font-size: 10px;
    background-color: LightCyan;
}

If you decide to precompile your templates and CSS, you can use a file name convention.

Use It in Your Favorite MV* Framework

Everything I’ve talked about is framework agnostic and can be used with any library as long as there component-like things that have templates and CSS. Wiring it up in Backbone or Angular should not be more than a few dozen lines of code.

Do I Have to Write a CSS Compiler?

The CSS compiler that adds an attribute suffix to all simple selectors can be quite complicated. How complicated? It mostly depends on what CSS features you want to support: Do you want to support media queries? Do you want to support pseudo selectors such :host and :host-content? It is, however, not as bad as it may seem. You can write an implementation that handles the majority of cases in a few hundred lines of JavaScript.

Having said that, you do not have to do it. Just use the compiler that comes with Platform.js. It does exactly what I was talking about in this article.

Read More

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:

Published at DZone with permission of Victor Savkin, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}