Unpacking Webpack For Frontend Developers (Part 1)

π Hi, Iβm Mide, a software developer passionate about learning in public. I explore programming, system design, AI/ML, databases, and advanced frontend β documenting my journey toward developer proficiency.
Through this blog, I share notes, insights, and experiments from what I learn, hoping to grow while helping others grow too.
If youβve worked with React.js or Vue.js, youβve probably encountered this familiar scenario: you type npm run build into your terminal, and like magic, scripts run, and a build folder filled with neatly packed bundles appears. Everything works, and your app is ready for productionβbut have you ever wondered what really goes on behind the scenes?
Thatβs where Webpack comes in. Itβs one of those tools that a lot of developers use every day without really knowing whatβs going on under the hood. With its powerful module bundling capabilities, Webpack helps developers manage the complexities of modern web applications by organizing and optimizing assets for production.
In this article, weβre going to dive into Webpack, demystifying the processes of bundling and other core concepts that make it essential for modern web development. By the end, youβll not only understand what happens when you hit build, but youβll also gain the tools to make smarter choices when configuring Webpack for your own projects. So, letβs crack open Webpack and explore why itβs become such a crucial tool in the frontend developer's toolkit.
Introduction to Webpack
At its core, Webpack is a static module bundler designed to handle modern JavaScript applications. It builds a dependency graph, which is essentially a map that shows how all the different modules in your project are connected. This process starts at one or more entry points, typically the main JavaScript file of your app.
Webpack reads this file and checks which other files it depends on. It continues this process recursively until it identifies every file in your project thatβs somehow linked. Webpack handles everything from JavaScript to CSS, ensuring your app is bundled efficiently.
In this hands-on article, youβll learn how Webpack works by building a project step-by-step. We'll go through setting up your project with pnpm, installing Webpack, and configuring it to demonstrate core concepts like bundling, code splitting, and more.
Initial Setup
Letβs initialize a basic project using pnpm and install the required Webpack packages:
- Initialize the project:
mkdir webpack-demo
cd webpack-demo
pnpm init
- Install Webpack and dependencies:
pnpm add -D webpack webpack-cli
- Project Structure:
webpack-demo/
βββ node_modules
βββ package.json
βββ pnpm-lock.yaml
βββ public
| βββ index.html
βββ src
βββ index.js
βββ styles.css
Youβll be building alongside as we go deeper into Webpack's core concepts and advanced features!
Webpack Core Concepts
Before diving into the specifics, let's explore some of Webpack's core concepts. Understanding these fundamentals is key to leveraging Webpack effectively.
Configuration File
While Webpack can bundle projects without a configuration file (since version 4.0.0), most projects benefit from having one for more complex setups.
Create a configuration file webpack.config.js in the root directory of your project:
webpack.config.js
module.exports = {}
Entry
Webpack uses an entry point (or multiple entry points) to begin building its dependency graph. The default entry point is ./src/index.js, but you can specify a different one in your configuration.
For example:
webpack.config.js
module.exports = {
entry: './src/index.js',
};
You can also specify multiple entries:
webpack.config.js
module.exports = {
entry: {
app: ".src/index.js",
adminApp: "/src/admin/index.js"
},
};
We'll use the first example for our project.
Output
The output property tells Webpack where to emit the bundled files. By default, Webpack creates a ./dist/main.js file in a ./dist folder. Let's configure this in webpack.config.js:
webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
}
};
Loaders
Webpack only understands JavaScript and JSON out of the box. Loaders allow Webpack to process other file types, like CSS or TypeScript, and convert them into valid modules.
For instance, to load .css files, weβll need the css-loader.
Install it:
pnpm add -D css-loader
Then, add the loader to your webpack.config.js:
webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{test: /\.css$/, use: "css-loader"}
]
}
};
Plugins
Plugins extend Webpack's functionality beyond file transformation, providing features like optimization or file management. For example, the html-webpack-plugin automatically injects bundled JavaScript into an HTML file.
Install it:
pnpm add -D html-webpack-plugin
Add it to your configuration:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{test: /\.css$/, use: "css-loader"}
]
},
plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
};
Exploring Webpack Features
Update the files in your project before moving on to advanced features.
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack Demo</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
src/styles.css
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
color: #333;
}
h1 {
text-align: center;
margin-top: 50px;
}
src/index.js
import './styles.css';
const app = document.getElementById('app');
app.innerHTML = `<h1>Hello, Webpack!</h1>`;
In package.json, add a build command for Webpack:
package.json
"scripts": {
"build": "webpack",
"start": "webpack serve --open"
}
Install webpack-dev-server to serve your app:
pnpm install -D webpack-dev-server
This build command tells webpack to bundle your application and create a dist folder in your root directory. While the start command starts up a mini server to run your application.
Asset Management
Webpack can handle files like CSS, images, and fonts using loaders and plugins.
Loading CSS
There are two approaches to loading CSS in Webpack:
Injecting CSS via JavaScript: Import CSS directly into your JavaScript. This approach works by adding <style> tags directly into our HTML at runtime.
Install style-loader
pnpm add -D style-loader
Add 'style-loader' to webpack.config.js:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/i,
use:[ "style-loader", "css-loader" ]
}
]
},
plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
};
The order in which the loaders are arranged is very important 'style-loader' comes first and followed by 'css-loader'. If this convention is not followed, webpack is likely to throw errors.
css-loader: Interprets@importandurl()likeimport/require()and resolves them.style-loader: Injects CSS into the DOM by adding a<style>tag.
Generating a Separate CSS File: Use MiniCssExtractPlugin to generate a separate CSS file in production builds. This method improves performance since the browser can download and cache the CSS separately, without waiting for the JavaScript to load.
Install it:
pnpm add -D mini-css-extract-plugin
Modify webpack.config.js to use MiniCssExtractPlugin:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/i,
use:[ MiniCssExtractPlugin.loader, "css-loader" ]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new MiniCssExtractPlugin({
filename: 'main.css'
}),
],
mode: 'production', // Switch to production mode for optimized builds
};
For simplicity, weβll use CSS injection in this project.
Loading Images
Webpack allows for more efficient handling of images by importing them directly into JavaScript or CSS files rather than manually specifying paths in HTML or CSS. This method ensures that images are optimized, processed, and included in the final build, improving caching and performance.
Asset Modules for Image Management:
asset/resource: This loader copies image files into the output directory and returns their URLs for use in the code.asset/inline: This loader inlines small images as base64 URLs, reducing the number of HTTP requests.
For this guide, we will use the asset/resource module to handle images.
Add asset/resource to webpack.config.js
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/i,
use:[ "style-loader", "css-loader" ]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
]
},
plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ],
};
Add an image to your project. The folder structure for the project might look like this:
webpack-article-demo
βββ node_modules
βββ package.json
βββ pnpm-lock.yaml
βββ public
| βββ assets
| | βββ fonts
| | βββ images
| | βββ background.svg
| | βββ my-image.png
| βββ index.html
βββ src
| βββ index.js
| βββ styles.css
βββ webpack.config.js
I added two image files /public/assets/images/background.svg and /public/assets/images/my-image.png.
src/index.js
import './styles.css';
import MyImage from '../public/assets/images/my-image.png'
const app = document.getElementById("app");
app.innerHTML = `<h1>Hello, Webpack!</h1>`;
const container = document.createElement("div");
container.className = "container";
app.appendChild(container);
const img = document.createElement("img");
img.src = MyImage;
container.appendChild(img);
src/styles.css
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
color: #333;
background: url("../public/assets/images/background.svg");
background-size: contain;
background-position: center;
}
h1 {
text-align: center;
margin-top: 50px;
}
.container {
width: 100%;
margin: auto;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.container img {
width: 500px;
height: 500px;
border-radius: 50%;
}
Loading Fonts
Handling fonts with Webpack follows a similar process. You configure Webpack to handle font files (e.g., .ttf, .woff, .otf) using the asset/resource loader.
Modify webpack.config.js file to load font files:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.css$/i,
use:[ "style-loader", "css-loader" ]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
},
]
},
plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ],
};
Add some font files to your project. The folder structure for the project might look like this:
webpack-article-demo
βββ node_modules
βββ package.json
βββ pnpm-lock.yaml
βββ public
| βββ assets
| | βββ fonts
| | | βββ Anton-Regular.ttf
| | βββ images
| | βββ background.svg
| | βββ my-image.png
| βββ index.html
βββ src
| βββ index.js
| βββ styles.css
βββ webpack.config.js
I added the font file /public/assets/fonts/Anton-Regular.tff to the fonts folder.
src/styles.css
@font-face {
font-family: "Anton";
src: url("../public/assets/fonts/Anton-Regular.ttf") format("ttf");
font-weight: 400;
font-style: normal;
}
body {
font-family: "Anton";
background-color: #f0f0f0;
color: #333;
background: url("../public/assets/images/background.svg");
background-size: contain;
background-position: center;
}
h1 {
text-align: center;
margin-top: 50px;
}
.container {
width: 100%;
margin: auto;
overflow: hidden;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.container img {
width: 500px;
height: 500px;
border-radius: 50%;
}
By using the @font-face directive and loading fonts through Webpack, the build process takes care of optimizing the fonts and injecting them into the final output.
Output Management
Letβs see how the typical output from a webpack build looks like. So far here is how our project structure looks like.
webpack-article-demo
βββ node_modules
βββ package.json
βββ pnpm-lock.yaml
βββ public
| βββ assets
| | βββ fonts
| | | βββ Anton-Regular.ttf
| | βββ images
| | βββ background.svg
| | βββ my-image.png
| βββ index.html
βββ src
| βββ index.js
| βββ styles.css
βββ webpack.config.js
Weβll go ahead to make some adjustments to our webpack.config.js file
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: "development",
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].bundle.js",
clean: true
},
module: {
rules: [
{
test: /\.css$/i,
use:[ "style-loader", "css-loader" ]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
},
]
},
plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ],
};
Notice how the filename field under the output change from bundle.js to [name].bundle.js. This tells webpack to generate a bundle file using the name of the entry point. We also set our mode to development which tells webpack to use its built-in development mode optimizations accordingly. Now we can run our first build, letβs run pnpm run build and see what it generates.
assets by status 1.58 MiB [cached] 3 assets
assets by path . 38.4 KiB
asset main.bundle.js 38.2 KiB [emitted] (name: main)
asset index.html 287 bytes [emitted]
runtime modules 2.57 KiB 8 modules
javascript modules 12.5 KiB
modules by path ./node_modules/.pnpm/ 8.73 KiB
modules by path ./node_modules/.pnpm/style-loader@4.0.0_webpack@5.95.0_webpack-cli@5.1.4_/node_m...(truncated) 5.84 KiB 6 modules
modules by path ./node_modules/.pnpm/css-loader@7.1.2_webpack@5.95.0_webpack-cli@5.1.4_/node_mod...(truncated) 2.89 KiB
./node_modules/.pnpm/css-loader@7.1.2_webpack@5.95.0_webpack-cli@5.1.4_/node_mod...(truncated) 64 bytes [built] [code generated]
+ 2 modules
modules by path ./src/ 3.76 KiB
./src/index.js 376 bytes [built] [code generated]
./src/styles.css 1.66 KiB [built] [code generated]
./node_modules/.pnpm/css-loader@7.1.2_webpack@5.95.0_webpack-cli@5.1.4_/node_modules/css-loader/dist/cjs.js!./src/styles.css 1.73 KiB [built] [code generated]
asset modules 126 bytes (javascript) 1.58 MiB (asset)
./public/assets/images/my-image.png 42 bytes (javascript) 1.43 MiB (asset) [built] [code generated]
./public/assets/fonts/Anton-Regular.ttf 42 bytes (javascript) 158 KiB (asset) [built] [code generated]
./public/assets/images/background.svg 42 bytes (javascript) 1.57 KiB (asset) [built] [code generated]
webpack 5.95.0 compiled successfully in 225 ms
β¨ Done in 0.94s.
Conclusion
In this first part of our Webpack exploration, we laid the groundwork by discussing essential concepts such as configuration files, entry points, output settings, loaders, and plugins. We also learned how to manage assets like images and fonts efficiently using the asset/resource module, optimizing performance and simplifying our workflow.
Armed with these core concepts, we are now ready to delve into advanced features like code splitting, lazy loading, hot module replacement, and optimization techniques in the next section. Mastering both the basics and these advanced functionalities will enhance our ability to create efficient and scalable applications. Join us as we continue to unlock Webpack's powerful capabilities!




