Starting with Webpack 4 and VueJs — A quick-start tutorial.

One of them actually gave a hit!

Webpack4 is officially here! Did you know they’re calling it Legato! There are a lot of new features and improvements. Sean T. Larkin’s 😎 game has been upped 3 levels in Webpack 4. You can read about the changes in this article, or here in the git-changelog.

In a nutshell, Webpack4 is up to 98% faster. They have Mode, #0CJS, and sensible defaults now. 😻 It has chunking as a configurable default for different modes. Module Types are introduced along with .mjs support and a support for WebAssembly. Javascript of the future is here! 💥

I migrated one basic VueJs starter to Webpack4 and found it quite easy. I had few hiccups because few of the plugins are yet to update for Webpack 4. But once the necessary support is there, it’s going to take minutes to port the existing projects to the latest version. I started with a #0CJS (Zero-Config JS) where default modes are used, but once I integrated Vue, I added few basic configurations to set up a starter project that includes routing, testing and a configurable HTML file.


On to the code: I’ve used 0-Configuration modes for production/development builds and used webpack-dev-server for development purposes. I’ve added all the related articles I could find on Webpack4 in the end of this article, to make your migration process easier. Each subheading points to Git repo’s code, you can directly check it from there as well by clicking on the headings.

  1. Package.json: 📁
    My initial package.json is simple. A script for development, build and one for webpack-dev-server. There are scripts for testing too.
{
"name": "webpack-4-quickstart",
"version": "1.0.0",
"description": "> Webpack 4 tutorial: VueJs starter-kit, from 0 Conf to Production Mode",
"main": "index.js",
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --inline --hot",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"test": "node_modules/.bin/karma start",
"test-grep": "node_modules/.bin/karma start --grep"
},
"repository": {
"type": "git",
"url": "git+https://https://github.com/nnupoor/webpack4vue.git"
},
"keywords": [],
"author": "Neha Nupoor",
"license": "MIT",
"bugs": {
"url": "https://github.com/nnupoor/webpack4vue/issues"
},
"homepage": "https://github.com/nnupoor/webpack4vue#readme",
"devDependencies": {
"autoprefixer-loader": "^3.2.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"chai": "^4.1.1",
"cross-env": "^5.1.3",
"css-loader": "^0.23.1",
"extract-text-webpack-plugin": "^2.1.0", //not working currently.
"file-loader": "^0.8.4",
"html-webpack-plugin": "webpack-contrib/html-webpack-plugin", //is a patch right now.
"inject-loader": "^3.0.1",
"istanbul": "^0.4.5",
"karma": "^1.7.0",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.1",
"karma-mocha": "^1.3.0",
"karma-requirejs": "^1.1.0",
"karma-sinon": "^1.0.5",
"karma-webpack": "^2.0.4",
"karma-webpack-grep": "^1.0.1",
"mocha": "^3.5.0",
"node-sass": "^4.5.3",
"requirejs": "^2.3.5",
"sass-loader": "^6.0.5",
"sinon": "^3.2.1",
"style-loader": "^0.14.1",
"surge": "^0.18.0",
"svg-sprite-generator": "0.0.7",
"svg-sprite-loader": "^0.3.0",
"vue-loader": "^14.1.1",
"vue-template-compiler": "^2.5.13",
"vue-resource": "^1.2.1",
"vue-router": "^2.3.0",
"webpack": "^4.0.0",
"webpack-cli": "^2.0.8",
"webpack-dev-server": "^3.0.0",
"webpack-serve": "^0.1.4"
},
"dependencies": {
"vue": "^2.5.13"
}
}

2. webpack.config.js file : 💣
Once you have your package.json set, time to set up the webpack.config.js file. I have set up a basic file, but it can be separated or enhanced further. This one should be enough to get you started.

const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const NODE_ENV = process.env.NODE_ENV;
const setPath = function(folderName) {
return path.join(__dirname, folderName);
}
const isProd = function() {
return (process.env.NODE_ENV === 'production') ? true : false;
}
const buildingForLocal = () => {
return (NODE_ENV === 'development');
};
const setPublicPath = () => {
let env = NODE_ENV;
if (env === 'production') {
return 'https://your-host.com/production/';
} else if (env === 'staging') {
return 'https://your-host.com/staging/';
} else {
return '/';
}
};
const extractCSS = new ExtractTextPlugin({
filename: "css/styles.[hash].css",//"[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
});
const extractHTML = new HtmlWebpackPlugin({
title: 'History Search',
filename: 'index.html',
inject: true,
template: setPath('/src/tpl/tpl.ejs'),
environment: process.env.NODE_ENV,
isLocalBuild: buildingForLocal(),
imgPath: (!buildingForLocal()) ? 'assets' : 'src/assets'
});
const config = {
/**
* You can use these too for bigger projects. For now it is 0 conf mode for me!
*/
// entry: {
// build: path.join(setPath('src'), 'main.js'),
// vendor: path.join('setPath('src'), 'vendor.js')
// },
// output: {
// path: buildingForLocal() ? path.resolve(__dirname) : setPath('dist'), //this one sets the path to serve
// publicPath: setPublicPath(),
// filename: buildingForLocal() ? 'js/[name].js' : 'js/[name].[hash].js'
// },

optimization:{
runtimeChunk: false,
splitChunks: {
chunks: "all", //Taken from https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693
}
},
resolveLoader: {
modules: [setPath('node_modules')]
},
mode: buildingForLocal() ? 'development' : 'production',
devServer: {
historyApiFallback: true,
noInfo: false
},
plugins: [
extractHTML,
// extractCSS,
new webpack.DefinePlugin({
'process.env': {
isStaging: (NODE_ENV === 'development' || NODE_ENV === 'staging'),
NODE_ENV: '"'+NODE_ENV+'"'
}
})
],
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
js: 'babel-loader'
}
}
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [{
loader: "babel-loader",
options: { presets: ['es2015'] }
}]
},
{
test: /\.css$/,
use: extractCSS.extract({
fallback: "style-loader",
use: ["css-loader", "autoprefixer-loader"]
})
},
{
test: /\.scss$/,
use: !buildingForLocal() ?
extractCSS.extract({
fallback: "style-loader",
use: ['css-loader', 'autoprefixer-loader', 'sass-loader']
}) :
[{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}]
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
query: {
name: '[name].[ext]?[hash]',
useRelativePath: buildingForLocal()
}
}
]
},
};
module.exports = config;

2.1 HtmlWebpackPlugin setup : 🍫
Simplifying it, I’ve used HtmlWebpackPlugin to take an .ejs template, and populate it with generated bundles of js/css. HtmlWebpackPlugin is not working officially, but I’ve taken a patch provided by webpack for now.

const extractHTML = new HtmlWebpackPlugin({
title: 'History Search',
filename: 'index.html',
inject: true,
template: setPath('/src/tpl/tpl.ejs'),
environment: process.env.NODE_ENV,
isLocalBuild: buildingForLocal(),
imgPath: (!buildingForLocal()) ? 'assets' : 'src/assets'
})
// Inside config object
const config = {
.
.
plugins: [extractHTML],
}

2.2 Extracting the CSS using ExtractTextPlugin : 🙅🏻
Next we take ExtractTextPlugin and extract out the CSS so as to facilitate CSS-Splitting. Now this plugin is not working right now. (*Edit: They had released a beta version of the plugin I was not aware of. Sean T. Larkin pointed that out, and I’ve updated the code so it works.*)

const extractCSS = new ExtractTextPlugin({
filename: "css/styles.[hash].css",//"[name].[contenthash].css",
disable: process.env.NODE_ENV === "development"
});
// Inside config object
const config = {
.
.
plugins: [extractHTML],
}

2.3 Setting up the mode configuration : ✋🏻
We can also set the mode of the webpack config explicitly in the config object.

const config = {
.
.
mode: buildingForLocal() ? 'development' : 'production'
}

2.4 Setting up the optimization configuration : 🎯
You can also configure `optimization`object. Plugins like ‘NoEmitOnErrorsPlugin’, ‘ModuleConcatenationPlugin’, ‘NamedModulesPlugin’ are moved to optimization config, and have default values set based on the mode you select. ‘CommonsChunkPlugin’ has been removed, it’s config is inside optimization object now. It can be configured as below:

optimization:{
runtimeChunk: false,
splitChunks: {
chunks: "all", //Taken from https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693
}
}

One good thing about this migration was, I was able to figure out my mistakes just by reading the console errors. I did many mistakes while configuring above setting, and was easily able to set up in the correct way thanks to the informative errors I was getting. 🧀

2.5 Setting up devServer configuration for webpack-dev-server : 🎢
Now setting up the devServer. One thing I noticed was, if I set noInfo: true, then I was not getting any info about the port my server is running on, unlike previous versions. Need to probe further into it, to reach any conclusions. For now it’s false.

devServer: {
noInfo: false
}

2.6 Setting up Modules : ⚓️
 
Nothing fancy, just old Vue, JS, CSS, SCSS, SVG, and image-loaders.

module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
// postcss: [require('postcss-cssnext')()]
// options: {
// extractCSS: true
// }
loaders: {
js: 'babel-loader'
}
}
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [{
loader: "babel-loader",
options: { presets: ['es2015'] }
}]
},
// {
// test: /\.css$/,
// use: extractCSS.extract({
// fallback: "style-loader",
// use: ["css-loader", "autoprefixer-loader"]
// })
// },
{
test: /\.scss$/,
use: //!buildingForLocal() ?
// extractCSS.extract({
// fallback: "style-loader",
// use: ['css-loader', 'autoprefixer-loader', 'sass-loader']
// }) :
[{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}]
},
{
test: /\.svg$/,
loader: 'svg-sprite-loader'
},
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
query: {
name: '[name].[ext]?[hash]',
useRelativePath: buildingForLocal()
}
}
]
}

If you see I’ve commented out the extractCSS functions. They were not working. Scoped CSS in Vue for the win! 🥊 (*Edit: Works now. See above.*)

That’s it. This is the basic configuration/migration for webpack 4 that I needed to do, to run a simple VueJs project from scratch. I’ve added basic tests, routing, followed the directory structure of page, layout, and components.

Note: In the code above, I’ve removed few lines for the ease of explanation, the Git repo has all the necessary code for anyone to get started in VueJs and Webpack 4. I’m looking to learn and open to any feedback you all have to offer.


NOTE: This code has been updated on git for webpack v4.1.1 and modules like "mini-css-extract-plugin" have been added which are released recently. Take a look at the code, if not, most of the issues with plugins have been sorted by now. Happy coding! :)

Like what you read? Give neha nupoor a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.