Vue.js and Webpack 4 From Scratch, Part 2

Daniel Cook
ITNEXT
Published in
5 min readMar 18, 2018

--

Click here to share this article on LinkedIn »

This time with style!

Continuing from Part 1, in this article I will continue to build our Vue.js application by adding:

  • Stylus for adding pre-processed CSS
  • Hot Module Reloading and HTML injection
  • Babel for building our scripts

Part 3 will complete the application by adding:

  • Static assets processing
  • ESLint for linting
  • Testing using Jest

Let’s start by getting webpack-dev-server setup, by doing this we won’t need to keep running our build script to see the changes in the browser.

We’ll need to install the npm module:

npm install --save-dev webpack-dev-server

And change our build script to run this instead of webpack, at the same time I’m going to rename the command from build to dev:

"dev": "webpack-dev-server --config build/webpack.config.dev.js"

Running this now and the browser starts up on http://localhost:8080 and I can see my app. All with zero config! However, there’s a slight problem, if I change my App.vue to say “Good Evening San Dimas!” instead of “Hello World!” then the browser does not update.

If I check my console I can see the application is building so what is wrong? Well when we setup our index.html we hardcoded the path to our javascript, for hot module reloading to work we need to allow this path to be injected, that way the dev server can update the html to include the changes as and when they are made.

Go to the index.html and remove the script tag that points to dist/main.js, then install the html-webpack-plugin:

npm install --save-dev html-webpack-plugin

Now we need to add this to our webpack config file, so it ends up looking like this:

If you now run the application up you can change the message in App.vue and it is immediately reloaded in the browser window!

This is awesome, however we haven’t yet got true HMR, if you look in the browser devtools you can see that the whole page is refreshed when we make a change. To fix this we need to add one more piece of configuration:

'use strict'const webpack = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',

entry: [
'./src/app.js'
],
devServer: {
hot: true,
watchOptions: {
poll: true
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
}

With this in place you should be able to see the changes hot loaded into the page without refreshing, something like this:

Now we have our app auto-updating we should be able to move much quicker so let’s add some CSS. Change the App.vue file so it looks like this:

<template>
<div class="full-width center-content">
<h1>Hello World!</h1>
</div>
</template>
<style scoped>
.full-width {
width: 100%;
}
.center-content {
display: flex;
justify-content: center;
align-items: center;
}
</style>

This change used to get applied automatically in the browser however now you will see an error message:

There is a similar issue if the CSS is in a file of its own. Create a folder called assets in the root, add a file called app.css and copy the full-width and center-content classes into it. To include it in our app, we can import it in out app.js….

import '../assets/app.css'

…and remove the styles from App.vue.

Now, when you try to run your app there is an error:

To fix this we need to configure the css loader in our webpack config, add this to rules:

{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}

And install the style-loaders:

npm install --save-dev css-loader vue-style-loader

The css should then load and we have css processing in both our external assets and our single-file components.

One final change here, I like using stylus for CSS pre-processing so I’ve installed the loader:

npm install --save-dev stylus stylus-loader

Added a rule:

{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
}

And convert my app.css to app.styl:

.full-width
width 100%
.center-content
display flex
justify-content center
align-items center

At this point if you’re using VSCode there’s a stylus extension which will colour-code your styles correctly.

So far we have managed to get by without adding any script sections to our components, as this is such a simple application we’re not going to do anything too ambitious, let’s just create a HelloComponent that takes name as a prop and issues the appropriate greeting.

Under the components folder, add HelloComponent.vue, it should look like this:

<template>
<h1>Hello {{ name }}!</h1>
</template>
<script>export default {
props: {
name: {
type: String,
required: true
}
}
}
</script>
<style lang="stylus" scoped>
h1
color red
</style>

Then change App.vue to use the new component:

<template>
<div class="full-width center-content">
<hello-component name="World" />
</div>
</template>
<script>
import HelloComponent from './components/HelloComponent.vue'
export default {
components: {
HelloComponent
}
}
</script>

This works, however older browsers may struggle with this ES6 syntax (and this is something we will come up against when we start testing) so we are going to install Babel to transpile our code to ES5 which can be understood by all browsers.

First, install the npm modules:

npm install --save-dev @babel/core babel-loader @babel/preset-env

Then add the loader config:

{
test: /\.js$/,
use: 'babel-loader'
}

It is important at this stage that this loader config comes after vue-loader in your config, vue-loader will split out your single-file components into separate modules for your html, js and styles, we want babel-loader to process the js part of this.

Finally in the root of the project create a file called .babelrc, this is where we will put babel-specific configuration. At the moment we are going to keep this as simple as possible:

{
"presets": [
["@babel/env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}]
]
}

We can now run our application up and we see no surface changes however under the hood our ES6 code has been transpiled to ES5 and will work on a much wider range of browsers.

That’s all for this part, in the final part we’ll finish up the application by looking at external asset processing, linting and testing. Thanks for reading!

--

--