What is Webpack and How does it work

Loading table of contents...

Webpack

webpack

This is another build tool (task runner) like grunt or gulp but mostly a module bundler like browserify and also can be used with a transpiler like babel (to be able to use ES6 javascript feautures from the future!) or typescript (which is cool).

Questions

Why do we need build tools?

First of all we can't use development files as in production, and we need to optimize them to speed up our applications. But by the development phase we also need the non-optimized versions, like seperate javascript files, and JS, CSS extensions file Sass, Less, typescript etc.

So we always need to process these assets before deploying applications and for these constant tedious work we need the build tools.

What can be done with build tools?

To name a few we can do: Minimize our JS and CSS (by removing whitespace and shorten variable names), eliminate unused code, compile extension scripts and new features like ES6 to classic JS and CSS, bundle contents, integrate linting tools.

You can check examples or plugins of grunt and gulp to see all possible use cases.

What is the distinctive feature of Webpack from build tools?

The main purpose of using Webpack is to resolve dependencies and create bundles of assets. On the other hand there are lots of loaders and plugins that can do a build tool's job.

What is a dependency?

Before ES6, there wasn't any module system for Javascript (in the browser). We need to explicitly load javascript files in the right order in our applications to work.

Let's say we have an hotel reservation system, and we have 3 files of customer.js, order.js which depends on customer, and book.js which depends on both of them. We need to place first the customer, than order and lastly the book <script> tags to run our application.

That could seem easy in the first but when your application grows and you've got 50 files, how can you manage the dependencies manually easy and efficient?

It should be easy because I don't want to rearrange my script tags when I changed some of the dependencies, or it should be efficient because I don't need to all of the dependencies if it isn't used on that page.

In the server, with node.js, often you can define dependencies using require function. But in the browser you have 4 options:

  • Use AMD (Asynchronous Module Definition) with a library like require.js or system.js
  • Use import statement in ES6 but you need a transpiler like babel, or traceur.
  • Use module bundler like browserify in CommonJS modules (Node.js uses CommonJS) with require
  • Or use module bundler like webpack to integrate any of methods above.

What is the distinctive feature of Webpack from Browserify?

With browserify you can only use CommonJS, and for only Javascript files. Webpack on the other hand can handle all major module systems and can compile/bundle not only javascript files but also CSS files, images and fonts.

And with extensive loaders and plugins of webpack, you can add different tasks to your build workflow or extend how webpack works.

Why should I need a transpiler?

If you heard of ES6 or ES2015, javascript is constantly evolving and new features added to the language. But we need to wait until Node.js supports them. In the client we can never use them because of the old browsers like IE9! or we need to wait for modern browsers to implement that specific feauture.

But there is a workaround! We can use transpilers to transfer+compile our code from new shiny ES6/ES7 javascript to more supported ES5 version. The most used tools for this are babel and traceur.

Why should I need a linter, or what's IS a linter anyway?

Javascript is a great language with good features but there are many best practices should be applied also many features and usages to be avoided. In addition code should be readable and clean. We can check these standarts with tools like JSLint, JSHint or ESLint.

You can read my article about Javascript Linting with ESLint for more info.

Demo

Let's download libraries and tools we needed first:

1sudo npm install webpack -g
2sudo npm install webpack-dev-server -g

You need node.js installed on your OS. Just google it if not.

sudo is for UNIX/Linux systems (you'll only need when installing globally).

With this command we can use webpack as an executable on the terminal/command line, so we needed to add -g global parameter. If we want to use it as a library, we also need to download to our application by without using the parameter.

From now on we're going to use webpack and we need a project to play with in.

1git clone https://github.com/dhalsim/webpack-tutorial.git
2cd webpack-tutorial
3git checkout one

Type this to see webpack.config.js file content:

1cat webpack.config.js                                                
 1var path = require('path');
 2
 3module.exports = {
 4  context: path.resolve('js'),
 5  entry: "./app.js",
 6  output: {
 7    path: path.resolve(__dirname, "build"),
 8    filename: "bundle.js"
 9  }
10};
1cat public/index.html
 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <meta charset="utf-8">
 5    <title>Webpack tutorial</title>
 6  </head>
 7  <body>
 8    <script src="/build/bundle.js" charset="utf-8"></script>
 9  </body>
10</html>

cat is a unix command that prints a file's content to the terminal. You can open files in your favourite editor if you want.

This is so simple, we're only creating a bundle script from the entry point app.js file. app.js file uses booking.js which uses customer.js and order.js. So bundle should contain recursively all of them.

See webpack entry documentation for more options about entry.

Install dependencies and start the application:

1npm install
2nmp start

Webpack-dev-server

Note: webpack-dev-server is mostly for client-side development and not a production server, as the name suggests. You'll most likely want to use express or similar for your server specific codes. I highly recommend you to use different npm scripts for development and production seperation like this one.

These commands will download and start the application with webpack-dev-server using npm scripts (see package.json). Open http://localhost:8080/public in a browser and take a look at the development console then the bundle.js in source view which created with webpack. You can see webpack specific codes for dependencies and requires to work.

To configure webpack-dev-server, we used devServer node in the configuration file.

Hot Module Replacement (HMR):

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.

webpack-dev-server uses this technique to load changes to the browser. This is helpful if you are developing, so that you don't need to restart web server and your client application on the browser. For more info visit the documentation. To use HMR and all other goodies we need to proceed even further.

Demo Part 2

Now we have a basic understanding of how webpack works, so type following commands to get little dirty:

1git checkout two
2cat webpack.config.js  
 1var path = require('path');
 2var webpack = require('webpack');
 3
 4module.exports = {
 5  context: path.resolve('js'),
 6  entry: "./app.js",
 7  output: {
 8    publicPath: '/',
 9    path: path.resolve(__dirname, "build"),
10    filename: "bundle.js"
11  },
12  devtool: 'source-map',
13  devServer: {
14    contentBase: './public',
15    hot: true,
16    inline: true
17  },
18  module: {
19    loaders: [
20      {
21        loader: 'babel-loader',
22        test: /\.js$/,
23        include: [path.resolve(__dirname, 'js')]
24      }
25    ]
26  },
27  plugins: [new webpack.HotModuleReplacementPlugin()]
28};
1cat public/index.html
 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <meta charset="utf-8">
 5    <title>Webpack tutorial</title>
 6  </head>
 7  <body>
 8    <script src="bundle.js" charset="utf-8"></script>
 9  </body>
10</html>


Check out the project is still working:

1npm install
2npm start

This time open http://localhost:8080 on your browser.

Now lets look what have been changed with the configuration:

  • We can test without /public thanks to devServer.contentBase: './public' setting.
  • We changed script src="build/bundle.js" to script src="bundle.js" in the html file thanks to output.publicPath setting.
  • We enabled source-maps with devtool: 'source-map' so we can debug our application modules in the browser even it is bundled.
  • We enabled HMR with devServer.hot and its plugin.
  • We added babel-loader so we can now use new fancy ES6 syntax. (see booking.js file)

Webpack configuration

We can either configure webpack with command line parameters or from config file. I rather use the config file option.

Main options of config are:

  • context: relative path to entry
  • entry: entry point options, could be array for multiple entry points
  • output: output bundle options, use [name] for multiple entry points
    • path: relative path to your output files, build.js will be created in this folder
    • publicPath: use this path for URL's on your <script> or <link> tags. We use build.js instead of /build/build.js.
  • devtool: for source-maps
  • devServer: for webpack-dev-server configuration
  • modules: for using loaders
    • preLoaders: runs before loaders (could be used for linter integration)
    • loaders: add different tasks for your files
    • postLoaders: runs after loaders (maybe for cleanup?)
  • plugins: There are lots of plugins that you can extend webpack.

Webpack-dev-server

This will serve your files when developing, and rebundles in every change. Actually it is smarter than that: if you change a file it though that would be on memory so not written to the output folder. Start with from our script npm start.

Webpack loaders

Webpack uses loaders for task specific purposes. They can be added through config or require statement. But adding them through require adds a dependency to webpack in your source code. So config is a better alternative. Also check out the documentation for loader orders.

Note: you can pipe multiple loaders, just careful piping order is from right to left.

  • raw-loader

You can read any file content and save it to a variable. Install it via npm i raw-loader -D and simply use it with var content require('raw!./file.xml') or through configuration.

CSS Loaders

  • style-loader

Load your css files right into the DOM using javascript! You can use with other loaders like css-loader, or less-loader.

Demo Part 3

1git checkout three
2cat webpack.config.js
1...
2module: {
3    loaders: [
4      {
5        loader: 'style-loader!css-loader',
6        test: /\.css$/,
7        include: [path.resolve(__dirname, 'css')]
8      },
9...
1cat app.js
1import '../css/styles.css';
2...
1npm start

Now if you check out http://localhost:8080 you can see our css is injected with a style tag and background-color is changed.

Load Images, fonts with Loaders

You can load your images by using file-loader and requiring them:

 1...
 2module: {
 3    loaders: [
 4        {
 5            test: /.(png|jpe?g)$/,
 6            exclude: /node_modules/,
 7            loader: 'file-loader'
 8        }
 9    ]
10}
11...

Alternatively you can use ......

Additional Loaders

You can use ES6 syntax and features and transpile back to ES5 with babel.

1module: {
2    loaders: [
3        {
4            test: /\.js$/,
5            exclude: /node_modules/,
6            loader: 'babel-loader'          
7        }
8    ]
9}

You can also use ts-loader to transpile your files from TypeScript to regular JavaScript.

  • strip-loader

If you want to remove some of your code on the production environment, like logging to console, alerts etc. you can use strip-loader to remove all of them.

Webpack plugins

Other than the usual stuff like dependency resolving and creating packages, webpack can do a variaty of things, and it supports 3rd party plugins, too (which you can install via npm)

Categories of plugins:

  • environment
  • compiler
  • entry
  • output
  • source
  • optimize

List of plugins

Some useful plugins that I found

  • NoErrorsPlugin: When there is a failure in your scripts, webpack doesn't emit the broken scripts, so there will be less reloads while developing in the middle (but you can see errors in the console)
  • CommonsChunkPlugin: Create a seperate JavaScript file used commonly on your code or combine all vendor libraries to a one seperate file
  • ProvidePlugin: Can be used like a dependency injection library, for example inject to _ either lodash or underscore, inject or polyfill native Promise with something like bluebird etc.
  • DefinePlugin: Create global varibles for example environment variables that will be removed if redundant on minification
  • I18nPlugin: Use this to create language specific bundles.

Any third party plugins?

Just search npm with webpack-plugin!

Solution to Configurations With Resolve.Alias

From my previous node.js articles, you may seen some dynamic configuration loading as you can run different configurations from environments (like NODE_ENV is development or production). To achieve this ability we may also use webpack, particularly its resolve.alias option. If I have a webpack configuration of:

1resolve: {
2    alias: {
3        config: path.join(__dirname, 'config', process.env.NODE_ENV)
4    }
5}

and when I require with require('config') it will load either ./config/development.js or ./config/production.js based on NODE_ENV.

Sourcemaps

If you minimize your files, for debugging purposes, you would like to have sourcemaps, too. Sourcemaps are easy for webpack and it's built-in.

There are lots of options available.


blog comments powered by Disqus