A tutorial on using Webpack 4 with standard conventions.

It is very important to understand the default behavior of webpack using a minimal configuration, or without any configuration at all.

Without understanding the default behavior, a developer cannot really understand any configuration file. Default conventions are a double-edged sword.

To assist webpack users, this tutorial will demonstrate a no-configuration project, and then incrementally scale a configuration to a more complex but very common scenario.

The only prerequisites are node and npm. To see if they are installed, type the following commands.

> node -v
v10.8.0
> npm -v
6.4.1

It is beyond the scope of this tutorial to describe installing and using Node and NPM.

Our goal it to create an HTML page that includes a single bundled script. The script will be the product of transpiling, minifying, and combining several Node modules, including our app along with the dependent third party libraries. Our Javascript will use features not available by default in web browsers, such as the es2016 module import/export statement.

Our application will be a simple Vue application that synchronizes the contents of two text fields.

Create an NPM project inside a folder.

mkdir WebpackTutorial
cd WebpackTutorial
npm init -y

The -y argument will use defaults without prompting to create an npm package.json file. The name of the folder will become the name of the project.

Install the dependencies needed to pack and transpile.

npm i -D webpack @babel/core @babel/cli @babel/preset-env

We use the -D flag to save the modules in package.json as a development-time dependency.

Our tutorial application uses Vue at compile time and runtime, so we will install that, too.

npm i -S vue

We use the -S flag to save the modules in package.json as a runtime dependency.

Our HTML file will include our webpack bundled script. The default output for webpack is dist/main.js, so we will include the output script at the end of our body tag, and place our index.html file inside the dist directory.

Contents of dist/index.html

<!DOCTYPE html>  
<html lang="en">  
<head>  
 <meta charset="UTF-8">  
 <title>Webpack Tutorial</title>  
</head>  
<body>  
<h1>Webpack Tutorial</h1>  
<div id="app"></div>  
<script src="main.js"></script>  
</body>  
</html>

If you try to view the HTML file, you will get an error in the console because we have not created our main.js file yet.

Failed to load resource: the server responded with a status of 404 (Not Found)

We are going to use es2016 syntax and features not supported in browsers, then transpile them to a format that most browsers support.

The default entry script name is src/index.js. We do not have to configure webpack with the entry parameter if we use the convention.

src/index.js

import Vue from 'vue/dist/vue.esm.js'
new Vue({  
  el: '#app',  
  template: `
    <div>  
     <input v-model="text"/> 
     <input v-model="text"/>
    </div>  
 `,  
  data: {  
    text: 'webpack tutorial'  
  }  
})

We are using a version of Vue that can compile on the fly at the expense of size and speed. We will optimize this later to precompile. You do not need to understand Vue here; Basically we two-way bind the text fields to a single text member; Vue takes care of the rest.

Build your bundle with webpack

webpack -d

Note how there is only one javascript file created: dist/main.js; It contains a combination of our code along with the Vue source code we imported as well.

Open your HTML file in a web browser. The two text fields should synchronize values; Changing one should change the other.

We use the -d to build using development mode. Development mode will turn off optimizations and enable debugging features.

We are now going to add a titlecase function inside a new Javascript file, also known as a module.

Create src/titlecase.js

export default function titlecase(str) {  
  return str.replace(  
    /\w*/g,  
  function (txt) {  
      return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();  
  }  
  );  
}

This file is a simple module that exports a single function for converting text to title-case.

Import the titlecase module in your index.js file, and add a watch function to your Vue component.

import Vue from 'vue/dist/vue.esm.js'  
import titlecase from './titlecase'  

new Vue({  
  el: '#app',  
  template: `<div>  
 <input v-model="text"/> <input v-model="text"/></div>  
 `,  
  data: {  
    text: 'Webpack Tutorial'  
  },  
  watch: {  
    text(value) {  
      this.text = titlecase(value)  
    } 
  }  
})

You do not need to understand Vue here; Basically we watch for changes to the text member, and set it to the title-case value when it changes.

Pack using webpack -d, and then view index.html in your browser to see the update. Note how there is still only a single JavaScript file that contains ALL of our code.

Build your bundle with Webpack's production mode.

webpack -p

By default, production builds are minimized and tree-shaked; The default build mode is production, so we do not need to specify the mode. We still use -p however, because we will get a warning if relying on the default mode without defining it in a configuration file.

This will remove unused code (tree shake or dead-code-elimination) and minify the output. The resulting code will be smaller size, compile faster, and run faster. On my machine, the file size of main.js was reduced from 813kb to 90.7lkb . That is a huge difference: Almost 90%!

For the remainder of this tutorial, you can use either webpack -p to optimize, or webpack -d to make debugging easier.

We have reached the point where the default configuration can no longer satisfy our needs. The following examples all require a configuration file. If we put the file in the root of our project, and call it webpack.config.js, we will not have to tell webpack where to find it.

webpack.config.js

module.exports = {
}

The configuration is simply a node module that exports an object.

Webpack will check the configurations validity when running and report any syntax errors.

It is very useful to remove our script from index.htmland have webpack add it automatically. Let’s generate an index.html file in the dist directory, using our original index.html file as a template. The plugin will add our bundled JavaScript script elements for us.

This is very helpful when including multiple entry points and/or code splitting into multiple bundles where each bundle name may include a hash, or when having dynamic code that is loaded on-demand. Renaming the output bundle filename will also be handled by the HtmlWebpackPlugin.

First, Move your dist/index.html file to the root directory. DO NOT IGNORE THIS STEP, as the plugin will overwrite dist/index.html.

Install the HtmlWebpackPlugin plugin

npm i -D html-webpack-plugin

Update webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');  
module.exports = {  
  plugins:[  
    new HtmlWebpackPlugin({
        template:'index.html',
        inject:true
    })  
  ]  
}

The template parameter instructs the plugin to use index.html instead of generating one on the fly, and the injectparameter instructs the plugin to add the webpack output script elements to the end of the body tag.

There are many advantages to splitting your code into more than 1 bundle. By separating frequently changed code from unchanging library code, the client only needs to download the changed bundles.

Add this to your webpack configuration:

optimization: {  
  splitChunks: {  
    chunks: "all",  
  },  
}

Now when you webpack, all code from outside the src directory is put into vendors~main.js, and your source is put into main.js.

Once we build our project, our chunk sizes are:

Development Mode

Chunk Size
main.js 9.25kb
vendors~main.js 806kb

Production Mode

Chunk Size
main.js 1.76kb
vendors~main.js 89.5kb

This means when you change your sources, only a 1.76kb file will need to be reloaded by the client.

If you installed and configured the HtmlWebpackPlugin as suggested, both of these files will be injected into your dist/index.html file, making maintenance and further code splitting a breeze.

An explanation of our configuration is available at https://webpack.js.org/guides/code-splitting/. Basically, we are telling webpack to split and refactor duplicate code as well.

We can also split code into modules that download only when needed.
This speeds up initial load times and lowers bandwidth.
Perfect candidates for lazy loading include dynamically loaded dropdowns, click-to-reveal content, multi-page applications, and security situations.

As an example, we will alter our project to only load the code for our lowercase function when the user types.

index.js

new Vue({  
  el: '#app',  
  template: `<div>  
 <input v-model="text"/> <input v-model="text"/></div>  
 `,  
  data: {  
    text: 'Webpack Tutorial'  
  },  
  watch: {  
    async text(value) {  
      const titlecase = await import(/* webpackChunkName: "titlecase.lazy" */ './titlecase')  
      this.text = titlecase.default(value)  
    }  
  }  
})

As you can see, instead of importing the module at the top, we call the function import() instead.
It returns a promise that resolves with the modules exported value. We use async/await to handle the asynchronous return. If you open your console, the bundle will be downloaded the first time the text field's value is changed.

The comment /* webpackChunkName: "titlecase.lazy" */ is optional and provides a hint as to the bundle's name. If omitted, a numeric name is used for the bundle (1.js, etc).

If we precompile our Vue templates, our code will be small and faster. We can also use an optimized module of the Vue module.

Use the optimized (non compiler) version of Vue by changing import Vue from 'vue/dist/vue.esm.js to import Vue from 'vue in src/index.js.

Convert the component definition from src/index.js to a single file component.

Once your start using Vue Single File Components, you will never go back to string templates.

src/app.vue

<template>
  <div>
    <input v-model="text"/>
    <input v-model="text"/>
  </div>
</template>

<script>
  import titlecase from "./titlecase";

  export default {
    data: function () {
      return {
        text: 'Webpack Tutorial'
      }
    },
    watch: {
      text(value) {
        this.text = titlecase(value)
      }
    }
  }
</script> 

We now import the app module (our compiled Vue template) and use render instead of template in our component definition.

src/index.js

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

new Vue({  
  el: '#app',  
  render: h => h(app)  
})

We cannot compile yet, because babel does not know how to deal with .vue files.

Install the Vue Loader and Vue Template Compiler

npm i -D vue-loader vue-template-compiler

Update the webpack configuration to use the vue-loader

const HtmlWebpackPlugin = require('html-webpack-plugin');  
const VueLoaderPlugin = require('vue-loader/lib/plugin')  

```js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: 'index.html',
      inject: true,
    }),
    new VueLoaderPlugin()
  ],
  optimization: {
    splitChunks: {
      chunks: "all",
    }
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
    ]
  }
}

We import the loader, add an instance to the plugins, and add a rule to use vue-loader on all files that end with .vue.

Once we build our project, our chunk sizes are: Chunk Size
main.js 2.23kb
vendors~main.js 65.2kb

Our vendors bundle dropped in size from 89k to 65k.

For more information on the vue-loader-plugin, see https://vue-loader.vuejs.org/guide/#manual-configuration.

Until now, you have been viewing your static HTML page in a browser. Wouldn't it be nice if your changes were immediately updated in the browser without needing to reload?

Launch webpack-dev-server

webpack-dev-server -d

Open http://localhost:8080 in your browser.

In src/app.vue, change the string 'Webpack Tutorial' to 'My Webpack Tutorial' and save.

Your page should update immediately.

Lets make our text fields yellow. Add this to the end of src/app.vue

<style>  
  input {  
    background: yellow;  
  }  
</style>

Now we configure babel to use the css-loader and the vue-style-loader

Install the css-loader into NPM

npm i -D css-loader

Add a .css rule to webpack.config.js

  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.css$/,
        loader: "vue-style-loader!css-loader"
      }
    ]
  }

This will use the css-loader for css files, and then feed the output into the vue-style-loader.

To make things consistent, add build and serve commands to NPMs package.json package.json

"scripts": {
  "build": "webpack -p",
  "serve": "webpack-dev-server -d"
},

And now run them.

npm run serve
npm run build

Using the default conventions makes your configuration file lean, and also makes your project easier to understand by others, and by you when you revisiting your own code. Even if following conventions, I suggest you always have a configuration file, even if it is empty. Even better, add the common HtmlWebpackPlugin and optimization options but disable them until you need them.

https://webpack.js.org/api/cli/ https://www.valentinog.com/blog/webpack-tutorial/

This is the convention when we do not supply a configuration to webpack.

webpack.config.js

module.exports = {
    mode: 'production'
    entry: 'src/index.js'
    output:{
       filename: 'dist/main.js`
    }
}

Running webpack without arguments is the same as this:

webpack --config webpack.config.js

todo: Post a sample folder and demo.