Table of Contents
  1. Overview
  2. What's a bundler?
  3. Getting Started
    1. Project Setup
      1. Images
      2. Scripts
      3. Styles
  4. Webpack Minimal Configurations
    1. Webpack and Webpack CLI
    2. Webpack Configuration
    3. How To Run Webpack
  5. Webpack Further Customization
    1. Output & Mode
      1. Load bundled.js file into index.html
    2. Watch mode
  6. Webpack Dev Server
    1. Install webpack-dev-server
    2. Update webpack.config.js
    3. Update package.json
    4. Update App.js
    5. Test webpack-dev-server
      1. 1. Make a change in HTML file
      2. 2. Make a change in CSS file
  7. CSS Workflow With Webpack
    1. Add CSS Support To Webpack Configuration
      1. Create a styles.css
      2. Import the styles.css in App.js
      3. Install css-loader & style-loader
      4. Configure webpack.config.js
    2. Add PostCSS Support To Webpack Configuration
      1. Install postcss-loader and 3 plugins
      2. Configure webpack.config.js
  8. What's Next
Webpack Essentials, Part 1 - Setting Up Workflow

Overview

In this two part series article, I am going to show you how to configure Webpack to get started with your next project using html, javascript, and css(more specifically PostCSS). The Webpack is our choice for bundling all the necessary resources together in order for our website to work.

Then what is Webpack? According to Webpack's official documentation, Webpack is:

At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph from one or more entry points and then combines every module your project needs into one or more bundles, which are static assets to serve your content from.

What's a bundler?

A bundler is a tool that puts together all your JavaScript code and its dependencies and throws a new JavaScript output file with everything merged, ready for the web, commonly known as the bundle file.

Basically, Webpack bundles:

  • Assets:
    • images(jpg, jpeg, png, svg, etc.)
    • scripts(javascript)
    • styles(css)

When we structurize our project directories below, we will follow this concept.

Getting Started

Project Setup

Now that we understand what is a bundler and Webpack, we can get started with creating our project directory and initialize it with npm:

Terminal
mkdir webpack-starter
cd webpack-starter
npm init -y
vim . # Or code . if you use VS Code Editor

First, let's create ./app directory in the project root. And within this app folder, let's create something that we would want Webpack to bundle for us.

Inside the app folder, create a index.html:

./app/index.html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Webpack Starter</title>
8 </head>
9 <body>
10 <div>
11 <img src="assets/images/card-1.jpg" width="800" />
12 <h1>Title</h1>
13 <p>
14 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi aut
15 quisquam maiores ratione quidem veritatis sapiente expedita
16 necessitatibus harum accusamus.
17 </p>
18 <h4>Date</h4>
19 </div>
20 </body>
21</html>

Create the follwoing directories inside the ./app/assets folder:

  • images: will contain all the image resources(jpg, jpeg, png, svg, etc.)
  • scripts: will contain javascript files
    • App.js: javascript file that we would want Webpack to process for us
  • styles: will containt css files

You will have a structure like this:

.
├── app
│   ├── assets
│   │   ├── images
│   │   ├── scripts
│   │   │   └── App.js
│   │   └── styles
│   └── index.html
└── package.json

We will go over each directory one by one throughout the article.

Images

I've saved some default images in the images folder for demonstration purpose. You can download images by visiting here.

Scripts

./app/assets/scripts/App.js
1alert('Hello, Webpack!!!')

Styles

We are not going to generate anything in the styles directory for now because we will take care of that later when we add CSS suppoprt to Webpack configuration.

Webpack Minimal Configurations

Webpack and Webpack CLI

Although we created the javascript file App.js in the scripts folder, we haven't told Webpack to look at this file and process/bundle it for us. To do that, we have to install two packages: webpack & webpack-cli:

Open up your terminal and run the following command:

Terminal
npm install webpack webpack-cli --save-dev

Note that it might take a while to finish the installations.

Webpack Configuration

The way we tell Webpack to do what we want to do is to configure webpack.config.js file.

./webpack.config.js
1module.exports = {
2 entry: './app/assets/scripts/App.js',
3}

Remember that we created App.js file? We want to tell Webpack to find/process this file as an entry point before it builds out its internal dependency graph.

This is the minimal setup for our demonstration, but we will add more properties later.

How To Run Webpack

Now that we've configured Webpack, it is time for us to run Webpack to bundle things for us. But how? The answer is we will utilize scripts property in package.json.

./package.json
1{
2 "name": "webpack-starter",
3 "version": "1.0.0",
4 "description": "",
5 "scripts": {
6 "dev": "webpack",
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [],
10 "author": "",
11 "license": "ISC",
12 "devDependencies": {
13 "webpack": "^5.72.0",
14 "webpack-cli": "^4.9.2"
15 }
16}

Because we installed Webpack as a dev dependency, not as a global, our terminal can't understand a direct command webpack, but by using the scripts inside package.json, we can run Webpack like this:

Terminal
npm run dev

It is highly likely that you will find one warning message saying The mode option has not been set..., but for now we can ignore this. As soon as you run the command, you will notice a brand new folder named dist, and within that folder there's a new file named main.js.

What happened behind the scenes is that the npm found the Webpack package inside node_modules in our project root and ran it for us. Then Webpack looked at App.js file and it processed it and bundled it and created the new main.js.

Webpack Further Customization

Output & Mode

We can add more properties to the webpack.config.js file for further customization.

What I want to customize here is:

  • Instead of creating dist/main.js as defualt, I would like to change main.js to bundled.js and then save it to the existing ./app directory. In other words, I want the bundled javascript file to sit with index.html side by side in the app folder.
  • By setting mode to development, I want to resolove the warning message that we've seen earlier.

So jump back into your webpack.config.js.

./webpack.config.js
1const path = require('path')
2
3module.exports = {
4 entry: './app/assets/scripts/App.js',
5 output: {
6 filename: 'bundled.js',
7 path: path.resolve(__dirname, 'app'),
8 },
9 mode: 'development',
10}

Now, before we test this out again, let's be sure to delete the dist folder in the project root. We didn't want the bundled file to be output there any longer.

Let's see what we get now by running npm run dev again:

Terminal
npm run dev

Notice that we don't have any warning message any more. Now in our app folder, we have the brand new generated bundled.js file sitting by index.html.

Load bundled.js file into index.html

We now have bundled.js and index.html sitting side by side in ./app directory. However, to take our webpages to the next level by harnessing javascript, we need to load the bundled.js into the index.html.

To call javascript code from within HTML, you need the <script> element.

./app/index.html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Webpack Starter</title>
8 </head>
9 <body>
10 <div>
11 <img src="assets/images/card-1.jpg" />
12 <h1>Title</h1>
13 <p>
14 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi aut
15 quisquam maiores ratione quidem veritatis sapiente expedita
16 necessitatibus harum accusamus.
17 </p>
18 <h4>Date</h4>
19 </div>
20
21 <script src="bundled.js"></script>
22 </body>
23</html>

As you see, we've added the line 21 <script> tag to load bundled.js into the index.html file. Let's save the file and open up a file explorer on your OS and drag the index.html file into the web browser. If you see a pop-up alert with 'Hello, Webpack!!!', then you successfully loaded the bundled.js into the index.html.

network

Watch mode

Right now, whenever we make changes in our codes, we need to run Webpack again to rebundle. Can we make Webpack rebundle automatically whenver we save changes to our source file? Absolutely yes!!! Again we will add a new property to webpack.config.js file.

So jump back into your webpack.config.js.

./webpack.config.js
1const path = require('path')
2
3module.exports = {
4 entry: './app/assets/scripts/App.js',
5 output: {
6 filename: 'bundled.js',
7 path: path.resolve(__dirname, 'app'),
8 },
9 mode: 'development',
10 watch: true,
11}

With this new configuration, please run npm run dev again. Since we set the watch option to true(line 10), Webpack will stay running and it will watch and detect any time we save a change to our source file or our entry file so that we don't need to run npm run dev manually over and over again.

Webpack Dev Server

However, we still need to refresh pages in the browser to see the actual changes made, right? Can we make our source files updated in the browser without a full reload? Yes!!! This is where webpack-dev-server comes into play to save our life.

Install webpack-dev-server

Terminal
npm install webpack-dev-server --save-dev

Update webpack.config.js

./webpack.config.js
1const path = require('path')
2
3module.exports = {
4 entry: './app/assets/scripts/App.js',
5 output: {
6 filename: 'bundled.js',
7 path: path.resolve(__dirname, 'app'),
8 },
9 devServer: {
10 watchFiles: ['./app/**/*.html'],
11 static: {
12 directory: path.join(__dirname, 'app'),
13 },
14 hot: true,
15 port: 3000,
16 host: '0.0.0.0',
17 },
18 mode: 'development',
19 watch: true,
20}

We added devServer property that tells Webpack to:

  • reload the browser for any changes in html files

  • watch ./app directory for any changes

  • do hot module replacement(HMR) when we save changes to the source files

  • listen for requests on a port 3000

  • make the dev server to be accessible externally by other devices such as a tablet, a mobile phone connected to the same Wi-Fi

    • For example, if you use a mac os, you can open up System Preferences > Network to find your local ip address:

      network

      In my case, it is 192.168.0.170. I can access the same website from my mobile phone by typing 192.168.0.170:3000 in my Chrome browser.

Note that we no longer need watch: true(line 19) because webpack-dev-server is told to do the same already.

Update package.json

Next, to be able to run webpack-dev-server, we need to make a slight modification on the scripts in package.json file:

./package.json
1{
2 "name": "webpack-starter",
3 "version": "1.0.0",
4 "description": "",
5 "scripts": {
6 "dev": "webpack",
7 "dev": "webpack serve",
8 "test": "echo \"Error: no test specified\" && exit 1"
9 },
10 "keywords": [],
11 "author": "",
12 "license": "ISC",
13 "devDependencies": {
14 "webpack": "^5.72.0",
15 "webpack-cli": "^4.9.2"
16 "webpack-dev-server": "^4.8.1"
17 }
18}

Update App.js

Lastly, we need to tell our entry point javascript file to accept updates on the fly:

./app/assets/scripts/App.js
1alert('Hello, Webpack!!!')
2if (module.hot) {
3 module.hot.accept()
4}

We no longer need alert function, so I removed it.

Test webpack-dev-server

Terminal
npm run dev

1. Make a change in HTML file

Let's jump into index.html and change Title to Title!!! in <h1>> tag.

./app/index.html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Webpack Starter</title>
8 </head>
9 <body>
10 <div>
11 <img src="assets/images/card-1.jpg" />
12 <h1>Title</h1>
13 <h1>Title!!!</h1>
14 <p>
15 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi aut
16 quisquam maiores ratione quidem veritatis sapiente expedita
17 necessitatibus harum accusamus.
18 </p>
19 <h4>Date</h4>
20 </div>
21
22 <script src="bundled.js"></script>
23 </body>
24</html>

You should be able to see the change immediately if you access localhost:3000.

Please note that a full page reload will happen when you save any changes to *.html file. This is different from hot module replacement in that the webpage will lose any previous states.

2. Make a change in CSS file

By the time we add CSS support to our Webpack configuration, we will be able to see hot module replacement work whenever we save changes to CSS files. Once again, this HMR differs from a full page reload that forces us to lose any state on our webpage.

One last thing I want to point out is that webpack-dev-server does not produce a new bundled javascript file to our local hard disk whenever we save changes to files. The dev server truly works with the bundled javascript file residing in memory. So even if we delete the bundled.js file from our directory, the server will still work without any issues. You can confirm this by deleting the bundled.js file manually and accessing localhost:3000/bundled.js in the web browser. You should see the bundled.js file still loaded without any problems because it is pulled from memory.

CSS Workflow With Webpack

Writing CSS is an essential part of the front-end development. There are lots of useful CSS preprocessors such as Sass, Less, Stylus, I will introduce a postprocessor, PostCSS. It is very well known that PostCSS can do the similar work as preprocessors, but it is modular and faster. PostCSS is a tool that uses JavaScript-based plugins to automate routine CSS operations. If you heard about autoprefixer, that is one of those plugins available in PostCSS ecosystem. PostCSS makes it possible to write CSS in a developer friendly way that can't be understood by a normal web browser, but it parses those codes into regular CSS codes that the browser can understand.

Add CSS Support To Webpack Configuration

Before we configure PostCSS, let's start small by making a regular CSS work with Webpack.

Create a styles.css

./app/assets/styles/styles.css
1body {
2 color: blue;
3}

Import the styles.css in App.js

We will import this styles.css in App.js. The reason why we import styles.css in javascript file instead of injecting it into html using stylesheet is very important for our development workflow and its efficiency. However, we will have used the stylesheet to load CSS later when we actually serve our website to public. We will tackle this in the Part 2 - CSS Artchitecture.

./app/assets/scripts/App.js
1import '../styles/styles.css'
2
3if (module.hot) {
4 module.hot.accept()
5}

Install css-loader & style-loader

In order to tell Webpack to make CSS workflow, we need to install two dependencies:

  • The css-loader interprets @import and url() like import/require() and will resolve them.
  • The style-loader injects CSS into the DOM.
Terminal
npm install css-loader style-loader --save-dev

Configure webpack.config.js

Now it is time to tell Webpack to use these dependencies to handle CSS.

So jump back into your webpack.config.js. To configure CSS workflow, we will use modules, rules properties:

./webpack.config.js
1const path = require('path')
2
3module.exports = {
4 entry: './app/assets/scripts/App.js',
5 output: {
6 filename: 'bundled.js',
7 path: path.resolve(__dirname, 'app'),
8 },
9 devServer: {
10 watchFiles: ['./app/**/*.html'],
11 static: {
12 directory: path.join(__dirname, 'app'),
13 },
14 hot: true,
15 port: 3000,
16 host: '0.0.0.0',
17 },
18 mode: 'development',
19 module: {
20 rules: [
21 {
22 test: /\.css$/i,
23 use: [
24 'style-loader',
25 {
26 loader: 'css-loader',
27 options: { url: false },
28 },
29 ],
30 },
31 ],
32 },
33}

Basically, what the added codes do is to tell Webpack to test if any file is CSS. If so, use css-loader, style-loader. By setting the url option to false, the css-loader won't attempt to handle any images we reference in our CSS. That is, we'll manage our image files manually.

Now let's test if the CSS workflow works:

Terminal
npm run dev

This will generate a new bundled.js file with the styles.css incoprated. Howerver,

Open up your web browser and access localhost:3000 again. You should see the text changed to blue immediately with the help of webpack-dev-server and hot module replacement we've configured earlier.

css

Add PostCSS Support To Webpack Configuration

Install postcss-loader and 3 plugins

Terminal
npm install postcss-loader postcss-simple-vars postcss-nested autoprefixer --save-dev
DependenciesUsage
postcss-loaderloads CSS with PostCSS
postcss-simple-varssupports for Sass-style variables
postcss-nestedunwraps nested rules
autoprefixeradds vendor prefixes

Configure webpack.config.js

./webpack.config.js
1const path = require('path')
2
3const postCSSPlugins = [
4 require('postcss-simple-vars'),
5 require('postcss-nested'),
6 require('autoprefixer'),
7]
8
9module.exports = {
10 entry: './app/assets/scripts/App.js',
11 output: {
12 filename: 'bundled.js',
13 path: path.resolve(__dirname, 'app'),
14 },
15 devServer: {
16 watchFiles: ['./app/**/*.html'],
17 static: {
18 directory: path.join(__dirname, 'app'),
19 },
20 hot: true,
21 port: 3000,
22 host: '0.0.0.0',
23 },
24 mode: 'development',
25 module: {
26 rules: [
27 {
28 test: /\.css$/i,
29 use: [
30 'style-loader',
31 {
32 loader: 'css-loader',
33 options: { url: false },
34 },
35 {
36 loader: 'postcss-loader',
37 options: { postcssOptions: { plugins: postCSSPlugins } },
38 },
39 ],
40 },
41 ],
42 },
43}

With this new configuration, let's add some PostCSS-specific syntax and try that out:

./app/assets/styles/styles.css
1body {
2 color: blue;
3}
4
5$mainRed: red;
6
7.card {
8 h1 {
9 color: $mainRed;
10 }
11}

Let's target <div> element with .card selector in index.html:

./app/index.html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Webpack Starter</title>
8 </head>
9 <body>
10 <div>
11 <div class="card">
12 <img src="assets/images/card-1.jpg" width="800" />
13 <h1>Title</h1>
14 <p>
15 Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi aut
16 quisquam maiores ratione quidem veritatis sapiente expedita
17 necessitatibus harum accusamus.
18 </p>
19 <h4>Date</h4>
20 </div>
21
22 <script src="bundled.js"></script>
23 </body>
24</html>

Spin up the dev server again and access localhost:3000.

Terminal
npm run dev

Jump back on the browser. As we expected, the color on Title has been changed into red.

css

What's Next

We went through a lot in this Part 1. With all the basic configurations done, we will take another step to talk about CSS architecture with Webpack in the Part 2. I hope you enjoyed reading and please feel free to leave any questions or comments if you have.

all posts

Comments

© 2022 Youngjae Jay Lim. All Rights Reserved, Built with Gatsby