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 ->
Project Setup1.1. Create project folder and land on the project root (via terminal)1.2. Initialize a project via npm1.3. Create project directories1.4. Populating our project directories1.5. Installing webpack
Land on the webpack config file3.1. Exporting our config object3.2. Define entry point(s)3.3. Define output point3.4. Define Context
Setting up webpack-dev-server4.1. Installing webpack-dev-server4.2. Adding command to package.json4.3. Configuring webpack-dev-server
Loaders and plugins6.1. Loaders6.2. Plugins
Some loaders and plugins in action7.1. clean-webpack-plugin (PLUGIN)7.2. babel-loader (LOADER)7.3. html-loader (LOADER) & html-webpack-plugin (PLUGIN)7.4. css-loader (LOADER), style-loader (LOADER), sass-loader (LOADER), extract-text-webpack-plugin (PLUGIN)7.5. file-loader (LOADER)
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 responsibilities_README.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:
project_architecture
#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
webpack_destination
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, our
entry
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 modulepath: 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 : herepath.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:
Webpack config file basic setup — 1
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..
Webpack config file basic setup — 2
And our package.json looks like:
package.json file for webpack quickstart
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:
Webpack config file basic setup — 3
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
babel-loader_config
Webpack config file basic setup — 4
Configuring .babelrc file
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'})]
Webpack config file basic setup — 5
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-dochtml-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 [@](http://twitter.com/import "Twitter profile for @import")[i](http://twitter.com/import "Twitter profile for @import")[mport](http://twitter.com/import "Twitter profile for @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:
Webpack config file basic setup — 6
References:extract-text-webpack-plugin : webpack , githubcss-loader : webpack , githubsass-loader : webpack , githubstyle-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](http://twitter.com/font "Twitter profile for @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
Webpack config file basic setup — 7
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
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
Webpack Quickstarter Template — Final
🎉🎉🎉🎉 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 🙇