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.

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+

},

"keywords": [],

"author": "Neha Nupoor",

"license": "MIT",

"bugs": {

"url": "

},

"homepage": "

"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"

}

} "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');

};

let env = NODE_ENV;

if (env === 'production') {

return '

} else if (env === 'staging') {

return '

} else {

return '/';

}

}; 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'

});

/**

* 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

}

},

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; 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:

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.