Vue.JS is my favorite JavaScript view-library now. In this article, I will explain to you how to build a project using Vue.JS, hot module replacement, webpack and the entire vue ecosystem completely from scratch so you can understand exactly how your app works.

Introduction

I have been developing in JavaScript for a long time now, from about 2007. I love angular, I love react and ember. I can see the motivations and the brilliance behind each of them. But the one library which truly sparks bliss and excitement in me is VueJS. It's not the best solution for every problem and every person - but me (and a lot of other people) really see the value in this library.

There's a general trend in JavaScript, especially in the react community to write libraries to do one thing well, and to allow the user to glue the pieces together in a way that fit best. While this is fantastic, and works for a ton of people, it can be a bit overwhelming for beginners. I was inspired by this article to write this tutorial. There are tons of react boilerplates out there - some are outdated, some have problems, and some simply don't work, which can be overwhelming to a beginner.

At the time of writing this article, the developer of Vue is working on an "official starter kit" including a scaffolding CLI to generate a new project. This is fantastic because it gives a clear and well-maintained starting point.

Still, there are some benefits to setting up your build system from scratch yourself:

  • Your project requirements may be slightly different from what the boilerplate author might build.
  • If something breaks or if you need something new, like depending on a new preprocessor or toolchain, you can bring it in without breaking the rest of the build.
  • The boilerplate might use things and libraries you simply don't need.

That said, boilerplates and scaffoldings - especially those published by the authors who really understand the libraries are an invaluable learning resource and you can pick up a lot of useful tips, and after you read the code you may choose to use their boilerplate as-is.

Do note that I am learning a few new things along the way myself, and this article might be updated often.

Before we begin

I've last verified these instructions working on Dec 28th 2015. I have tested this on Ubuntu Linux on latest stable version of Node.js (5.3).

Any commands to run on the command line will be listed like this

$ ls                           <- Run this in root directory of our project
$ (public/) ls                 <- Run this in the 'public' directory

Part 1: Creating basic structure

Let's create a project called "myapp" starting with an empty folder. This will be the root folder for all future instructions.

$ mkdir myapp
$ cd myapp/

Create a package.json in the root folder. So far we have no dependencies.

{
  "name": "myapp",
  "version": "0.0.1",
  "description": "My app",
  "dependencies": {
  },
  "devDependencies": {
  },
  "author": "You"
}

Create an index.html in the root folder. This will be the actual html served to the browser.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue Example</title>
  </head>
  <body>
    <h3>{{ message }}</h3>
    <script src="dist/build.js"></script>
  </body>
</html>

Notice two things about this html file:

  1. It uses dist/build.js as a JS file which doesn't exist yet.
  2. It has an {{ message }} snippet which will be filled in by vue later.

Create a folder src and add a file src/main.js:

import Vue from 'vue'

new Vue({
  el: 'body',
  data: {
    message: "Hello Vue"
  }
})

And we have the skeleton of a basic vuejs app, but it still needs to be built.

Part 2: Basic webpack build

Create a file called webpack.config.js in your root folder. Add this to your file:

module.exports = {
  // This is the "main" file which should include all other modules
  entry: './src/main.js',
  // Where should the compiled file go?
  output: {
    // To the `dist` folder
    path: './dist',
    // With the filename `build.js` so it's dist/build.js
    filename: 'build.js'
  },
  module: {
    // Special compilation rules
    loaders: [
      {
        // Ask webpack to check: If this file ends with .js, then apply some transforms
        test: /\.js$/,
        // Transform it with babel
        loader: 'babel',
        // don't transform node_modules folder (which don't need to be compiled)
        exclude: /node_modules/
      }
    ]
  }
}

Create a file .babelrc in your root folder. Babel is the tool used to convert futuristic ES6 into present day JavaScript. It needs to be configured with presets. Vue requires es2015 preset (which supports the next version of EcmaScript/JavaScript) and stage-0 (I have no idea what this does).

{
  "presets": ["es2015", "stage-0"],
  "plugins": ["transform-runtime"]
}

Install the webpack command line tool:

$ npm install -g webpack

Install local libraries required to build (as dev dependencies). To do this you can add this to the devDependencies part of your package.json.

    "babel-core": "^6.1.2",
    "babel-loader": "^6.1.0",
    "babel-plugin-transform-runtime": "^6.1.2",
    "babel-preset-es2015": "^6.1.2",
    "babel-preset-stage-0": "^6.1.2",
    "babel-runtime": "^5.8.0",
    "webpack": "^1.12.2",

Once you're done updating the package.json, run:

$ npm install

Note: I reccomend checking up on the official Vue starter kits for the best supported versions, because sometimes the latest versions of libraries might not be directly supported.

And last, but not the least, install the vuejs library to be actually required into your main code. You want to put this in dependencies.

$ npm install --save vue

Now you can build your app with WebPack. In your root directory, run:

$ webpack

You should see some output like:

Hash: 6568e32467dc12c8aeeb
Version: webpack 1.12.9
Time: 743ms
   Asset    Size  Chunks             Chunk Names
build.js  246 kB       0  [emitted]  main
    + 3 hidden modules

And open index.html in your browser. If you see "Hello Vue" in your browser, then everything is good so far. Congratulations, you have a basic working VueJS project.

Part 3: vue-loader and .vue files

Now we get to one of the most awesome parts of vuejs: Building components with .vue files.

Update your index.html to look like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue Example</title>
  </head>
  <body>
    <app></app>
    <script src="dist/build.js"></script>
  </body>
</html>

Note that now, instead of building a simple ViewModel on the root body node, we're mounting a component called app which will mounted to <app></app>

Change your main.js to this code:

import Vue from 'vue'
import App from './app.vue'

new Vue({
  el: 'body',
  components: { App }
})

Now, note that we're fetching a new component called App from a separate file called app.vue and including it into the instance mounted to the body element.

We will create a file called src/app.vue now. Add this to the app.vue code:

<template>
<div class="message">{{ msg }}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello from vue-loader!'
    }
  }
}
</script>

<style>
.message {
  color: blue;
}
</style>

There's a lot happening in this file: We're setting a style, defining the behavior in the script and defining the HTML template in template. To learn how components work, you can check the vuejs docs.

Now let's run webpack again and see what happens:

Hash: c71cc00f645706203ac4
Version: webpack 1.12.9
Time: 749ms
   Asset    Size  Chunks             Chunk Names
build.js  246 kB       0  [emitted]  main
   [3] ./src/app.vue 0 bytes [built] [failed]
    + 3 hidden modules

ERROR in ./src/app.vue
Module parse failed: /home/anirudh/work/misc/vue-scaffold/example/src/app.vue Line 1: Unexpected token <
You may need an appropriate loader to handle this file type.
| <template>
| <div class="message">{{ msg }}</div>
| </template>
 @ ./src/main.js 7:11-31

Webpack doesn't know how to handle the .vue files which is a new syntax. Luckily this is quite easy to fix. Open up your webpack config, and update it to this:

module.exports = {
  entry: './src/main.js',
  output: {
    path: './dist',
    publicPath: 'dist/',
    filename: 'build.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        exclude: /node_modules/
      },
      {
        test: /\.vue$/,
        loader: 'vue'
      }
    ]
  },
  vue: {
    loaders: {
      js: 'babel'
    }
  }
}

Now add the following items to your package.json in the devDependencies section:

    "css-loader": "^0.23.0",
    "style-loader": "^0.13.0",
    "vue-loader": "^7.3.0",
    "vue-html-loader": "^1.0.0",

Run npm install to fetch the new libraries

$ npm install

Finally, run webpack again, and you should see something like this:

Hash: 740a1d3c85161f03a0cf
Version: webpack 1.12.9
Time: 1355ms
   Asset    Size  Chunks             Chunk Names
build.js  258 kB       0  [emitted]  main
    + 11 hidden modules

If you open your browser, you should see the text "Hello from vue-loader" in blue text. This means that the style, HTML and javascript compiled properly.

Ok, so we made it this far. As a reward, here's a picture of Darcy:

Darcy

Part 4: Hot Module Replacement / Hot Reload

Hot Module Replacement or Hot Reload is the "hottest" new technology in the JavaScript world. This allows you to save your JavaScript file and have the component update in real-time.

Basically this is what happens:

  • You write an app.
  • You load this app in the browser. And play around with this app.
  • The app has a 'state' which is used by Vue to render the screen.

Let's say you want to make a quick change or fix a small bug. You have to reload the page, and do all the steps to bring it to the original state.

With hot reload, the process works differently:

  • You open the app, bring it to a given state.
  • You update the source code and save it.
  • Webpack detects the change. It re-compiles only the module that has been changed
  • Webpack sends the code and changes over-the-wire live using something like websockets to the browser.
  • Vue picks up the new model and hot-patches your code, and re-renders the app while keeping the state intact.

The first step here is that we need to use WebPack's dev server and cannot open it as a html file in the browser. First, update the devDependencies in your package.json with the following:

    "vue-hot-reload-api": "^1.2.0",

And run the following:

$ npm install
$ npm install -g webpack-dev-server
$ webpack-dev-server --inline --hot

When you run webpack-dev-server you'll see a pretty big output:

http://localhost:8080/
webpack result is served from /dist/
content is served from /home/anirudh/work/misc/vue-scaffold/example
Hash: ef5ed1df9062de968cb6
Version: webpack 1.12.9
Time: 1773ms
   Asset    Size  Chunks             Chunk Names
build.js  511 kB       0  [emitted]  main
chunk    {0} build.js (main) 465 kB [rendered]
    [0] multi main 52 bytes {0} [built]
    [2] (webpack)-dev-server/client?http://localhost:8080 2.48 kB {0} [built]
    [3] (webpack)/~/url/url.js 22.3 kB {0} [built]
    [... omitted for brevity ...]
   [85] ./~/vue-html-loader!./~/vue-loader/lib/selector.js?type=template&index=0!./src/app.vue 58 bytes {0} [built]
   [86] ./~/vue-hot-reload-api/index.js 5.62 kB {0} [built]
webpack: bundle is now VALID.

Open up http://localhost:8080 in your browser, and you should see the same output. But this doesn't really show off how awesome Vue's HMR is.

Instead, change your src/app.vue to this:

<template>
<div class="message">Value is: {{ count }}</div>
<a href="#" @click.prevent="increment">Increment</a>
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  },
  methods: {
    increment () {
      this.count ++;
    }
  }
}
</script>

<style>
</style>

Reload http://localhost:8080 and you should see a counter with a button to increment. If you click it, the counter goes up.

Now, make an update to the code. You can change the style, change the name of the button or even how the increment function behaves. If you save it, the component updates but the counter value stays the same. This is a big, big deal.

What's next?

This is by no means everything that you need for your project, but this is all the vue-specific info you might need. But before you start building the actual app here are things you spend some time googling and setting up:

Separate dev/production build

You will want a separate minified build for production. You can see how vuejs suggests it in the vue-loader-example webpack config

Note that moving your webpack.config into a separate folder than your root folder will require you to specify other command line configs.

Testing

Testing is beyond the scope of this tutorial, and your testing strategy will depend on your app itself. See the vue-loader-example test example for some simple code on how to test it.

Working with images, static files, and CSS

Your app will soon require custom CSS files and images - and webpack can help you with appropriate loaders like file-loader, url-loader and improve the development workflow.

Linting

You can configure eslint to be run directly from webpack using the eslint-loader. The vue-loader-example sets it up nicely, and even has a good .eslintrc which is guaranteed to work with eslint

Credits

Next Post