To read more articles like this, visit my blog
More often than not, we use create-react-app
for scaffolding a new React application and call it a day.
But under the hood, a lot of libraries are working to ensure everything just works. And webpack is the most important of them.
Today, we will take a deep dive into webpack and try to understand the different parts of webpack that are relevant to React development.
What’s the Problem?
We know that our browsers don’t understand anything but HTML, JavaScript, and CSS. So the obvious questions are.
How our browsers understand other files like
.jsx
How are all the different files loaded into the browser?
How our application is bundled?
How we can optimize them?
Webpack to the Rescue
Webpack is here to save the day for us! It’s the most popular bundler out there and for obvious reasons!
What webpack does is convert everything into browser-readable files that are nicely depicted in the following image:
Why Should You Care?
Most of the time you wouldn’t. But the knowledge of webpack can help you in some advanced cases:
Scaffold an application from scratch
Performance optimization
Implement micro-frontends
Generally, having an overview of the bundling process makes you more confident about your craft and understand bugs in a production system.
How Webpack Works
Typically, webpack is configured through a file named webpack.config.js
. This is where we can configure everything!
Let’s start with a simple configuration file and try to understand its various pieces.
Example
The following is a very simple configuration file for webpack:
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, 'src', 'index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}
Let's talk about entry
and output
.
1. Entry
entry
is the entry point of your application. This is the place where webpack starts its journey. We know that our React applications are like a tree. All the components span out from App.js
.
But where does our App.js
live? It lives inside the index.js
file, and that’s why webpack enters into the application via index.js
and creates a dependency graph to determine which files are needed to be loaded first.
2. Output
This is another easy concept to understand. After all the work is done, webpack creates a bundle file. In output
, we can specify the name and the location of the output file.
It has also other uses too. If you want to generate dynamic bundle names for the production system for version management you can do that here!
Let's take this a step further and try to understand how our .jsx or .css files are handled.
3. Loader
Loaders are a very important concept in webpack. They are like compilers. Webpack checks for a certain type of file and uses appropriate loaders to handle them.
A typical configuration of the loader
can be like the following:
module.exports = {
entry: { ... as before}
output: { ... as before },
module: {
rules: [
{
test: /\.[jt]sx?$/, // matches .js, .ts, .jsx and .tsx files
use: ['babel-loader'],, // uses babel-loader for the specified file types
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/,
}
],
}
}
Now, look at the module
part. It takes an array of rules. Each rule has several parts:
test
-> It checks for a certain file type. It uses regular expression.use
-> It specifies the list of loaders used for this particular file type.include
-> Which files should be processed.exclude
-> Which should not be processed.
Sometimes we need more than one type of loader for a specific file. A good example is loading CSS files. The rule for that is:
{
test: /\.css$/, // matches .css files only
use: ['style-loader', 'css-loader'],
},
Here, both css-loader
and style-loader
are used to process the file. One important thing to note here is these loaders are loaded in the reversed order.
That means first the css-loader
will work and then style-loader
will work on the output produced by css-loader
.
Similarly, for our .scss
files:
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
4. Plugins
Plugins are another very important aspect of webpack. Plugins are the main reason why webpack is so powerful.
Plugins are like libraries. They can tap into any stage of the compilation process and do whatever they wish.
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = merge(common ,{
entry: {...},
output: {...},
module: {...}
plugins: [ new CleanWebpackPlugin() ]
})
In the example above, we used a plugin named clean-webpack-plugin
. What this plugin does is clean the output folder each time we run our build command.
There are lots of plugins that you can take advantage of. You can refer to webpack’s official website if you are interested.
5. Mode
This is a simple concept to understand. You would want to use a different setup for your development and production. For this, you can use mode
.
module.exports = merge(common ,{
entry: {...},
output: {...},
module: {...},
plugins:{...},
mode : 'development' or 'production'
})
If you set the mode to production
, then your output bundle will be minified and optimized.
6. Development Server
This is the final setup for your application. While developing the application, you will not want to compile it every time you change something. That’s why you need a devServer
setup for your application.
module.exports = merge(common ,{
entry: {...},
output: {...},
module: {...},
plugins:{...},
mode : {...},
devServer: {
contentBase: path.join(__dirname, 'public/'),
port: 3000,
publicPath: 'http://localhost:3000/dist/',
hotOnly: true,
},
})
Now this setup will serve your application from the dist
that you set up earlier as your output
.
Conclusion
There you go. Now you should have a basic understanding of webpack and its different parts. Here is a full setup if you are interested:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, 'src', 'index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
mode: 'development',
module: {
rules: [
{
test: /\.[jt]sx?$/,
use: ['babel-loader'],
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.html$/,
use: ['html-loader']
}
]
},
plugins: [new CleanWebpackPlugin()],
devServer: {
contentBase: path.join(__dirname, 'public/'),
port: 3000,
publicPath: 'http://localhost:3000/dist/',
hotOnly: true,
}
}
Have a great day!
Have something to say? Get in touch with me via LinkedIn or Personal Website
Top comments (1)
The first "problem" in Webpack is "Entry". Webpack "understand" only JavaScript as an entry point.
But there is new powerful html-bundler-webpack-plugin what allows to specify an HTML template as an entry point.
All source scripts and styles can be referenced directly in HTML, similar to how it works in Vite.
The plugin detects all source files referenced in a template and extracts processed assets to the output directory. In the generated HTML and CSS, the plugin substitutes the source filenames with the output filenames.
You can very easy define HTML templates as entry points in the
entry
option of the plugin.It is also a huge developer experience improvement as many things can be inferred from the HTML.