return to list of articles

A gentle intro to webpack 2

A short intro guide to webpack 2 that touches on loaders, modules, and plugins.

Webpack for newcomers

This is a little bit of an explanation about using webpack 2, for my forgetful future self.

Installation

Webpack requires npm. Check if you have it installed by running the following to check what version you have:

npm -v

To install the latest version of webpack first check what the latest available version is by running:

npm view webpack versions --json

The --json flag ensures that all the versions are visible. Choose the one you’re happy with an install it:

npm i -D webpack@2.2.1
npm i -g webpack@2.2.1

The -D flag in the first line means save the module as a development dependency in the package.json file in your working directory. Because it’s only a development dependency it won’t be packed into your output bundle. This is great because it won’t weigh down your filesize. The second line installs webpack globally.

Playing with webpack

From here you can run webpack from the CLI. The basic usage requires you to provide 2 arguments, the input file and the output file, something like:

webpack ./src/app.js ./dist/app.bundle.js

The modern JavaScript convention is that development files are kept in a src directory (source) and the bundled files are kept in a dist folder (distribution). The above command bundles the assets using development environment rules. If you want to bundle your files for a production environment (minified) then pass the production flag:

webpack ./src/app.js ./dist/app.bundle.js -p

If you’re lazy and want to have your assets bundled without re-running the command every time you can tell webpack to listen out for any changes by providing it the watch argument:

webpack ./src/app.js ./dist/app.bundle.js --watch

A better way

Obviously this approach won’t scale. A better convention is to create a configuration file and add all of your options there. You can do this by creating a file webpack.config.js in the root directory where you’ll be running your commands from. A barebones config file needs to have a module.exports object where you can tell webpack what your entry and output files are:

module.exports = {
  entry: './src/app.js',
  output: {
    filename: './dist/app.bundle.js'
  }
}

To make this work, open up your package.json file and edit the scripts object. You can remove the script for "test" and replace it with the following:

"scripts": {
  "dev": "webpack -d --watch",
  "prod": "webpack -p"
}

Now you can run webpack using either of the following commands for development and production, respectively:

npm run dev
npm run prod

Of course the configuration can be significantly more complex. There are a range of webpack concepts worth knowing, one of those being plugins.

Webpack plugins

Webpack plugins are modular chunks of code that you can inject into the webpack build process. As the documentation says, they’re the backbone of webpack and allow you to do a lot of intricate and useful things. For instance, there’s a plugin called html-webpack-plugin the lets developers create HTML files during the bundling process. It will automatically include all the necessary script tags so you won’t need to worry about including your JavaScript assets in the final bundle in your ./dist folder.

To use a plugin, first install it:

npm -i html-webpack-plugin -D

Again, the -D flag will automatically add it as a development dependency to your package.json file. Then require it at the top of your webpack.config.js file:

var HtmlWebpackPlugin = require('html-webpack-plugin');

Then to use this plugin, add it to the array value for the plugins key-value pair inside the root of the module.exports object. The nesting looks like:

module.exports = {
  entry: 'something.js',
  output: {
    path: 'dist',
    filename: 'something.js'
  },
  plugins: []
}

Inside the array you’ll want to instantiate a new HtmlWebpackPlugin object and pass it some arguments. The best advice is to always check the documentation on the github repository for any plugin. The npm page might have outdated documentation. Always keep an eye out for which version a certain documentation is intended for. In this instance the html-webpack-plugin github repo shows that you can instantiate it like so (at the time of writing):

plugins: [new HtmlWebpackPlugin()]

If you scroll down in the docs you’ll notice under the configuration header that you can pass some options to this plugin. This specific plugin will create ./dist/index.html for you automatically with a default layout. You can specify a template and some options like so:

var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'app.bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Webpack starter project',
      minify: {
        collapseWhitespace: true
      },
      hash: true,
      template: './src/index.ejs'
    })
  ]
}

This looks slightly scarier but those lines inside the plugins array are just arguments in the options hash for the plugin and you can find all of the latest documentation on the github repo. In this case, we’re providing a title to inject into the rendered html file’s <title> tag, we’re telling it to strip all the whitespace and minify the file, and we’re telling it to use a specific template saved a an embedded javascript file. That file looks something like:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <p>Content goes here, hello.</p>
  </body>
</html>

You’ll notice the rails-esque interpolation blocks <%= %>. This is a placeholder for a value that will be replaced during the bundling process. As you can see from the option, it’ll look for the title attribute in the options hash for the plugin, which we set above as ‘Webpack starter project’. The last option is hash which just append a hashed value to the assets in the bundled file, which is useful for cache busting. Our resultant ./dist/index.html will be an html file with all the whitespace stripped out.

Meet webpack modules

That’s useful. But we’ll also want to bundle our stylesheets as part of the process. Webpack uses modules via a concept known as loaders. As the docs tell us, loaders describe to webpack how to process non-JavaScript modules and include these dependencies into your bundles.

The most common loader for CSS for webpack is called css-loader. To use it you’ll first need to install it. It’s recommended to combine this installation with a module called style-loader.

npm i css-loader style-loader -D

Normally webpack appends all the styles inside of a stylesheet tag in the head of the body, so let’s skip all that by installing and including a module called extract-text-webpack-plugin which will extract those styles and add them to an external file. Also because I usually work with SASS rather than CSS I’ll include the sass loader:

npm i sass-loader node-sass -D

The above command also installs the node sass library which provides binding for Node.js to LibSass. It won’t work without a functional binding.

Alongside the plugins object in your config file you can add the following lines providing some rules for the modules to be used in the bundle process:

module: {
  rules: [
    {
      test: /\.scss$|\.css$/,
      use: ExtractTextPlugin.extract({
        use: ['css-loader', 'sass-loader'],
        fallback: 'style-loader',
        publicPath: 'dist'
      })
    }
  ]
},

The code above tests any files that end in .scss or .css using regex, and for any files that match this criteria webpack will use the specificied plugin. As you can see it uses the pluging’s extract method which has a few options. The plugin will use the 2 specified loaders with a fallback, and it’ll save the resulting files in the specified public path. Again, please check the github repos for the latest documentation.

If you get errors at any point then chances are that there’s been a breaking API change somewhere in the stack. There’s no need to panic, and there’s no need to start googling the stack trace, yet. Check out the documentation pages, comment out as much code as possible and narrow it down to the breaking point. Only once you’ve updated your own code to the code shown on the latest documentation pages should you go google-hunting on stack overflow.

To make the module work you’ll need to include the plugin in the plugins array. It should now look like:

plugins: [
  new HtmlWebpackPlugin({
    title: 'Webpack starter project',
    minify: {
      collapseWhitespace: true
    },
    hash: true,
    template: './src/index.ejs'
  }),
  new ExtractTextPlugin({
    filename: "app.css",
    disable: false,
    allChunks: true
  })
]

Don’t forget to include it at the top of your file:

const ExtractTextPlugin = require("extract-text-webpack-plugin");

That’s pretty much it for the intro. Your final configuration file should look like:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'app.bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.scss$|\.css$/,
        use: ExtractTextPlugin.extract({
          use: ['css-loader', 'sass-loader'],
          fallback: 'style-loader',
          publicPath: 'dist'
        })
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Webpack starter project',
      minify: {
        collapseWhitespace: true
      },
      hash: true,
      template: './src/index.ejs'
    }),
    new ExtractTextPlugin({
      filename: "app.css",
      disable: false,
      allChunks: true
    })
  ]
}

You can test your webpack configuration by running npm run dev and then opening up the html file in the dist folder in your browser. If you add some basic css and javascript code you can test whether they bundled properly. I suggest a simple CSS rule:

// ./src/app.scss
body { background-color: blue; }

And a simple JavaScript line:

// ./src/app.js
console.log('hello');

If your resulting file has a blue background and a message in your web inspector then you’re all set to keep exploring some more complex options.


Get notified when Pawel releases new posts, guides, or projects