4 Important Changes In Vue.js 2.4.0

4 Important Changes In Vue.js 2.4.0

Vue.js 2.4.0 has been released this week with an abundance of new features, fixes and optimisations.

In this article, I’ll give you a breakdown of four new features that I think are the most interesting:

  1. Server-side rendering async components
  2. Inheriting attributes in wrapper components
  3. Async component support For Webpack 3
  4. Preserving HTML comments in components

1. Server-Side Rendering Async Components

Before Vue 2.4.0, async components were not able to be server rendered; they were just ignored in the SSR output and left to the client to generate. This gave async components a significant downside, and fixing the issue allows for much better PWAs with Vue.

Async Components

Async components are really handy. If you’ve been following this blog I’ve been writing about them a lot lately. In a nutshell, they allow you to code-split your app so non-essential components (modals, tabs, below-the-fold content, other pages etc) can load after the initial page load, thus allowing a user to see the main page content quicker.

Let’s say you decided to load below-the-fold content asynchronously. Your main component might look like this:

<template>
  <div id="app">
    <!--Above-the-fold-->
    <sync-component></sync-component>

    <!--Below-the-fold-->
    <async-component></async-component>
  </div>
</template>
<script>

import SyncComponent from './SyncComponent.vue';
const AsyncComponent = import('./AsyncComponent.vue');

export default {
  components: {
    SyncComponent,
    AsyncComponent
  }
}
</script>

By using Webpack’s dynamic import function, AsyncComponent would be loaded by AJAX from the server after the page loads. The downside is that while it’s loading the user will likely only see a spinner or blank space.

This can be improved with server-side rendering, since the async component markup would be rendered on the initial page load, which is going to be a lot better for UX than a spinner or blank space.

But until Vue 2.4.0, this wasn’t possible. The SSR output of this main component would just look like this:

<div id="app" server-rendered="true">
    <!--Above-the-fold-->
    <div>
      Whatever sync-component renders as...
    </div>

    <!--Below-the-fold-->
    <!---->
  </div>

As of Vue 2.4.0, async components will be included in the SSR output so you can code split your Vue apps until your heart is content, without the UX debt.

2. Inheriting Attributes in Wrapper Components

One annoying thing about props is that they can only be passed from parent to child. This means if you have deeply nested components you wanted to pass data to, you have to bind the data as props to each of the intermediary components as well:

<parent-component :passdown="passdown">
  <child-component :passdown="passdown">
    <grand-child-component :passdown="passdown">
      Finally, here's where we use {{ passdown }}!

That’s not so bad for one or two props, but in a real project you may have many, many more to pass down.

You can get around this problem using an event bus or Vuex, but Vue 2.4.0 offers another solution. Actually, it’s part of two separate but related new features: firstly, a flag for components called inheritAttrs, and secondly, an instance property $attrs. Let’s go through an example to see how they work.

Example

Say we bind two attributes on a component. This component needs attribute propa for its own purposes, but it doesn’t need propb; it’s just going to pass that down to another nested component.

<my-component :propa="propa" :propb="propb"></my-component>

In Vue < 2.4.0, any bound attribute not registered as a prop would simply be rendered as a normal HTML attribute. So if your component definition looks like this:

<template>
  <div>{{ propa }}</div>
</template>
<script>
export default {
  props: [ 'propa' ]
}
</script>

It’ll render like this:

<div propb="propb">propa</div>

Note how propb was just rendered as a normal HTML attribute. If you want this component to pass propb down, you’ll have to register it as a prop, even if the component has no direct need for it:

export default {
  props: [ 
    'propa',
    'propb' // Only registering this to pass it down :( 
  ]
}

This obscures the intended functionality of the component and makes it hard to keep components DRY. In Vue 2.4.0, we can now add the flag inheritAttrs: false to the component definition and the component will not render b as a normal HTML attribute:

<div>propa</div>

Passing down propb

propb doesn’t disappear, though, it’s still available to the component in the instance property $attrs (which has also been added in Vue 2.4.0). This instance property contains any bound attributes not registered as props:

<template>
  <div>
  {{ propa }}
  <grand-child v-bind:propb="$attrs.propb"></grand-child>
  </div>
</template>
<script>
  export default {
    props: [ 'propa' ],
    inheritAttrs: false
  }
</script>

Imagine you need to pass hundreds of props from a parent down through several layers of nested components. This feature would allow each intermediary component template to be declared much more concisely in the parent-scope:

<input v-bind="$attrs">

Oh, and this also works exactly the same when passing data up by binding listeners with v-on:

<div>
  <input v-bind="$attrs" v-on="$listeners">
</div>

3. Async Component Support For Webpack 3

Scope hoisting is one of the key features of the recently released Webpack 3. Without going into too much detail, in Webpack 1 and 2, bundled modules would be wrapped in individual function closures. These wrapper functions are slow to execute in the browser compared to this new scope hoisting method, which is made possible by the new ES2015 module syntax.

Two weeks ago, vue-loader v13.0.0 was released, and it introduced a change where .vue files would be outputted as ES modules, allowing them to take advantage of the new scope hoisting performance advantages.

Unfortunately, ES modules export differently, so the neat async component syntax you can use for code splitting in a Vue project e.g.:

const Foo = () => import('./Foo.vue');

Would have to be changed to this:

const Foo = () => import('./Foo.vue').then(m => m.default);

Vue 2.4.0, however, automatically resolves ES modules’ default exports when dealing with async components, allowing the previous, more terse syntax.

4. Preserving HTML Comments In Components

Okay, this feature is not too significant, but I still think it’s cool. In Vue < 2.4.0, comments were stripped out of components when they were rendered:

<template>
  <div>Hello <!--I'm a comment.--></div>
</template>

Renders as:

<div>Hello</div>

The problem is that sometimes comments are needed in the rendered page. Some libraries might have a need for this, for example, using comments as a placeholder.

In Vue 2.4.0, you can use the comments flag to indicate you want to preserve comments:

<template>
  <div>Hello <!--I'm a comment.--></div>
</template>
<script>
export default {
  comments: true
}
</script>
Anthony Gore's Picture

About Anthony Gore

Anthony is a web developer and online course instructor with a passion for Javascript. He currently resides in Chiang Mai, Thailand.

Comments