Consider VueJS for Your Next Web Project

Codeship NewsDevelopment

Reading Time: 12 minutes

UPDATE: With January 1st, 2017 we rebranded our hosted CI Platform for Docker from “Jet” to what is now known as “Codeship Pro”. Please be aware that the name “Jet” is only being used four our local development CLI tool. The Jet CLI is used to locally debug and test builds for Codeship Pro, as well as to assist with several important tasks like encrypting secure credentials.

Whether or not you’ve ever heard of VueJS before, never fear. I’m here to share some insights on how and why we at Codeship used this “new” library for our Docker-builds UI,.

What Is VueJS?

To get a rough idea of what Vue (pronounced “view”) does, think about it as the ‘V’ in an MV* pattern. If you’d like to compare it to something else, it’s probably closest to React but with a far lower barrier of entry. No need to learn something like JSX or pre-compile files just to give it a try. Drop it into your HTMLs header like any JS library, and you’re all set. Templating will probably look familiar to anyone who has worked with Angular or Mustache in the past.

So let’s talk about what made VueJS so attractive for us at Codeship.

Data-driven versus DOM-driven UIs

Separation of concerns is a common term in software engineering. Yet when it comes to UIs, we see code like this all too often:

function textReturningFunction () {
  return 'Hello World!'
}

// Set some text in jQuery
$('.someElement').text(textReturningFunction())

I wrote JS like this for a long time early in my career. But if you think about it, this code is pretty far removed from the idea of separation of concerns.

The JS has to know a lot about your markup and the general structure of the DOM. At any given time when the markup changes — .someElement gets renamed to .some_element, for example — your JS will break. The function that’s actually returning the data we want to show did not break though. It’s still returning Hello World! when called. In this instance, the data is the same, but the binding is broken. This happened because the JS is DOM driven.

Let’s look at a simple VueJS instance that should print out the same text as above.

The Markup:

<div class="app">
  <span class="someElement">
    {{ msg }}
  </span>
</div>

The JS:

new Vue({
  el: '.app',

  data: {
    msg: 'Hello World!'
  }
})

The Vue instance is a simple object. Running this code will do the trick, and you’ll find <span class="someElement">Hello World!</span> on the page. If the class on the span would change now, it will not break the JS because the class is not referenced.

To stay true from the start, we still referenced an element in our Vue instance. The .app element is the entry point from which Vue will handle things. If that one gets changed, the reference needs to be updated as well.

Reactivity

All variables in Vue are reactive. This means they are observable and can have watchers attached. Variables that change will automatically inform their peers of the change. On the VueJS side, there’s actually a very detailed description of this system that I highly recommend you read.

For quick understanding, compare Vue’s behavior to dirty checking in Angular. If something changes in Angular, it will start to search for that change. It’ll compare new values with old until no more changes are found, and it will update what’s necessary. This takes time and eats up resources while correlating with the complexity of the application.

A reactive data system like Vue uses may need more initiation time upfront, but that’s a good price to pay.

Components

Vue’s flexible but simple system is built around the concept of components. Components are small, reusable parts of the UI. Actually, this should be nothing new — the majority of JS frameworks these days are built around that concept. Web Components are a known solution for quite some time now. The problem is always the browser-wide implementation.

Vue takes care of this by providing a browser-independent component system. This system is straightforward to use and falls in line with the rest of Vue’s structure.

Sign up for a free Codeship Account

Why We Chose VueJS

Inside the Codeship application, we have one high-load and interaction-heavy page: It’s the build detail page of our Docker-based build infrastructure (Jet), letting the user read terminal output. The challenges of this page after the initial release included:

  • Fix the performance problems we were facing (frozen browser)
  • Allow efficient rendering of objects (10K+)
  • Make the code behind the system more accessible
  • Keep the overhead of introducing a new technology small

For the first implementation, we used Angular. The terminal output consisted of a large string coming from the server. On the client, that string got transformed and rendered onto the page in one process. When we tried to use objects for rendering, Angular wasn’t performing well enough. At a certain number of lines, those logs where so big that they could crash the browser. After some investigation, we were able to trace this back to the scope used by Angular. The sheer number of lines was not the issue, but rather that Angular tried to keep track of the whole DOM and all its changes.

We needed a new tool that didn’t care for the DOM in a way that allowed it to crash like that. Also, we couldn’t afford to greatly change how we handled assets, by introducing new build tools or attaching new infrastructure. Vue seemed like it would fulfill those needs.

A rough prototype with large HTML files (more than 1MB) dumped into the browser was promising. No impact on the performance of the page was noticeable. In the next iteration, we tried to render HTML based on plain JS objects. Ten thousand lines were no problem at all. After some further testing and a pros/cons list, it made sense to move forward with Vue. Let me show you what we did.

Starting Simply with VueJS

Our first iteration of rebuilding focused on preparing the base structure of the build detail page of Jet; our first goal was to render the sidebar of the page. This sidebar contains steps and services that are clickable elements following a given structure, in this case, an HTML structure.

<!-- Application Example -->
<div id="app">
<aside>
<ul class="services">
<li v-for="service in services">
{{ service.name }}
</li>
</ul>
</aside>
</div>

Notice how easy this small snippet of HTML is to read. Even if you’re not familiar with the exact syntax of Vue, the code makes sense. For every service object out of the array services, we plan to render a li element with the name of a service.

Let’s see what the Vue code would look like:

new Vue({
  el: '#app',

  data: {
    services: [
      {name: 'first service'},
      {name: 'second service'}
    ]
  }
})

When firing up the browser, the result is as you would expect:

<!-- Rendered HTML -->
<div id="app">
<aside>
<ul class="services">
<li>first service</li>
<li>second service</li>
</ul>
</aside>
</div>

The next step is leveraging components:

<!-- Application Example -->
<div id="app">
<aside>
<ul class="services">
<!-- Placeholder for the component -->
<jet-service v-for="service in services" v-bind:service="service"></jet-service>
</ul>
</aside>
</div>

The components in Vue fall into place quite easily:

var JetServiceComponent = Vue.extend({
  name: 'jet-service',

  props: ['service'], // made available by v-bind:service="service"

  template: `
    <li>
      <strong>{{ service.name}}</strong>
    </li>
  `
})

// Register that component in our Vue Object
new Vue({
  el: '#app',

  data: {
    services: [
      {name: 'first service'},
      {name: 'second service'}
    ]
  },

  components: {
    'jet-service': JetServiceComponent
  }
})

Running our updated code in the browser will eventually produce the following output:

<!-- Rendered HTML with components-->
<div id="app">
<aside>
<ul class="services">
<li><strong>first service</strong></li>
<li><strong>second service</strong></li>
</ul>
</aside>
</div>

What should be obvious so far is that using VueJS is very straight-forward. There’s no need for a lot of boilerplate code to get started. The only thing you need is a good understanding of JS objects and functions.

Adding Complexity with VueJS

The jet-steps component was a little more complex. Steps can be grouped and nested, and the structure is probably different for every project. Let’s proceed by aiming for the following structure:

-- some step
-- step group
  -- grouped step alpha
  -- grouped step beta

or as JSON

[
  { name: 'some step', type: 'step'},
  { name: 'step group', type: 'group_step', steps: [
    { name: 'grouped step alpha', type: 'step'},
    { name: 'grouped step beta', type: 'step'},
  ]}
]

The component should be able to self-invoke based on the structure, making it a little more complex. The Vue object uses specific key values for structuring. During the setup process, getters are made available that can then be used in a template. The computed key allows us to store functions on the Vue object that return computed values. Let’s use a function for checking whether a step is a group step:

var JetStepsComponent = Vue.extend({
  name: 'jet-step',

  props: ['step'], 

  computed: {
     isGroupStep: function () {
       return this.step.type === 'group_step'
     }
  },

  template: `
    <li>
      <span>{{ step.name }}</span>
      <ul v-if="isGroupStep">
        <jet-step v-for="step in step.steps" v-bind:step="step"></jet-step>
      </ul>
    </li>
  `
})

You may have already guessed how the Vue object would look. Vue is not getting more complex to allow nested components. There’s a line written by Evan You, the creator of Vue, that I want to quote here:

Thoughts on simple versus easy: Why not make it simple AND easy?

This is exactly what Vue tends to do, and it does it really well.

VueJS Performance

At this point, let’s step away from looking at the syntax of Vue and see where it outshines our previous implementation. Performance was the biggest issue as I mentioned earlier. The UI should show a large amount of terminal output to the user.

I ran some benchmarks on the performance at certain steps of the development process. The test generates 5,000 lines of a Base64 encoded random log line. A log object would look something like this:

{
  timestamp: 'some UTC timestamp',
  service: 'app',
  payload: 'A base64 decoded string'
}

The first intention is probably to go the route we did with Vue by rendering every object line in a loop. That’s pretty much how it should have worked, but Angular was unperformant doing this.

The problem

The implemented way was to prerender the HTML as a string on the server and pass it down to the client. The client would then use plain JS to attach that string to the DOM. That approach brought the fastest results.

But as soon we got close to around 8,000 lines, Angular regularly froze the browser tab, especially on slower clients. After investigating, it turned out that Angular’s need for keeping track of inner scope was killing it. Eight thousand lines of log output generated 32,000 DOM nodes. Angular could not process all of those as easily as we had hoped.

The first solution

The way we made it work to give Angular the necessary “breathing room” was a custom worker. That worker transformed the string into DOM nodes straight away but only injected 200 lines every 40 ms into the view. This worked fine, and Angular was then able to process up to 15,000 lines of log. However, at this level, the performance of the browser went down. Scrolling became slow.

Obviously, this still wasn’t good enough — we had clients that had even larger logs. Also we moved a lot of work to the server. Base64 decoding and pre-rendering was all done outside of the client side code.

To get a rough understanding of efficency in rendering, I tested the 5,000 log lines in the old UI and the first Vue implementation. The only difference here was that Vue missed the worker and the log was directly dumped into the View.

TechProcessesRenderingTotal
Angular2706.0006.270
Vue1202.0002.120
  • Times in ms
  • Processes are tasks done by the code outside of the log rendering

Just by switching the tool, we already cut the time more than in half.

The final solution

In the next iteration, I wanted to fully leverage client-side rendering of the log lines. The plain objects should get passed down, with the payload still Base64 encoded. The loop for rendering the log lines was now as simple as one might guess.

html
<div class="logLine" v-for="line in log">
<span> {{ line.timestamp }} </span>
<span> {{ line.service }} </span>
<span> {{ line.payload }} </span>
</div>

Before printing out the log, I had to decode it. For this, I created a simple JS class with functions. Vue did not force me to do things in any certain or complicated way. The code looks something like this minus the Ajax parts:

// The class is written in ES6
class LogHelper {

  getLog () {
    // ... function that gets the log from the server
    // eventually we have the raw log available for further use
    let rawLog = [...]

    return _prepareLog(rawLog)
  }

  _prepareLog (arr) {
    let decodedLog = arr.map( (line) => {
      line.payload = this._decode(line.payload)
      return Object.freeze(line)
    })

    return decodedLog
  }

  _decode (str) {
    // This correctly preserves UTF8 characters
    return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
      return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
    }).join(''))
  }

}

In the end, we now have a reference to the log in our Vue instance, so the UI has access to it. I ended up using Vuex, a state management inspired by Flux or Redux, but that’s another story. As soon as the log was available for the template, Vue took care of rendering it. See the new benchmark added to the table:

TechProcessesRenderingTotal
Angular2706.0006.270
Vue1202.0002.120
Vue (Objects)1.9001.7003.600

Those numbers were pretty interesting to me for various reasons. Let’s break it down real quick:

Angular versus Vue (Objects)

The significant detail here is that the total process time is still almost 50 percent faster. But besides cutting the time in half, we also freed up process time on the server. We now Base64 decode on the client and don’t need to pre-render an HTML string. This also makes the payload we need to initially load from the server smaller. Clean win!

Vue versus Vue (Objects)

One of the biggest questions is probably why the general process’ time is so much bigger. The reason for this is that Vue pre-renders all the objects in the Virtual DOM of the browser. In correlation, the actual rendering time is even 300ms faster than in the previous Vue implementation.

The Virtual DOM starts shining when it comes to the point of filtering log lines. Every object-connected DOM element is already cached and can be re-rendered instantly. That’s a cost I was willing to take.

Why Vue Could Work for You

There is no such thing as the perfect tool for every job, but there is a right tool for a particular job. Vue is a tool I found to be very helpful and efficient. It only tries to be very good at one thing and nails it: bringing data into the view of your web application is a breeze as we’ve seen in this article. What’s even more appealing about Vue is the fact that it can grow into something more when needed.

These days, Vue comes with a great ecosystem already.

  • Routing? Vue-Router is available.
  • Ajax Resources? Vue-resource takes care of this. Edit: As of now, axios is the recommended choice
  • Application State Management? Vuex is ready.
  • Webpack or Browserify? Vue has the tools already prepared.

Vue has a thriving community, a lot of stars on GitHub (Vue & Angular at 55,000+ (a.o. 6th. June 2017)) and a great maintainer I fully trust.

Please let me know what you think about Vue in the comments below, and I hope this article at least sparked your interest in considering Vue for your next project.

Posts you may also find interesting:

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • Pingback: “Laugh it up, fuzzball!” - Han Solo - Magnus Udbjørg()

  • Paulo Dias

    Sweet

  • Adrian Jackson

    I wonder how something like CycleJS (http://cycle.js.org/) and other Rx-based frameworks would compare. Is this something that you guys explored?

    • Stardrive Engineering

      CycleJS is just RxJS along with an opinionated view on how to handle side-effects in a Haskell-ian way. IMO there is to much fuss about what CycleJS calls drivers and in order to adhere to these self-invented standards one can get dogged down with the most basic of tasks (for instance, having a single Atom state be part of an app). Taken to the extreme to satisfy the driver ideal you would have to rewrite the API world as we know it.

      If your just looking to work with RxJS and build actual LARGER apps and care less for duplicate ways of supporting different streaming APIs then why not liberate yourself and use Vue with its new Snabbdom integration together with RxJS. Vue has it’s own reactivity solution and if you utilize it as an extension of RxJS it works like a charm.

      Just let the view be the view and the logic be the logic and use the best tool for each. Because RxJS and Vue are both mature products they integrate easily and simply, for the Win-Win.

      • RyanVice

        How is v-for letting the view be the view? You either have to add logic to markup or markup to logic. All things being equal I’d rather mix mark up in my JS and have all the power of JS then have to use some custom markup DSL (ala angular or view). I feel that this preference is the key driver at the moment in which approach to go with.

  • In relation to this “The reason for this is that Vue pre-renders all the objects in the Virtual DOM of the browser” I thought the virtual DOM implementation for Vue was coming in 2.0 [1] ? Are you guys using 2.0 ?

    1 – http://vuejs.org/guide/comparison.html#React

  • pke

    When I see the html templates I see all the problems that JSX solves perfectly. Those templates with their own domain language embedded in HTML are just un-debuggable. Same problem you had with NG you have again now with VUE.
    Thanks, I will not consider it over react for my next (web) projects.

    • Roman Kuba

      The HTML templates is just the ease of use. You can always put the templates into the components as well.
      As of Vue 2.0 you can use JSX inside Vue-Templates as well.
      Although it’s possible to have HTML templates, they are fundamentally different in the implementation from what Angular does.

      • RyanVice

        I think his point is that v-for and ng-repeat are less desirable than array.map() and I think it’s hard to argue against that point. Everything is about tradeoffs and that is a negative trade of the template DSL approaches to logic.

  • Elad Nava

    Excellent article, but I think Angular has 51k stars, not 13k:
    https://github.com/angular/angular.js

    • Roman Kuba

      Thank you.
      You are totally right. I have apparently taken the wrong angular repository. Will remove that line.

    • David Lee

      Your article still says the wrong number of stars for Angular, it’s a misleading comparison.

  • Pingback: Дайджест свежих материалов из мира фронтенда за последние две недели №220 (11 — 24 июля 2016) - itfm.pro()

  • Andrew

    Angular Light is faster than any of them.

  • Pingback: "Dead or alive, you're linking with me!" - Robocop - Magnus Udbjørg()

  • Ryan Brewer

    Great article, thanks for sharing your experience!

    For a large ecommerce site, my team and I chose Vue. Vue quickly became my library of choice, probably around my 3rd or 4th week (120-160 hours) with it. (React was my favorite until then.)

    For context, I have worked on three long-term, separate Knockout stacks over the course of 3+ yrs, and three smaller React stacks totaling ~400 hrs (10 wks) of tinkering. I have very little Angular experience, ~100 hrs during just one project–and of all the big MV* frameworks, it was my least favorite. (Angular 1.2 I think.)

    Why Vue? I think this article explained it better than I could, but I give it high marks for its simplicity, familiarity (when compared to Angular and React), and usage of ES6 to reduce boilerplate. And compared to React, I found the animation DSL much easier to work with.

  • TorturedMoon

    Thanks for sharing. I’ve been interested in Vue for while now so this was very useful.

  • RyanVice

    Slightly orthogonal to your point but I’m a bit confused as to why you’d have 15k rows in a dom when you can see maybe 20 max at a time in the browser.

    https://github.com/kamilkp/angular-vs-repeat

    Can solve this performance problem in Angular and we’ve used http://ui-grid.info/ with 10,000s of rows of data with no issues.

    I know that Vue.js is faster than Angular and I’m not trying to disagree with that point but pretty much everything is so that’s a low bar to measure against. I do, however, feel the article is a bit unfair to what you can accomplish with Angular.

    Full disclosure my current preference is ReactRedux and I’m not an Angular fan boy but we do use it on a lot of projects without major problems.

    • mbokil

      You have some good points there. Using a data grid and employing smart design, such as using one-way binding as a default can give you decent performance in Angular 1.x.

      • Roman Kuba

        @RyanVice:disqus thank yo for the comment and you are definitely right that there are ways to squeeze more performance out of it. At that point our technical architecture was challenging and it was important to get a performance gain out of a vanilla system.
        Of course we also used 1-way binding in Angular and even tried to leverage raw DOM manipulation on top but yeah.
        Vue ended up being so much cleaner on top of speed gains and very accessible throughout the team, what’s a big win for us as well.

  • Pingback: webMASTAH.weekly.021 - Jak pracować z domu i nie oszaleć? - webMASTAH()

  • orrd

    When the article talks about Angular, I think it’s referring to Angular 1, not Angular 2, right? Despite sharing the same name, 1 and 2 are very different things.

    • Roman Kuba

      Yes, it was targeting Angular v1

  • > Ajax Resources? Vue-resource takes care of this.

    vue-resource is retiring, and every ajax library could work with vue.

  • Pingback: Revision 283: vue.js und Chatbots | Working Draft()