blog home

Simplify Static Asset Management With Vue.js Single-File Component

Static asset management is one of the more painful and mysterious challenges front-end developers face. We want our source code to be simple to develop and maintain, but we rely on browsers to be lean and mean. Browsers can’t be expected to understand our fancy source files with conveniences like Sass or the newest bleeding edge JavaScript features. We configure transpilers, linters, and compressors that watch for source code changes or get triggered by build processes. And finally, these tools emit static assets that a browser can interpret. 

http://etsy.me/3aBV66v + https://en.wikipedia.org/wiki/Crayon

Even when we don’t use fancy preprocessors, we still copy the source files to a dist directory to deploy them…because…well…it’s just what we do!

Image credit: Office Space, 1999

On top of this complexity, we have traditionally maintained a separation of concerns between markup, scripting, and styling. This separation can lead to extreme bloat that makes our applications difficult to maintain. 

Imagine a request to remove a component from a view. You remove the markup…but can you track down CSS selectors that are specific to this component? All the media queries? Do you feel safe removing code that may affect other parts of the view? What about a request to modify a component’s appearance? Do you add a new class to your updated component and leave the old styling behind, just in case there are other parts of the view that are styled by it? Technical debt accumulates. Eventually, you have a pile of dead code mixed in with the good stuff. It’s very difficult to clean up, and nobody wants to pay for the effort.

http://gunshowcomic.com/648

Not to imply that your code is inherently bad. It’s a consequence of the separation of concerns that has made the internet look sharp since the W3C adopted CSS in 1996.

You might also like this article: Triggering MacOS Notifications inside the Unix Pipeline with Tee, Grep, and Xargs

So, what are our alternatives? Inline CSS? An occasional inline style attribute is acceptable in the 21st century. But even with CSS-in-JS libraries, this solution can be challenging to scale. We need the cascade. We need media queries. 

Many modern frameworks combine JavaScript with markup; it’s the core of the React, Angular, and Vue.js revolution. The concept of “styled components” is also trending. But unification usually comes at a cost. The learning curve, awkward code structure, and dependency on 3rd party libraries may outweigh the advantages. However, Vue’s out-of-the-box support for the concept makes it simple to grasp and implement. 

The Vue.js framework Single-File Component (SFC) allows you to combine templating, scripting, and styling in a single source file that can accept props and manage state. Vue CLI – the “beginner” installation of Vue.js – will pre-configure the bridge between your .vue source files and webpack that requires absolutely no configuration or webpack knowledge. Let’s open a terminal and quickly build a working example using npm.

  1. Install Vue CLI: npm install -g @vue/cli
  2. Create a new project (accept the default settings): vue create vue-sfc-example
  3. Start the project: npm run serve
  4. Render HelloWorld.vue in a browser: http://localhost:8080/
image credit: Staples

SO easy. Let’s open the source to see what we built. 

<template>
  <div class="hello">
    ...some markup...
  </div>
 </template>

 <script>
 export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
 }
 </script>

 <!-- Add "scoped" attribute to limit CSS to this component only -->
 <style scoped>
 h3 {
  margin: 40px 0 0;
 }
 ul {
  list-style-type: none;
  padding: 0;
 }
 li {
  display: inline-block;
  margin: 0 10px;
 }
 a {
  color: #42b983;
 }
 </style>

The <template> tag wraps Vue template syntax enhanced markup.

The <script> tag wraps JavaScript.

The <style> tag wraps CSS.

Aside from placing the styling at the end of the file, this source file looks an awful lot like an html file that a browser could interpret. There is a lot going on under the hood, but Vue doesn’t clutter the source with tooling bloat. 

Let’s concentrate on the <style> tag.  The boilerplate contains some basic CSS, and an attribute named “scoped”. As the code comment implies, this attribute allows you to “scope” this block to only apply to this particular component, by automatically namespacing the CSS. Compare this to a more traditional approach, which might involve creating a selector like:  “#hello-world-component-namespace.this-component {...}” in some faraway css file. The scoped attribute is optional. If you want to modify child components, one approach is to exclude the scoped attribute. You may also use multiple blocks of CSS, in case you wish to scope part of the code, but style children with a separate CSS block.

<style scoped>
h3 {
 margin: 40px 0 0;
}
...
</style>
<style>
#child-component > h3 {
 margin: 10px;
}
...
</style>

If you inspect the source code in your browser, you can see this style block rendered in the head of the document:

<style type="text/css">
h3[data-v-469af010] {
  margin: 40px 0 0;
}
ul[data-v-469af010] {
  list-style-type: none;
  padding: 0;
}
li[data-v-469af010] {
  display: inline-block;
  margin: 0 10px;
}
a[data-v-469af010] {
  color: #42b983;
}
</style>

There is no need to version or deploy a CSS file, in this example. The data attribute in the first block is no accident. It uniquely identifies the component this styling is scoped to.

<div data-v-469af010="" class="hello">...</div>

Predictably, namespacing is suppressed for code blocks that are not scoped.

<style type="text/css">
#child-component > h3 {
  margin: 10px;
}
</style>

An alternative to this approach is the ::v-deep combinator, which allows you to style children from a scoped block. Details can be found here.

But what about my Sass? Good news: SFCs tap into all of your favorite webpack preprocessors. Install sass-loader with npm:

npm install -D sass-loader sass

Then add an scss attribute, and you’re all set:

<style scoped lang=”scss”>
h3 {
 margin: 40px 0 0;
}
ul {
 list-style-type: none;
 padding: 0;
   > li {
     display: inline-block;
     margin: 0 10px;
     a {
       color: #42b983;
       &amp;.very-green {
         #00ff00;
       }
     }
   }
}
</style>

But what about my Sass globals, includes, mixins, etc.? Never fear – the Sass block you include in your SFCs works just like your typical Sass source file. You can pass includes, set variables, nest media queries, and any other Sass convenience.

<style scoped lang=”scss”>
@import "mixins";
@import "global/global";

#main {
 padding-top: em(54);

 @media (min-width: $screen-md) {
   padding-top: 0;
 }
}
</style>

The vue-loader, which is included and pre-configured by Vue CLI, makes all of this work. Sass/Scss, Less, Babel, TypeScript, and other popular preprocessors and linters are supported. These features can be discretely configured, to the delight of advanced users. 

The Vue.js SFC offers the convenience our source code deserves, without the file management and webpack tooling headaches. You can also use the component state to set class and style inside your templates, using built-in lifecycle hooks. It is also important to note that you can still include CSS the typical way, or in a mixed mode. This is especially handy when using rapid prototyping libraries like Bootstrap. 

What’s the catch? Vue.js is a relatively new player. It’s picking up steam, but there aren’t as many applications using the framework as the competing products – Angular and React. That means the user community is comparably small. Examples are slim and basic. You are cutting your own trail. Also, we have detected some “spookiness” in the way preprocessors react to code structure. The preprocessors may need some configuration and babysitting, once you scale into a larger project.

Bottom line 

SFCs are great for small-to-medium sized projects. Larger projects? Your mileage may vary.

We use cookies to deliver the best possible experience on our website. To learn more, visit our privacy policy. By continuing to use this site, you consent to our use of cookies.

Accept