Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

A Note on Vue & Webpack

5.00/5 (2 votes)
27 Dec 2017CPOL6 min read 11.5K   103  
This is a note on Vue & webpack.

Introduction

This is a note on Vue & webpack.

Background

Angular, React, and Vue. CommonJS, Typescript, ES5, ES6 (ES2015) and Babel. SystemJS and Webpack. So many terminologies to learn, so many terminologies to do the same thing and so opinionated. Among Angular, React and Vue, Vue is less known but has the best documentation and at least made its effort to be less opinionated and quickly gaining its popularity.

  • Angular - Angular is heavy weight. The "zone.js" approach of change detection made the default behavior global. It also made the performance optimization so complicated and cumbersome;
  • React - React is lighter weight and less opinionated. The "setState()" function relieves the heavy lifting of the change detection. But requires additional effort if the state change happens in multiple components;
  • VueVue is light weight and less opinionated. The "Object.defineProperties()" approach of change detection pinpoints exactly what have been changed. It combines the best from Angular and React. It is global and it is efficient.

This note is an example that implements two versions of a small Vue application using both ESS5 Javascript and webpack. Because Vue and Vue CLI have clear documentations, This example may not provide you much value. It merely serves the purpose to have a small evaluation on Vue. If you want a quick start and if you want a running example, you can download this example.

The Node Application

Image 1

The attached is a simple Node application. The only purpose of this application is to serve the static content in the "vue-example" directory that has two Vue SPA applications. The entry of the Node application is the "app.js" file.

JavaScript
var express = require('express'),
    http = require('http'),
    path = require('path');
    
var app = express();
    
app.set('port', 3000);
    
app.use(function (req, res, next) {
    res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
    res.header('Expires', '-1');
    res.header('Pragma', 'no-cache');
    next();
});
    
app.use(express.static(path.join(__dirname, 'vue-example')));
    
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

To run the application, you need to issue the following command in the root directory of the application to install the "node_modules".

npm install

You can then start the Node application by the following command.

node app.js

The ES5 Javascript Vue SPA

Image 2

In the "plain-version" directory, I created a simple Vue SPA by following the instructions from the Vue documentation. It is implemented in ES5 Javascript that can be loaded directly into the browser. For simplicity, all the CSS, Javascript, HTML and Vue templates are included in the "index.html" file.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>vue-example-plain</title>
    
<style type="text/css">
    * {
        box-sizing: border-box;
    }
    
    .container {
        width: 400px;
        height: 400px;
        border: 1px solid red;
        border-radius: 6px;
        display: flex;
          flex-wrap: wrap;
          justify-content: space-evenly;
        align-content: flex-start;
    }
    
    .item {
        width: 80px;
        height: 80px;
        border-radius: 50%;
        background-color: blue;
        margin-top: 12px;
        cursor: pointer;
    }
    
    .item.red {
        background-color: red;
    }
</style>
    
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
    
<script type="text/x-template" id="container-template">
    <div class="container">
        <item v-for="(item, index) in items"
            v-on:itemIsClicked="itemIsClicked"
            v-bind:index="index"
            v-bind:value="items[index]"></item>
        <div style="width: 200px; height: 0px;"></div>
    <div>
</script>
    
<script type="text/x-template" id="item-template">
    <div class="item" v-on:click="itemClicked"
        v-bind:class="{red: value == 1}"></div>
</script>
    
<script type="text/javascript">
    
let item = {
    template: '#item-template',
    props: ['index', 'value'],
    methods: {
        itemClicked: function() {
            this.$emit('itemIsClicked', this.index);
        }
    }
};
    
let container = {
    template: '#container-template',
    components: {
        'item': item
    },
    data: function() {
        return {
            items: new Array(16).fill(0)
        };
    },
    methods: {
        itemIsClicked: function(i) {
            let v = this.items[i];            
            this.$set(this.items, i, (v == 0)? 1:0);
        }
    }
};
    
window.onload = function() {
    let app = new Vue({
        el: '#app',
        components: { 'container': container }
    });
};
    
</script>
    
</head>
<body>
<div id="app"><container></container></div>
</body>
</html>

Based on the instructions from the Vue documentation, the Vue library is linked from the CDN "https://cdn.jsdelivr.net/npm/vue". This simple Vue SPA has two Vue components.

  • The "item" component is a child component that displays a circle;
  • The "container" component is the parent component. It uses the "v-for" binding to add 16 items in it. It also take the event from the children to toggle the color of the circles;
  • The SPA is initiated in the "window.onload" event to the "<div id="app">".

This SPA is simple, but it actually answered the most frequently asked questions to create a Vue application as the following.

  • How to initiate a Vue application;
  • How to use a child component;
  • How to pass the data to the child as props;
  • How to bind CSS classes to a component's data or props;
  • How to hook to a user event to a Vue component;
  • How to "$emit" a event from the child to the parent;
  • How to update an entry of the data if it is of the Array type so Vue is aware of it when the entry is changed.

With the Node application running, you can access the Vue SPA by the URL - "http://localhost:3000/plain-version/".

Image 3

If you click on the circles, you should see that the color toggles as expected.

The Webpack Vue SPA

With the help from the Vue documentation, creating a SPA by Vue in plain ES5 Javascript is a simple and pleasant experience. It is light-weight and runs fast. Because Vue is light-weight, a Vue application is also easy to maintain. But if you are opinionated, you may not like the fact that you can write ES5 Javascript applications that just run on a web browser without a translation. You may also feel that a ES5 Vue application is less modularized. To make it modularized, Vue actually supports webpack.

Image 4

In the "wpack-version" directory, I created an almostly exactly same SPA with Vue that uses webpack. As Vue documentation suggested, the best method to create a Vue application with webpack is the Vue CLI. In fact, this SPA is created by the Vue CLI. I just made some small modifications to it.

The NPM Dependencies

To use webpack for a Vue application, we need a set of NPM dependencies. Now let's take a look at the "package.json" file in the "wpack-version" directory.

{
  "name": "a-vue-example-wpack",
  "description": "a-vue-example-wpack-version",
  "version": "1.0.0",
  "author": "Song Li",
  "license": "MIT",
  "private": true,
  "scripts": {
      "build-dev": "cross-env NODE_ENV=dev webpack --color --watch",
    "build-prod": "cross-env NODE_ENV=production webpack --hide-modules"
  },
  "dependencies": {
    "vue": "^2.5.11"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.0",
    "babel-preset-stage-3": "^6.24.1",
    "cross-env": "^5.0.5",
    "css-loader": "^0.28.7",
    "file-loader": "^1.1.4",
    "vue-loader": "^13.0.5",
    "vue-template-compiler": "^2.4.4",
    "webpack": "^3.6.0",
    "webpack-dev-server": "^2.9.1",
    "url-loader": "0.6.2"
  }
}

The Vue Components

When writing a Vue application with webpack, we need to create each component is a "vue" file. The following is the "item.vue" file.

<template>
    <div class="item" v-on:click="itemClicked"
        v-bind:class="{red: value == 1}"></div>
</template>
    
<script>
export default {
    name: 'item',
    props: ['index', 'value'],
    methods: {
        itemClicked: function() {
            this.$emit('itemIsClicked', this.index);
        }
    }
};
</script>
    
<style scoped>
    .item {
        width: 80px;
        height: 80px;
        border-radius: 50%;
        background-color: blue;
        margin-top: 12px;
        cursor: pointer;
    }
    
    .item.red {
        background-color: red;
    }
</style>

The "item" component is then imported and used by the "container" component in the "container.vue" file.

<template>
    <div class="container">
        <img src="./assets/logo.png">
        <item v-for="(item, index) in items"
            v-on:itemIsClicked="itemIsClicked"
            v-bind:key="index"
            v-bind:index="index"
            v-bind:value="items[index]"></item>
        <div style="width: 200px; height: 0px;"></div>
    </div>
</template>
    
<script>
import item from './item.vue';

export default {
    name: 'container',
    components: {
        'item': item
    },
    data: function() {
        return {
            items: new Array(16).fill(0)
        };
    },
    methods: {
        itemIsClicked: function(i) {
            let v = this.items[i];            
            this.$set(this.items, i, (v == 0)? 1:0);
        }
    }
};
</script>
    
<style scoped>
    .container {
        width: 400px;
        height: 600px;
        border: 1px solid red;
        border-radius: 6px;
        display: flex;
          flex-wrap: wrap;
          justify-content: space-evenly;
        align-content: flex-start;
    }
    
    .container>img {
        width: 350px;
    }
</style>

The "container" component is imported and started in the "main.js" file.

import Vue from 'vue';
import container from './container.vue';
    
new Vue({ el: '#app', render: h => h(container) });

The "webpack.config.js"

The "webpack.config.js" file configures how webpack to pack the application. 

var path = require('path')
var webpack = require('webpack')
    
module.exports = {
  entry: './app/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/wpack-version/dist/', filename: 'build.js'
  },
  module: {
    rules: [
      { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ] },
      { test: /\.vue$/, loader: 'vue-loader', options: { loaders: {} } },
      { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } },
      {
          test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
          use: [{ loader: 'url-loader', options: { limit: 10240 } }]
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }, extensions: ['*', '.js', '.vue', '.json']
  }
};
    
if (process.env.NODE_ENV === 'production') {
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.optimize.UglifyJsPlugin({})])}

The ".babelrc" File

The small ".babelrc" file is a hidden file. But it needs to be in the "wpack-version" directory. Without it, you won't be able to make a successful production webpack build.

{
  "presets": [
    ["env", { "modules": false }],
    "stage-3"
  ]
}

The "index.html"

The "build.js" file built by webpack in the "/wpack-version/dist/" directory will be loaded into the "index.html" for the browser to render the application.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>vue-example-wpack</title>

<style type="text/css">
* {
    box-sizing: border-box;
}
</style>
</head>
<body>
<div id="app"></div>
    
<script src="/wpack-version/dist/build.js"></script>
</body>
</html

Build & Run

In order to build the SPA in the "wpack-version" directory, you need to issue the following command from the root directory of the Node application to install the "node_modules" in the "wpack-version" directory.

npm -prefix vue-example/wpack-version install

To make a development webpack build, you can issue the following command. It will build the SPA and also set a watcher. Whenever you make any changes to the related files, the watcher will trigger another build to save your effort to manually trigger a new build to reflect the changes made.

npm -prefix vue-example/wpack-version run build-dev

If you want to minimize your webpack bundle, you can use the following command to trigger a production build.

npm -prefix vue-example/wpack-version run build-prod

The production build should only be used for a production release. The minification process is a slow process. The time that it takes seems too long when you need to constantly make changes and re-build your SPA during the development process.

Image 5

With the Node application running, you can access the webpack version of the SPA by the URL - "http://localhost:3000/wpack-version/". If you inspect the Vue logo in the browser, you can see that webpack not only packed all the Vue components, it actually converted a image file into the Base64 format and added it into the bundle.

Image 6

Points of Interest

  • This is a note on Vue & webpack;
  • I hope you like my postings and I hope this note can help you one way or the other.

History

First Revision - 12/26/2017

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)