

Why another webpack tutorial? Because, almost everything about it is confusing and Iโve suffered a lot like you. Thatโs why Iโve decided to write this step-by-step procedure to make a better webpack configuration with a good understanding of how webpack works. I hope that you wonโt get confused and leave again in the middle of configuring webpack again. [updated: 2018 Febย 22]
WEBPACK! Simply, itโs a module bundler, meansโโโyou can create as many modules(not only JS, but also HTML & CSS too!) while developing an app. Webpack takes the responsibility to bundle your application and give you the customized simplest version of your app which contains only HTML, CSS & Javascript.
Iโve realized that itโs a habit that have to be developed through understanding the core concepts of how webpack works and then applying it on different projects/demos.
This guide may let you easily configure webpack in just 5 minutes for any kind of projects. No Headaches anymore! In the end of this article, Iโm going to give you the simplest cheat-sheet to get ready for any projectย . Follow along with me. This blog is going to be a bigger one, so why donโt you grab a coffee and stay with me patientlyย :)
Or, just go to this repo and clone it to use it in your project: webpack-boilerplate
Before starting, letโs see whatโs inside this write up ->
NOTE: Iโm using linux in my pc. If youโre using windows, I recommend to look for the respective commands on windows. Mac users should have the same commands as mine.
#
1. PROJECT SETUP
#
Follow along with me to create a basic project. App architecture is the most important key!
> mkdir project_name && cd project_name
> npm init
> mkdir src dist src/assets src/assets/media src/assets/js src/assets/scss
> touch webpack.config.js README.md .babelrc src/index.html src/app.js src/assets/scss/app.scss
NOTE:.babelrc
file is for configuring babel (which is going to transpile our ES6/ES2015 code to ES5)webpack.config.js
for configuring our webpackโs responsibilitiesREADME.md
for documentation
Install webpack
> npm i -D webpack
npm i -D webpack
is just the shortcut of npm installโโโsave-dev webpack
An awesome article about some npm tricks that is so useful for me
Project hierarchy for our setup:
#
2. CONFIGURING PACKAGE.JSON
#
Letโs get some extra headaches out of our brain. We need to build our project for development and for production. And, we donโt want to refresh our browser again and again while modifying our code every time. So why donโt we put some thing to watch our code? ๐
If webpack is already installed globally in your machine, simply write these commands into your package.json
scripts:
"scripts": {
"build": "webpack",
"build:prod": "webpack -p",
"watch": "webpack --watch"
}
I think, the better approach is to install updated webpack locally for every project, which will give you a complete freedom in development process.
If youโre following the steps already and installed webpack locally, then, changes in package.json
would be:
"scripts": {
"build": "./node_modules/.bin/webpack",
"build:prod": "./node_modules/.bin/webpack -p",
"watch": "./node_modules/.bin/webpack --watch"
}
You can get the idea of the directory for those script commands below. Webpack executable binary file stays in this directory
NOTE: If youโre interested to know the details of webpack-cli: https://webpack.js.org/api/cli
webpack.config.js
to make webpack a liโl bit educatedย ๐#
3. LAND ON THE WEBPACK CONFIG FILE
#
First thing first -
Webpack simply needs to have itโs 4 core things to execute properly.
ย 1. Entry
ย 2. Output
ย 3. Loaders
ย 4. Plugins
Weโre going to define entry and output for webpack in this section and watch our first bundled output.
Since, weโre using node.js and webpack uses the modular pattern, we first need to export the configuration object from our webpack.config.js
module.exports = {
// configurations here
}
OR, this approach:
const config = {
// configurations here
};
module.exports = config;
The app starts executing from this point if your app is SPA(Single Page Application). Weโll define a path relative to our project root
const config = {
entry: './src/app.js',
};
If your app has multiple entry points (like: multi-page application), then, you have to define your entry points inside entry
object with identical names. Multiple entry points are called chunks
and the properties (individual entry point) of this entry object are called entryChunkName
. So, letโs create it:
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
}
Look carefully, ourentry
property is not astring
anymore, itโs now a purejavascript object
with different entries being the properties of it.
This is so helpful when we want to separate our app entry and vendor (like: jQuery/lodash) entry into different bundles.
Webpack needs to know where to write the compiled files in the disk. Thatโs why we need to define output point for webpack.
NOTE: while there can be multiple entry points, only ONE output configuration is specified.
We define our output point as an object
. This object must include these two following things at least:
ย 1. filename
(to use our output files)
ย 2. path
(an absolute path to preferred output directory)
We can define path
of the output point manually. But that wonโt be a wise choice, since our project rootโs name and location may change later. Besides, you or your collaborator may clone your projects on different pc in which case the custom absolute path wonโt work there too.
So, to solve this, we use node.js path
module which gives us the absolute path for our project root in a more convenient way.
To use node.js path
module we need to import it in our config file and then use it in our output object
const config = {
output: {
filename: 'bundle.js',
// Output path using nodeJs path module
path: path.resolve(__dirname, 'dist')
}
};
You can use path.join
or path.resolve
, though both working kinda same. To not to make this article bigger and distracting, Iโm going to skip the process of how node.js path.join
and path.resolve
works, but giving you the resources:
path.resolve resourceย : here
path.join resourceย : here
NOTE: when creating multiple bundles for multiple entry points, you should use one of the following substitutions to give each bundle a unique name
Using entry name:
filename: "[name].bundle.js"
Using hashes based on each chunksโ content:
filename: "[chunkhash].bundle.js"
For more naming options: https://webpack.js.org/configuration/output/#output-filename
Besides, you may want to give a relative path to your output file in the output.filename
Context is the base directory, an absolute path, for resolving entry points and loaders from configuration. By default the current directory is used, but itโs recommended to pass a value in your configuration. This makes your configuration independent from CWD (current working directory).
const config = {
context: path.resolve(__dirname, "src")
};
Until now, our basic setup:
Now, fire this command into your terminal to watch your first bundle:
> npm run build
For production ready bundle:
> npm run build:prod
For developing with watch mode ON:
> npm run watch
Voila! ๐ Weโve just landed on the ground of awesomeness!
Now, weโre going to add some loaders and plugins to make our actual configuration object after finishing two more setup. Weโre going to use some automation that is provided by webpack.
#
4. SETTING UP WEBPACK-DEV-SERVER
#
Hey! Wasnโt that an easy setup to get our first bundle? Now weโre going to get some amazing things thatโs gonna boost-up our development process and save us a lot of time. Weโre going to get a real server! Yeah, webpack provides us a built-in server for development purpose, so that weโre going to see whatโs going to happen while our application is ready for deployment into a real server. But we need to install this server setup first.
NOTE: This should be used for development only.
Install webpack-dev-server via terminal
> npm i -D webpack-dev-server
Add this command to scripts
in package.json
"dev": "./node_modules/.bin/webpack-dev-server"
There are so many configuration options for webpack-dev-server
. Weโre going to look for some important ones.
In your config
object letโs create a new property named devServer
(syntax is important)
devServer: {}
This object is ready to get some configuration options such as:
#1 devServer.contentBase
Tell the server where to serve content from. This is only necessary if you want to serve static files.
NOTE: it is recommended to use an absolute path. It is also possible to serve contents from multiple directories
For our project architecture, we want all our static images to be stored in dist/assets/media
directory
contentBase: path.resolve(__dirname, "dist/assets/media")
#2 devServer.stats
This option lets you precisely control what bundle information to be displayed. To show only errors in your bundle:
stats: 'errors-only'
for other stats
optionsย : https://webpack.js.org/configuration/stats
#3 devServer.open
If you want dev-server to open the app at the first time in our browser and just refresh afterwards while we change our code
open: true
#4 devServer.port
Mention which port number you want your application to be deployed in your webpack-dev-server
port: 12000
#5 devServer.compress
Enable gzip compression for everything served
compress: true
Finally our devServer configuration looks like:
devServer: {
contentBase: path.resolve(__dirname, "./dist/assets/media"),
compress: true,
port: 12000,
stats: 'errors-only',
open: true
}
#
5. DEVTOOL CONFIGURATION
#
This option controls if and how source maps are generated. With this feature, we know exactly where to look in order to fix/debug issues in our application. Very very useful for development purpose, but should NOT use in production.
devtool: 'inline-source-map'
There are much more options for devtool here
Weโve setup most of the things thatโs required for the first moment to configure webpack. Hereโs the updated snippet of what weโve done so far..
And our package.json looks like:
NOTE: Itโs worth to mention that at the time of writing this article I was on webpack 3.6.0
and webpack-dev-server 2.9.1
version. Your version number may differ than that of mine.
#
6. LOADERS AND PLUGINS
#
Weโve come so far. Now the fun part begins. Weโre actually going to explore what webpack can do by itself through some configurations.
Webpack enables use of loaders to pre-process files. This allows you to bundle any static resource way beyond JavaScript. Since webpack still doesnโt know what to do with these loaders, webpack config object use module
property to know what loader to work and how to execute them.
module
property of config
object itself is an object. It works with some extra options mentioned below:
# module.noParse
Prevent webpack from parsing any files matching the given RegExp
. Ignored files should not have calls to import, require, define or any other importing mechanism. This can boost build performance when ignoring large libraries.
module: {
noParse: /jquery|lodash/
}
# module.rules
It takes every loaders as a set of rules inside an array
. Whereas every element of that array is an object containing individual loaders and their respective configurations.
From Webpackโs documentation
A Rule can be separated into three partsโโโConditions, Results and Nested Rules.
1. Conditions: There are two input values for the conditions
ย a. The resource: An absolute path to the file requested.
ย b. The issuer: The location of the import.
In a Rule the properties test
, include
, exclude
and resource
are matched with the resource and the property issuer
is matched with the issuer.
2. Results: Rule results are used only when the Rule condition matches. There are two output values of a rule:
ย a. Applied loaders: An array of loaders applied to the resource.
ย b. Parser options: An options object which should be used to create the parser for this module.
3. Nested Rules: Nested rules can be specified under the properties rules
and oneOf
. These rules are evaluated when the Rule condition matches.
Okay! Letโs simplify them, since webpack doc always confuses us ๐๐
A loader needs some additional information to work correctly and efficiently in a module. We mention them in module.rules
with some configuration parameters stated below:
test:
(required) A loader needs to know which file extension itโs going to work with. We give the name with the help of RegExp
test: /\.js$/
include:
(optional) A loader needs a directory to locate where itโs working files are stored.
include: /src/
exclude:
(optional) We can save a lot of unwanted process likeโโโwe donโt want to parse modules inside node_modules
directory and can save a lot of memory and execution time
exclude: /node_modules/
use:
(required) A rule must have a loader
property being a string. Mention the loaders you want to use in for that particular task. Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured).
It can have a options
property being a string or object. This value is passed to the loader, which should interpret it as loader options.
ย For compatibility a query
property is also possible, which is an alias for the options property. Use the options
property instead.
use: {
loader: "babel-loader",
options: {
presets: ['env']
}
}
Detail configuration setup for module.rules
: https://webpack.js.org/configuration/module/#module-rules
The plugins
option is used to customize the webpack build process in a variety of ways. webpack comes with a variety of built-in plugins available under webpack.[plugin-name]
Webpack has a plugin configuration setup in itโs config object with plugins
property.
NOTE: Every plugin needs to create an instance to be used into configย object.
Weโre going to see them in action now!
#
7. SOME LOADERS AND PLUGINS IN ACTION
#
Every time we want to see our production ready dist
folder, we need to delete the previous one. Such a pain! clean-webpack-plugin
is to remove/clean your build folder(s) before building. Itโs very easy to setup:
Install via npm
> npm i -D clean-webpack-plugin
Import into your webpack.config.js
file
const CleanWebpackPlugin = require('clean-webpack-plugin');
Now, weโre going to use a plugin for the first time. Webpack has a plugin configuration setup in itโs config
object. Every plugin needs to create an instance to be used into config object. So, in our plugins property:
plugins: [
new CleanWebpackPlugin(['dist'])
]
Here, in the instance of clean-webpack-plugin
we mention dist
as an array element. Webpack now knows that we want to clean/remove dist
folder every time before building our bundle. This instance can have multiple directory/path as array elements and multiple options as object.
Syntax for clean-webpack-plugin usage:
plugins: [
new CleanWebpackPlugin(paths [, {options}])
]
You should watch the process of removing and creating your dist folder live in your directory and in your IDE too..
Reference doc: GitHub Doc
Until now our webpack.config.js
looks like:
We all want to write some ES2015/ES6 code, right? But until now our browser is not fully adopted to ES6 syntax, so we need to at first transpile our ES6 code to ES5 and then we can use it in our production bundle. Babel is taking that responsibility for us. We just need to include babel-loader
into our configuration through some easy steps.
Install babel-loader
> npm i -D babel-loader babel-core
Createย .babelrc
file in our project root to enable some babel-presets (Weโve already done it in project setup section. If you follow along with me, youโll find aย .babelrc
file in your project root directory already)
Install babel-preset-env
to use for environment dependent compilation
> npm i -D babel-preset-env
In order to enable the preset you have to define it in yourย .babelrc
file, like this:
{
"presets": ["env"]
}
NOTE: If we add this into our package.json > โscriptsโ
, then theย .babelrc
file is not needed anymore.
"scripts": {
"babel": {
"presets": ["env"]
}
}
Include rule into the config file module
module: {
rules: [
{
test: /\.js$/,
include: /src/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['env']
}
}
}
]
}
From our loader section we know that:test
ย : to let the loader know which file format itโs going to work oninclude
ย : to let the loader know which directory it should work intoexclude
ย : to let the loader know which directory should it avoid while parsinguse
ย : to let the loader know which specific loader itโs using with use.loader
and whatโs itโs configuration options with use.options
Since we want to edit our index.html
in src
directory and want to see the changes in the output dist
folder, we need to create and update index.html into dist every time webpack compile our project. Well, we should remove that painful job!
We need to use a loader and a plugin together to solve our problem. Because -html-loader
ย : Exports HTML as string. HTML is minimized when the compiler demands.html-webpack-plugin
ย : Simplifies creation of HTML files to serve your webpack bundles.
Install dependencies
> npm i -D html-loader html-webpack-plugin
Configuring html-loader
{ test: /\.html$/, use: ['html-loader']
Importing html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
Using our plugin
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
Now, weโve setup every thing. In your src/index.html
write something and then run some build commandsnpm run dev
ย : Youโll see our app now works and you can see the html element in the browser. The bundled js file has been injected into the html before the end of body tagnpm run build:prod
ย : Watch yourself the process of building the output index.html and the changes applied in the dist/index.html
Resources:
html-loaderย : webpack-docย , github-doc
html-webpack-pluginย : webpack-docย , github-doc
Using CSS and SASS with webpack may look like some extra headaches with some extra steps. Webpack compiles css and pushes the code into the bundled js. But we need to extract it from the bundle, and then create a identicalย .css file, and then push it into our dist/index.html
and then add the css to DOM. A lot of work, right? Not literally..
Iโve combined 3 loaders and 1 plugin to see them work together for our required output:style-loader
ย : Adds CSS to the DOM by injecting a <style>
tagcss-loader
ย : Interprets @import
and url()
like import/require() and will resolve them (stay on the article, donโt go to twitterโs โ@ importโ. mediumโs mistake, not mine ๐)sass-loader
ย : Loads a SASS/SCSS file and compiles it to CSSnode-sass
ย : Provides binding for Node.js to LibSass. The sass-loader requires node-sass and webpack as peerDependency. Thus you are able to control the versions accurately.extract-text-webpack-plugin
ย : Extract text from a bundle, or bundles, into a separate file
Now install the dependencies
> npm i -D sass-loader node-sass css-loader style-loader extract-text-webpack-plugin
We need to import our app.scss
into our app.js
to work to let webpack know about dependencies. So in our app.js
weโre going to write:
import './assets/scss/app.scss';
Additionally to check that our sass modules work fine, add one moreย .scss file in our src/assets/scss
directory via terminal
> touch src/assets/scss/_colors.scss
Import the newly created _color.scss
file into app.scss
and add some styling with:
@import '_colors';
body {
background: $bgcolor;
}
And define $bgcolor into _color.scss file:
$bgcolor : #e2e2e2;
Import extract-text-webpack-plugin
into config file
const ExtractTextPlugin = require('extract-text-webpack-plugin');
We need to import webpack
itself into our webpack.config.js
to use the webpackโs built-in plugins into our project
const webpack = require('webpack');
Now our workflow splits into two ways for two type of requirements.
(1) We want to have just a singleย .css file into our output
(2) We want more than oneย .css files as output
For a single stylesheet (weโre working on this):
First, we need to create an instance of ExtractTextPlugin
into which weโll define our output filename:
const extractPlugin = new ExtractTextPlugin({
filename: './assets/css/app.css'
});
Secondly, while configuring our css/sass loaders we need to use the previously created instance with itโs extract()
method (i.e. extractPlugin.extract()
) and inside the method we pass the required loaders as an argument and in a form of an object
So our configuration of these loaders is going to be:
{
test: /\.scss$/,
include: [path.resolve(__dirname, 'src', 'assets', 'scss')],
use: extractPlugin.extract({
use: ['css-loader', 'sass-loader'],
fallback: 'style-loader'
})
}
And now add the instance of ExtractTextPlugin
(which is extractPlugin
) into the plugins
section:
plugins: [
extractPlugin
]
NOTE: If you are not using any html-loader
then, include a index.html file in the dist folder and link the output stylesheet and js urls into that file like:
<link rel="stylesheet" href="./assets/app.css">
<script src="./assets/main.bundle.js"></script>
For multiple stylesheets (just for giving you example):
If youโre following the previous direction for creating a single extracted stylesheet, then thereโs nothing new for creating multiple stylesheets.
Just create as much instances of ExtractTextPlugin
as you want your stylesheets to have. Weโre creating here two instances, one for just css and one for sass compiled
const extractCSS = new ExtractTextPlugin('./assets/css/[name]-one.css');
const extractSASS = new ExtractTextPlugin('./assets/css/[name]-two.css');
NOTE: ExtractTextPlugin
generates a file per entry, so you must use [name], [id] or [contenthash] when using multiple entries.
Now our loaders configuration looks like:
{ test: /\.css$/, use: extractCSS.extract([ 'css-loader', 'style-loader' ]) },
{ test: /\.scss$/, use: extractSASS.extract([ 'css-loader', 'sass-loader' ], fallback: 'style-loader') }
Now add the instances into the plugins:
plugins: [
extractCSS,
extractSASS
]
Now run the npm run dev
to see it action in your browser.
If youโre working for single stylesheet like mine, you should see that the background changes to #e2e2e2
๐
If you see the view-source from the output app, you can see the stylesheet injected into the head
of our html and app.bundle.js
file injected to before the end of the body
tag
Now What?? Well, we need to debug things from our browser, right? We just need a source map to see the actual line number from the source code rather than being lost into the minified stylesheets!
In the options property for loader, you can switch ON the source map with sourceMap: true
{
test: /\.scss$/,
include: [path.resolve(__dirname, 'src', 'assets', 'scss')],
use: extractPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
],
fallback: 'style-loader'
})
}
Check your styling changes and inspect it in your browser with inspect element. Youโll find the actual line number showing where you made your changes!
Hereโs my configuration:
References:
extract-text-webpack-pluginย : webpackย , github
css-loaderย : webpackย , github
sass-loaderย : webpackย , github
style-loaderย : webpackย , github
Well, weโve setup configuration for every thing except static files like images, fonts. Now, weโre going to setup for static files with very useful loader file-loader
Install file-loader
> npm i -D file-loader
Configuring file-loader
{
test: /\.(jpg|png|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/media/',
publicPath: './assets/media/'
}
}
]
}
NOTE: BE VERY VERY CAREFUL ABOUT outputPath
and publicPath
in the file-loader configuration. You need to add a โ/โ at the end of the outputPath
, so that it will get the directory address rather than concatenating the specified string with the file name. VERY CAREFUL! And we donโt need to add publicPath
(I guess), since weโve already defined it in our output path
//file-loader(for images)
{
test: /\.(jpg|png|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './assets/media/'
}
}
]
},
//file-loader(for fonts)
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader']
}
With the loader configured and fonts in place, you can import them via an @font-face
declaration. The local url(โฆ)
directive will be picked up by webpack just as it was with the image
Adding font-face to stylesheet
@font-face {
font-family: 'MyFont';
src: url('./my-font.woff2') format('woff2'),
url('./my-font.woff') format('woff');
font-weight: 600;
font-style: normal;
}
Now add a sample image in your src/assets/media
directory and create an img
element into your src/index.html
to see it works
Run npm run dev
and npm run build:prod
to see everythingโs working fine and clear.
Thatโs it! Now you should find everything in place and working just fine.ย :)
We have come to the end of this guide, thatโs not everything about webpack though. But itโs the starting point that you have to understand deeply and correctly to get the the working procedure of webpack philosophy. In the upcoming blogs, Iโll try to simplify the actual powers/features of webpack like - Hot Module Replacement, Tree-shaking, Output management for development and production environment, Code splitting, Lazy loading and other stuffs.
And I highly recommend to follow this awesome article rajaraodv wrote to save our livesย : https://medium.com/@rajaraodv/webpack-the-confusing-parts-58712f8fcad9
You can simply clone this repo in your project root to get everything in place or follow the next steps to setup your project in just 5 minutes
https://github.com/postNirjhor/webpack-boilerplate
Donโt hesitate to fork on this repo. Iโd love to see others contributing on this boilerplate to make this configuration more robust and usable for everyone who has suffered a lot likeย me..
Cheat-sheet
> mkdir project_name && cd project_name
> npm init
> mkdir src dist src/assets src/assets/media src/assets/js src/assets/scss
> touch webpack.config.js README.md .babelrc src/index.html src/app.js src/assets/scss/app.scss
> npm i -D webpack
> npm i -D webpack-dev-server clean-webpack-plugin babel-loader babel-core babel-preset-env html-loader html-webpack-plugin sass-loader node-sass css-loader style-loader extract-text-webpack-plugin file-loader
{
"presets": ["env"]
}
"scripts": {
"build": "./node_modules/.bin/webpack",
"build:prod": "./node_modules/.bin/webpack -p",
"watch": "./node_modules/.bin/webpack --watch",
"dev": "./node_modules/.bin/webpack-dev-server"
}
import './assets/scss/app.scss';
src/index.html
(with an image too) and src/assets/scss/app.scss and add the image into src/assets/media๐๐๐๐ End of this long exhaustive guideย ๐๐๐๐
If you find something thatโs not right in this guide, mention it into the comment section. Iโd love to get your feedback on this write up.
If you like it, give some ๐ ๐ and share it on medium and twitter.
Thank You for your patience ๐
Create your free account to unlock your custom reading experience.