Webpack is the leading module bundler for React and Redux apps. I think folks using Angular 2 and other frameworks are also using it a lot these days.
When I first saw a Webpack config file, it looked very alien-y 👽 and confusing 😱. After playing around with it for some time, I now think that it is because Webpack just has a unique syntax and new philosophies that may cause confusion in the beginning. Incidentally, these philosophies are also responsible for making it so popular.
Since it’s confusing to get started, I thought I’ll write a few posts that’ll hopefully make it easy for others to get started and use it’s powerful features. Here is the 1st installment.
Webpack’s Core Philosophy
Two core philosophies of Webpack are:
- Everything is a module — Just like JS files can be “modules”, everything else (CSS, Images, HTML) can also be modules. That is, you can require(“myJSfile.js”) or require(“myCSSfile.css”). This mean we can split any artifact into smaller manageable pieces, reuse them and so on.
- Load only “what” you need and “when” you need — Typically module bundlers take all the modules and generate a large single output “bundle.js” file. But in many real-world apps, this “bundle.js” could be 10MB-15MB and could take forever to load! So Webpack has various features to split your code and generate multiple “bundle” files, and also load parts of the app asynchronously so that you just load what you need and when you need it.
OK, Let’s take a look at various “confusing” parts.
1. Development Vs Production
First thing to be aware of is that Webpack has tons of features. Some are for “Development-only”, some others are for “Production-only” and some are for both “Production-and-Development”.
Note: You can click on the pictures to zoom and read.
Typically most projects use so many features that they usually have two large Webpack config files.
And to create bundles you’ll write scripts in the package.json like so:
“scripts”: {
//npm run build to build production bundles
“build”: “webpack --config webpack.config.prod.js”, //npm run dev to generate development bundles and run dev. server
“dev”: “webpack-dev-server”
}
2. webpack CLI Vs webpack-dev-server
It’s important to note that Webpack, the module bundler, provides two interfaces:
- Webpack CLI tool — the default interface (installed as part of Webpack itself)
- webpack-dev-server tool — A Node.js server (You need to install it separately)
Webpack CLI (Good for Production Builds)
This tool takes options via CLI and also via a config file (default: webpack.config.js) and gives it to the Webpack for bundling.
While you may start learning Webpack using the CLI, you’ll only mostly use it for generating production builds afterwards.
Usage:
OPTION 1: //Install it globally
npm install webpack --g//Use it at the terminal
$ webpack //<--Generates bundle using webpack.config.js
OPTION 2 ://Install it locally & add it to package.json
npm install webpack --save//Add it to package.json's script
“scripts”: {
“build”: “webpack --config webpack.config.prod.js -p”,
...
}//Use it by running the following:
"npm run build"
Webpack-dev-server (Good for Development Builds)
This is an Express node.js server that runs at port 8080. This server internally calls Webpack. The benefit of this is that it provides additional capabilities like reloading the browser i.e. “Live Reloading” and/or replacing just the changed module i.e “Hot Module Replacement” (HMR).
Usage:
OPTION 1://Install it globally
npm install webpack-dev-server --save//Use it at the terminal
$ webpack-dev-server --inline --hotOPTION 2:// Add it to package.json's script
“scripts”: {
“start”: “webpack-dev-server --inline --hot”,
...
}// Use it by running
$ npm startOpen browser at:
http://localhost:8080
Webpack Vs webpack-dev-server options
It’s worth noting that some of the options like “inline” and “hot” are webpack-dev-server only options. Where as some others like “hide-modules” are CLI only options.
webpack-dev-server CLI options Vs config options
The other thing to note is you can pass options to webpack-dev-server in two ways:
- Through webpack.config.js’s “devServer” object.
- Through CLI options.
//Via CLI
webpack-dev-server --hot --inline//Via webpack.config.js
devServer: {
inline: true,
hot:true
}
I’ve found that sometimes the devServer config (hot:true and inline:true) doesn’t work! So I prefer just passing options as CLI options within package.json like so:
//package.json
{
scripts:
{“start”: “webpack-dev-server --hot --inline”}
}
Note: Make sure you are not passing hot:true and -hot both together.
“hot” Vs “inline” webpack-dev-server options
“inline” option adds “Live reloading” for the entire page. “hot” option enables “Hot Module Reloading” that tries to reload just the component that’s changed (instead of the entire page). If we pass both options, then, when the source changes, the webpack-dev-server will try to HMR first. If that doesn’t work, then it will reload the entire page.
//When the source changes, all 3 options generates new bundle but,
//1. doesn't reload the browser page
$ webpack-dev-server//2. reloads the entire browser page
$ webpack-dev-server --inline//3. reloads just the module(HMR), or the entire page if HMR fails
$ webpack-dev-server --inline --hot
3. “entry” — String Vs Array Vs Object
Entry tells the Webpack where the root module or the starting point is. This can be a String, an Array or an Object. This could confuse you but the different types are used for different purposes.
If you have a single starting point (like most apps), you can use any format and the result will be the same.
entry — Array
But, if you want to append multiple files that are NOT dependent on each other, you can use the Array format.
For example: you may need “googleAnalytics.js” in your HTML. You can tell Webpack to append it to the end of the bundle.js like so:
entry — object
Now, let’s say you have true multi-page application, not a SPA w/ multi-views, but with multiple HTML files (index.html and profile.html). You can then tell Webpack to generate multiple bundles at once by using entry object.
The below config will generate two JS files: indexEntry.js and profileEntry.js that you can use in index.html and profile.html respectively.
Usage:
//profile.html
<script src=”dist/profileEntry.js”></script>//index.html
<script src=”dist/indexEntry.js”></script>
Note: The name of the file comes from the “entry” object’s keys.
entry — combination
You can also use the Array type entries inside an entry object. For example the below config will generate 3 files: vendor.js that contains three vendor files, an index.js and a profile.js.
4. output — “path” Vs “publicPath”
output tells the Webpack where and how to store the resulting files. It has two properties “path” and “publicPath” that could be confusing.
“path” simply tells the Webpack where it should store the result. Where as “publicPath” is used by several Webpack’s plugins to update the URLs inside CSS, HTML files when generating production builds.
For example, in your CSS, you may have a url to load ‘./test.png’ on your localhost. But in production, the ‘test.png’ might actually be located at a CDN while your node.js server might be running on Heroku. So that means, you’ll have to manually update the URLs in all the files to point to the CDN when running in Production.
Instead, you can use Webpack’s publicPath and use bunch of plugins that are publicPath-aware to automatically update URLs when generating production builds.
Note: You can click on the pictures to zoom and read
// Development: Both Server and the image are on localhost
.image {
background-image: url(‘./test.png’);
}// Production: Server is on Heroku but the image is on a CDN
.image {
background-image: url(‘https://someCDN/test.png’);
}
5. Loaders And Chaining Loaders
Loaders are additional node modules that help ‘load’ or ‘import’ files of various types into browser acceptable formats like JS, Stylesheets and so on. Further loaders also allow importing such files into JS via “require” or “import” in ES6.
For example: You can use babel-loader to convert JS written in ES6 to browser acceptable ES5 like so:
module: {
loaders: [{
test: /\.js$/, ←Test for ".js" file, if it passes, use the loader
exclude: /node_modules/, ←Exclude node_modules folder
loader: ‘babel’ ←use babel (short for ‘babel-loader’)
}]
Chaining Loaders ( works right to left)
Multiple Loaders can be chained and made to work on the same file type. The chaining works from right-to-left and the loader are separated by “!”.
For example, Let’s say we have a CSS file “myCssFile.css” and we want to dump it’s content into <style>CSS content</style> tag inside our HTML. We can accomplish that using two loaders: css-loader and style-loader.
module: {
loaders: [{
test: /\.css$/,
loader: ‘style!css’ <--(short for style-loader!css-loader)
}]
Here is how it works:
- Webpack searches for CSS files dependencies inside the modules. That is Webpack checks to see if a JS file has “require(myCssFile.css)”. If it finds the dependency, then the Webpack gives that file first to the “css-loader”
- css-loader loads all the CSS and CSS’ own dependencies (i.e @import otherCSS) into JSON. Webpack then passes the result to “style-loader”.
- style-loader to take the JSON and add it to a style tag — <style>CSS contents</style> and inserts the tag into the index.html file.
6. Loaders Themselves Can Be Configured
Loaders themselves can be configured to work differently by passing parameters.
In the example below, we are configuring url-loader to use DataURLs for images less than 1024 bytes and use URL for images that are larger than 1024 bytes. We can do this by passing “limit” parameter in the following two ways:
7. The .babelrc file
babel-loader uses “presets” configuration to know how to convert ES6 to ES5 and also how to parse React’s JSX to JS. We can pass the configuration via “query” parameter like below:
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
}
]
}
However in many projects babel’s configuration can become very large. So instead you can keep those them in babel-loader’s configuration file called .babelrc file. babel-loader will automatically load the .babelrc file if it exists.
So in many examples, you’ll see:
//webpack.config.js
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel'
}
]
}
//.bablerc
{
“presets”: [“react”, “es2015”]
}
8. Plugins
Plugins are additional node modules that usually work on the resulting bundle.
For example, uglifyJSPlugin takes the bundle.js and minimizes and obfuscates the contents to decrease the file size.
Similarly extract-text-webpack-plugin internally uses css-loader and style-loader to gather all the CSS into one place and finally extracts the result into a separate external styles.css file and includes the link to style.css into index.html
//webpack.config.js
//Take all the .css files, combine their contents and it extract them to a single "styles.css"var ETP = require("extract-text-webpack-plugin");
module: {
loaders: [
{test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
]
},plugins: [
new ExtractTextPlugin("styles.css") //Extract to styles.css file
]
}
Note: If you want to just inline CSS as a style element into HTML, you can do that without the extract-text-webpack-plugin and by just CSS and Style loaders like below:
module: {
loaders: [{
test: /\.css$/,
loader: ‘style!css’ <--(short for style-loader!css-loader)
}]
9. Loaders Vs Plugins
As you might have realized, Loaders work at the individual file level during or before the bundle is generated.
Where as Plugins work at bundle or chunk level and usually work at the end of the bundle generation process. And some Plugins like commonsChunksPlugins go even further and modify how the bundles themselves are created.
10. Resolving File Extensions
Many Webpack config files have a resolve extensions property that has an empty string like shown below. The empty string is there to help resolve imports without extensions like: require(“./myJSFile”) or import myJSFile from ‘./myJSFile’ without file extensions.
{
resolve: {
extensions: [‘’, ‘.js’, ‘.jsx’]
}
}
That’s it! 👍
Thanks to Tobias Koppers (Webpack creator) for reviewing this post!
My Other Posts
LATEST:
[Video course] The Inner workings of the Browser — for JavaScript & Web Developers Use code: INNER15 and get 50% off!
2. Two Quick Ways To Reduce React App’s Size In Production
Functional Programming
- JavaScript Is Turing Complete — Explained
- Functional Programming In JS — With Practical Examples (Part 1)
- Functional Programming In JS — With Practical Examples (Part 2)
- Why Redux Need Reducers To Be “Pure Functions”
ES6
WebPack
- Webpack — The Confusing Parts
- Webpack & Hot Module Replacement [HMR] (under-the-hood)
- Webpack’s HMR And React-Hot-Loader — The Missing Manual
Draft.js
React And Redux :
- Step by Step Guide To Building React Redux Apps
- A Guide For Building A React Redux CRUD App (3-page app)
- Using Middlewares In React Redux Apps
- Adding A Robust Form Validation To React Redux Apps
- Securing React Redux Apps With JWT Tokens
- Handling Transactional Emails In React Redux Apps
- The Anatomy Of A React Redux App
- Why Redux Need Reducers To Be “Pure Functions”
- Two Quick Ways To Reduce React App’s Size In Production
Salesforce
🎉🎉🎉 If you like this post, please 1. ❤❤❤ it below on Medium and 2. please share it on Twitter. You may retweet the below card🎉🎉🎉
Thank you! 🙏