Webpack

Written by Craig Godden-Payne

On March 11, 2018

Reading this will take about 13 minutes.


More posts like this

What is Webpack?

According to the Webpack documentation:

Webpack is a module bundler. It takes modules with dependencies and generates static assets representing those modules. It focuses on Code Splitting meaning that static assets should fit seamlessly together through modularization.

What it tries to achieve is:

  • Split the dependency tree into chunks loaded on demand
  • Keep initial loading time low
  • Every static asset should be able to be a module
  • Ability to integrate 3rd-party libraries as modules
  • Ability to customize nearly every part of the module bundler

Webpack has two types of dependencies in its dependency tree: sync and async. Async dependencies act as split points and form a new chunk. After the chunk tree is optimized, a file is emitted for each chunk. Webpack can only process JavaScript natively, but loaders are used to transform other resources into JavaScript. By doing so, every resource forms a module.

Webpack has a clever parser that can process nearly every 3rd party library. It even allows expressions in dependencies such as: require(“./templates/” + name + “.jade”). It handles the most common module styles: CommonJs and AMD.

Webpack also features a rich plugin system. Most internal features are based on this plugin system. This allows you to customize webpack for your needs and distribute common plugins as open source.

What is Lodash?

According to the lodash documentation:

Lodash makes JavaScript easier by taking the hassle out of working with arrays, numbers, objects, strings, etc. Lodash’s modular methods are great for:

  • Iterating arrays, objects, & strings
  • Manipulating & testing values
  • Creating composite functions

Lets try an example

Create a directory, initialize npm, and install webpack locally.

npm init -y
npm i --save-dev webpack webpack-cli lodash

Create a file named src/index.js

function myComponent() {
  var element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  return element;
}
document.body.appendChild(myComponent());

Create another file name index.html

<!doctype html>
<html>
  <head>
    <title>Getting Started</title>
    <script src="https://unpkg.com/lodash@4.16.6"></script>
  </head>
  <body>
    Index.js file needs to only be run, once lodash has been downloaded and run. This is why the index.js script is at the bottom of the page, and its dependency is in the head section.
  </body>
</html>

What you notice straight away is there are implicit dependencies between the two scripts. Lodash needs to be included in the page before index.js runs. If lodash was referenced after index.js, you would get a horrible error, such as _ does not exist.

This is typically how dependencies were managed in the past, and there are many problems with this such as:

  • There is nothing to say that index.js depends on Lodash
  • If the dependency is missing not loaded first, index.js will not function.
  • If a different dependency is included but not used, the code is still downloaded, but never used.

Using webpack will fix all of the above problems.

Change the directory structure

If we have a source directory and output directory, we can using a build step to apply webpack.

webpack-tutorial
|-package.json
|-index.html
|-src
  |-index.js
|-output

We can then change our index.js script to import the lodash module using ecmascript styntax

import lodash from 'lodash';
function myComponent() {
    var element = document.createElement('div');
    element.innerHTML = lodash.join(['Hello', 'webpack'], ' ');
    return element;
  }
  document.body.appendChild(myComponent());

When looking at the code, we can now see that index.js explicitly requires lodash to be present. We bind it as lodash, which means we can change the named binding we use, and it also means we no longer need to have _ in the global scope.

Webpack can now look at the import declaration, to build a dependency graph and generate an optimized bundle where scripts will be executed in the correct order.

In order to use the bundled code though, we need to change index.html to use the output file for our index.js (and module dependencies).

  <!doctype html>
  <html>
   <head>
     <title>Getting Started</title>
   </head>
   <body>
     Rather than specifying the seperate dependencies, we just need to specify the bundled output file.
     <script src="output/bundle.js"></script>
   </body>
  </html>

This wont work though until we setup webpack and run it.

Start by creating a file named webpack.config.js

var path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'output')
  }
};

The run the following command to run webpack

npx webpack --config webpack.config.js

What is npx?

Npx executes a command either from a local node_modules/.bin, or from a central cache, installing any packages needed in order for the command to run. Npx will also check whether command exists in $PATH, or in the local project binaries, and execute that. If the command is not found, it will be installed prior to execution.

Continuing with the example

The output should now be

webpack-tutorial
|-package.json
|-index.html
|-src
  |-index.js
|-output
  |-bundle.js

If you run the index.html file, you should see Hello Webpack. And if you look at the source of bundle.js, you should see the code from index.js and also the code from lodash. (you will have to prettify the code first tho!)

Using babel and webpack together

Its really wasy to include babel as part of your webpak script.

First we are going to update index.js to use modern javascript syntax

import lodash from 'lodash';
class MyComponent {
  render(){
    var element = document.createElement('div');
    element.innerHTML = lodash.join(['Hello', 'webpack'], ' ');
    return element;
  }
}
document.body.appendChild(new MyComponent().render());

If you run webpack and test this now, it will probably still work, because your browser can deal withh the keyword class, but if you were to run on an old browser, it would most likely fail.

To combat this, we can add babel into your webpack config, to transpile the code to work on all browsers supporting javascript

npm i --save-dev webpack webpack-cli lodash babel-loader babel-core babel-preset-env 


var path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'output')
  },
  loader: {
	  loader: 'babel-loader',
	  exclude: /node_modules/,
	  query: {
	    presets: ['env']
	  }
  }  
};


npx webpack --config webpack.config.js

Sourcemaps

If you want to easily be able to debug your code in the browser, the way you originally wrote it, you will need to enable sourcemaps.

This is as straightforward as using the devtool property in your webpack.config.js file, there are different types, but for development I tend to use source-map

var path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'output')
  },
  loader: {
	  loader: 'babel-loader',
	  exclude: /node_modules/,
	  query: {
	    presets: ['env']
	  }
  },
  devtool: 'source-map'  
};

You need to make sure that if you are setting the devtool option, that you don’t supply the -d or -p (development or production flag) to webpack, as it will override the value.

The output will be a .map file, and you should be able to see this in chrome, and debug the code, as if it was the original source.

Written on March 11, 2018.

Please share and spread the word!

More posts like this