A Basic Introduction to Webpack

Subscribe to my newsletter and never miss my upcoming articles

Introduction

In this article, I will introduce the core concepts with Webpack in a practical easy to follow manner. I will explain setting up the webpack configuration file from scratch and what each of the configurations such as entry point, CSS, loaders, plugins mean.

What many people may not realize is that Webpack is what runs under the hood when you are bootstrapping an Angular or a React project with an angular-cli or create-react-app.

I have written this article as a follow-along. You could get the entire code for the webpack starter described in the article from the Github repository.

What is Webpack?

Webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph that maps every module your project needs and generates one or more bundles.

The illustration shown below can explain it in a much simpler way as to what Webpack actually does.

Webpack (1).png

How does Webpack help?

Let us take an example of any web application. It typically comprises of an index.html and within it referencing many script tags as shown below.

<body>

  ...

  <script src='src/blog.js'></script>
  <script src='src/about.js'></script>
  <script src='src/contact.js'></script>
  <script src='src/index.js'></script>
</body>

While the above way of including script tags within an HTML page works, it also presents a couple of challenges such as:

  • The script tags need to be included in a certain order. This is necessary, so that, the script referencing a function inside another script is loaded before itself. In the above example, about.js, contact.js, and blog.js must be loaded into the browser before index.js, since index.js is most likely to refer a function within each of the other scripts.
  • In addition, the above method is error-prone to typos.

Webpack precisely solves this problem and by using a bundler, you do not have to worry about including every script tag within your index.html and certainly not worry about the order.

<body>

  ...

  <script src='dist/index_bundle.js'></script>
</body>

Module bundling is just one aspect that Webpack solves. But, certainly, it is much much more powerful in terms of having the power to apply transformations to your HTML, CSS, and JS files before including them in the bundler. Let us jump right into how to install and set up Webpack.

Installing Webpack

To start with, you'll need to install the following two packages to use Webpack.

//Create a new folder
$mkdir webpack-example

//Initialize a new NPM projects (Creates a package.json with default values)
> webpack-example$npm init -y

//Include the packages webpack and webpack-cli as dev dependencies
> webpack-example$npm install webpack webpack-cli --save-dev

Things to Note:

  • If you wish to follow along, please create a new folder. Open your terminal, cd into the new folder, and run the above set of command.
  • The option '—save-dev' adds the packages into your package.json as a dev dependency. What this means is that these packages will not be included with your final production build.

The Configuration File

The file webpack.config.js is the main place where most of the action happens. It is where you will provide a set of instructions to the 'Webpack' to let it know what to do with your project files and how to bundle them in a way you would like to consume.

Note: As per Webpack official docs, as of Webpack 4.0.0, you don't need a configuration file to set up the bundler. However, the configuration file is probably the most important part of Webpack that you'll need to customize as per your project needs.

We will cover the following core concepts of the Config file:

  • Entry
  • Loaders
  • Output
  • Plugins
  • Mode

Before, we begin looking at the concepts, first create the webpack.config.js at the root structure of your project.

> webpack-example$touch webpack.config.js

Webpack does the following in the specified order:

  • How do I locate the files that I need to bundle? Or optionally apply transformations on?
  • What do I need to do once I access those files? Do I need to apply any specific transformations?
  • Where do I need to output (save) the bundle generated by me?

The Entry Point

The single file that kicks off everything is typically the entry point for your Webpack. It is generally an 'index.js' or an 'app.js'.

You can visualize the import structure shown below, as a sort of how the Webpack creates the dependency graph.

index.js
  imports about.js
  imports contact.js
  imports blog.js
    imports util.js
    imports api.js

Let us create the 'index.js' within the app folder.

> webpack-example$mkdir app

> webpack-example$cd app

> webpack-example$touch index.js

Let us add the entry point to the webpack.config.js

module.exports {
    entry: './app/index.js'
}

Loaders

Now that we've set up the entry point, the next thing to tell our Webpack is what should it do with the files within our project. In other words, what kind of transformations that need to be applied to our files.

To do that, we have something called the 'loaders'. By default, Webpack looks at all the JSON and JS files to build the dependency graph as shown above.

import contact from './app/config' // 👍
import config from './utils/config.json' // 👍
import './css/app.css' // ❌

In the above, the CSS import will be ignored by Webpack and Loaders is precisely what we need here to assist Webpack to help process files other than JS and JSON.

Let us look at how to the following steps to add a CSS Loader.

> webpack-example$npm install css-loader --save-dev

We will need to add the loaders within the property 'modules.rules' array. Webpack will look at the rules array, to determine the loaders setup and the associated rules for each file type.

module.exports = {
  entry: './app/index.js',
  module: {
    rules: []
  }
}

We will need to specify the loader properties within the rules array. Every loader has 2 attributes that need to be defined:

  • use - The name of the loader.
  • test - The regex to match the file path.
module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' }
    ]
  }
}

Now, if we use a CSS anywhere within our project, Webpack will recognize it through the help of loaders and import it within our project. Basically, the above CSS import statement which had a ❌ will now have a 👍.

import contact from './app/config' // 👍
import config from './utils/config.json' // 👍
import './css/app.css' // 👍

DOM Injection

While we have succeeded in importing a CSS file using webpack configuration, there's one more thing that needs to be done. We will need to inject the style elements into the DOM.

In simple words, the CSS style needs to be included as a 'Style' tag within the HTML.

To help us achieve that, we will need to use the 'style-loader'.

> webpack-example$npm install style-loader --save-dev

And modify the webpack.config.js to add the 'style-loader' to modules.rule array.

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }
    ]
  }
}

Things to Note:

  • Since there were 2 variables to be used within the 'use' property, we changed it to an array.
  • The order of mentioning the loader is important, since, webpack will process them in the reverse order. So, the 'css-loader' will first interpret the import command and then the 'style-loader' will inject the CSS into the DOM.

There are a lot more things a loader can do and a very popular example is the use of Babel. Babel is used to transforming every JavaScript file into the appropriate ES syntax based on the configuration specified in the webpack.config.js.

You can check the full list of loaders over here..

Output

As the name suggests, this configuration parameter simply tells the Webpack where to output the bundle that it creates.

//Import 'path' to resolve the file path
const path = require('path');

//Add this configuration after module.rules config
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
}

Things to Note:

  • We are importing the 'path' package to help with resolving the file path.
  • The output configuration requires the path parameter to inform the webpack as to where to save the bundle file. In addition, using the filename parameter, you can specify the 'name' of the bundle that is generated.

Quick Recap

So until this point, we have seen the entry, loader, and output configurations. Combining all the configuration definitions so far, the webpack.config.js looks like this:

//Import 'path' to resolve the file path
const path = require('path')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }
    ]
  },
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  }
}

A Quick Recap:

  1. Webpack finds out the entry point located at ./app/index.js.
  2. It examines all the import and require statements within the project and creates a dependency graph.
  3. Then it starts creating a bundle, whenever it comes across a path we have a loader for, it transforms the code according to that loader then adds it to the bundle.
  4. Finally, It bundles and outputs it the location mentioned in the configuration which is at dist/index_bundle.js.

Hope you are able to follow up to this point. We have two more configurations 'plugins' and 'mode' to cover. So, just hang tight, we are almost there.

Plugins

While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management, and injection of environment variables.

Plugins allow you to execute certain tasks after the bundle has been created. Because of this, these tasks can be on the bundle itself or simply applied to the source codebase.

Let us look at two examples of such Plugins:

HtmlWebpackPlugin

Earlier we saw that the main benefit of webpack was that it would generate a single bundle for us that we could then use to reference inside of our main index.html page.

What HtmlWebpackPlugin does is that it will generate the index.html page for us, stick it inside of the same directory where our bundle is put, and automatically include a <script> tag which references the newly generated bundle.

As always, the first step involves downloading the plugin from npm.

> webpack-example$npm install html-webpack-plugin --save-dev

Next, we add a plugins property which is an array to our webpack config.

//Import the HtmlWebpackPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

//Add this to the module.exports configuration
plugins: [
    new HtmlWebpackPlugin()
]

Now your entire webpack.config.js will look like this:

//Import 'path' to resolve the file path
const path = require('path')

//Import the HtmlWebpackPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }
    ]
  },
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
    plugins: [
    new HtmlWebpackPlugin()
    ]
}

HtmlWebpackPlugin is one of the most basic plugins available, you can check out the rest of the plugins offered by Webpack over here.

MiniCssExtractPlugin

MiniCssExtractPlugin is the second example of a plugin that we will look at. If you remember from the earlier example, we had used a Style-loader, but using this plugin we can completely separate the CSS file instead of inserting only the style elements of the CSS.

As usual, we will start by installing the dependency as the first step:

> webpack-example$npm install --save-dev mini-css-extract-plugin

Add the following to webpack.config.js file:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

plugins: [
  new MiniCssExtractPlugin(),
],

And last but not the least, we will replace the style-loader with MiniCssExtractPlugin.loader:

{
  test: /\.css$/,
  use: [
    MiniCssExtractPlugin.loader, // instead of style-loader
    'css-loader'
  ]
}

Now when you run the webpack, it will output the main.css file in the dist folder and will be referenced from the index.html file using the link header.

Mode

Using the mode parameter you can enable webpack's built-in optimizations based on the value 'development', 'production', or 'none'. The default value is 'production'.

mode: 'production' //Other values include 'development' or 'none'

An example of build optimization run by Webpack when the mode is set to Production is that it'll minify the code and strip out warnings. For a detailed list of optimizations run behind the scenes, you can read it over here.

Running Webpack

If you've followed along, your webpack.config.js at this point should look like this:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader'] }],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js',
  },
  plugins: [new HtmlWebpackPlugin()],
  mode: 'production',
};

Let us now proceed to run Webpack. First, we'll need to add the following configuration to 'package.json'.

"scripts": {
    "build": "webpack"
}

Now run the following command from your command terminal.

> webpack-example$npm run build

Webpack will execute and create an optimized bundle named index_bundle.js and put it inside of the dist directory.

You should see the final index.html created within the 'dist' folder.

Webpack DevServer

Webpack DevServer is a development server for webpack, that'll keep track of your files in memory and serve them via a local server instead of generating a dist directory.

But the best part is that it supports live reloading. What that means is whenever you make a change in your code, webpack-dev-server will quickly recompile your code and reload the browser with those changes.

We'll need to install the package from npm.

> webpack-example$npm install webpack-dev-server --save-dev

Then we will need to update the "scripts" attribute in the package.json to run webpack-dev-server.

"scripts": {
  "start": "webpack-dev-server"
}

Also, we'll make a small change to our 'mode' parameter in the webpack.config.js to default to 'development' if 'production' is not specified in the environment variable.

mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'

//If you’re on Windows, then the command is as follows:
"SET NODE_ENV='production' && webpack"

That's it! If you run 'npm start' from the terminal then your project will be served at localhost:8080.

> webpack-example$npm start

The complete project with customization is available in the following Github Repository.

Conclusion

While this turned out to be a long article, to be truthful, we have barely touched the surface of Webpack and the endless possibilities of the configuration it supports.

To summarize, we covered the following in this article:

  • What is Webpack and What benefit it provides?
  • The core components of Webpack:
    • Entry Point
    • Loaders
    • Output
    • Plugins
    • Mode
  • Running Webpack
    • In Local & Production Mode

I hope you found the article useful. If you find my articles interesting, please do not forget to subscribe to my newsletter.

You may also be interested in:

Bolaji Ayodeji's photo

Really insightful article, thanks for sharing!

Skay's photo

Thanks Bolaji..

Hassan Taherian's photo

very helpful! I learned al lot.

Skay's photo

Thanks Hassan :-) please do share for better reach!!

Chris Bongers's photo

Awesome, I was looking for a good Webpack article, but this really helps!

Skay's photo

Thanks Chris Bongers! Means a lot! Would appreciate sharing for better reach :-)

Rutik Wankhade's photo

I had difficulties understanding webpack and it's configuration. but this makes it a lot more simple and easy. Cleared few doubts.

Skay's photo

Thanks Rutik! I'm glad you found it useful. Do share the article for better reach!!

Victoria Lo's photo

Learnt a lot! Thanks for this article :)

Skay's photo

You are welcome. Do share for maximum reach 😎

Dinys Monvoisin's photo

The flow of the article is easy to follow along. You did a great job, no doubts about that.

I have some questions I wanted you to clear out.

  • What do webpack and webpack-cli are and use for?
  • Is --save-dev still required when doing npm start?
  • What do you mean by MiniCssExtractPlugin completely separate the CSS file?

Lastly, as a note, the link to the list of plugins is redirecting to the concept page rather the actual list.

Skay's photo

Hi Dinys Monvoisin,

Thank you for taking the time to read the article. I'm glad you found it easy to read and that was my intent considering I'm a newbie in the JS world myself.

Now, let us look at each of your question:

Ques 1. What do webpack and webpack-cli are and use for?

Answer: I went through the Webpack's npm documentation provided over here.

Likewise, there's the npm package manager documentation provided over here.

I would like to think of Webpack doing the work of locating the entry point, applying the transformations and building the distribution bundle etc. after reading the information from the webpack.config.js.

Webpack-cli on the other hand is more of a helper package that can help you get setup with the configuration in a much faster manner and also enable supporting the command line interface commands to run the webpack server.

Ques 2. Is --save-dev still required when doing npm start?

Answer: No. It is not required. When we specify the command --save-dev, during npm install, it basically tells that the module should be included or referenced by the project only during development and when you run a npm run build, these modules will not be copied over to the node_modules folder within the 'dist' package.

Ques 3. What do you mean by MiniCssExtractPlugin completely separate the CSS file?

Answer: If you head over to my gitrepo that I had specified within the project and set it up locally, you'll be able to understand this better.

But, basically, rather than injecting the style inside the HTML. It creates a 'main.css' file within the 'dist' folder and a link tag is added to the index.html to reference it. I'm positive, if you try out my Github project setup locally and do a npm run build (after npm install) you'll be able to get this part.

Lastly, I have updated the link to reference to the plugins list. Thanks for bringing it to my notice.

Appreciate your questions. It also helps me dig deep and explore. But, anytime you have questions, for example, why is a certain package used, I'll only advise you to refer to the npm package documentation.

Honestly, in my humble opinion, there's just so so much information available and packing all of that in an article is simply not possible. Having said that, I appreciate your questions. I'm hoping it'll help others as well.

Cheers!!

Dinys Monvoisin's photo

Hi Skay,

Thank you for taking the time to provide a detailed answer to my questions.

I understand it now for the MiniCssExtractPlugin, it is actually creating an external file rather than embedding the CSS to the HTML file.