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 Configuring package.json 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 Devtool Configuration 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! 1.1. Create project folder and land on the project root (via terminal) > mkdir project_name && cd project_name 1.2. Initialize a project via npm > npm init 1.3. Create project directories > mkdir src dist src/assets src/assets/media src/assets/js src/assets/scss 1.4. Populating our project directories > 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 1.5. Installing webpack Install webpack > npm i -D webpack is just the shortcut of npm i -D webpack 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 scripts: package.json "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 would be: package.json "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 Let’s make some configurations into to make webpack a li’l bit educated 😃 webpack.config.js #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. 3.1. Exporting our config object 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; 3.2. Define entry point(s) Single entry point: 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',}; Multiple entry points: If your app has multiple entry points (like: multi-page application), then, you have to define your entry points inside object with identical names. Multiple entry points are called and the properties (individual entry point) of this entry object are called . So, let’s create it: entry chunks entryChunkName const config = {entry: {app: './src/app.js',vendors: './src/vendors.js'}} Look carefully, our property is not a anymore, it’s now a pure with different entries being the properties of it. entry string javascript object This is so helpful when we want to separate our app entry and vendor (like: jQuery/lodash) entry into different bundles. 3.3. Define output point 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 . This object must include these two following things at least: 1. (to use our output files) 2. (an absolute path to preferred output directory) object filename path We can define 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 module which gives us the absolute path for our project root in a more convenient way. path path To use node.js module we need to import it in our config file and then use it in our output object path const config = {output: {filename: 'bundle.js',// Output path using nodeJs path modulepath: path.resolve(__dirname, 'dist')}}; You can use or , 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 and works, but giving you the resources: path.join path.resolve path.join path.resolve path.resolve resource : path.join resource : here 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_ 3.4. Define Context 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. 4.1. Installing webpack-dev-server Install webpack-dev-server via terminal > npm i -D webpack-dev-server 4.2. Adding command to package.json Add this command to in scripts package.json "dev": "./node_modules/.bin/webpack-dev-server" 4.3. Configuring webpack-dev-server There are so many configuration options for . We’re going to look for some important ones. webpack-dev-server In your object let’s create a new property named (syntax is important) config devServer 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 directory dist/assets/media 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 options : stats 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 use in production. NOT 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. 6.1. Loaders 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 property to know what loader to work and how to execute them. module property of object itself is an object. It works with some extra options mentioned below: module config **# module.noParse** Prevent webpack from parsing any files matching the given . Ignored files should not have calls to import, require, define or any other importing mechanism. This can boost build performance when ignoring large libraries. RegExp module: {noParse: /jquery|lodash/} **# module.rules** It takes every loaders as a set of rules inside an . Whereas every element of that array is an object containing individual loaders and their respective configurations. array From Webpack’s documentation A Rule can be separated into three parts — Conditions, Results and Nested Rules. 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. 1. Conditions: In a Rule the properties , , and are matched with the resource and the property is matched with the issuer. test include exclude resource issuer 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. 2. Results: Nested rules can be specified under the properties and . These rules are evaluated when the Rule condition matches. 3. Nested Rules: rules oneOf 😖😓 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 with some configuration parameters stated below: module.rules (required) A loader needs to know which file extension it’s going to work with. We give the name with the help of **_test:_** RegExp test: /\.js$/ (optional) A loader needs a directory to locate where it’s working files are stored. **_include:_** include: /src/ (optional) We can save a lot of unwanted process like — we don’t want to parse modules inside directory and can save a lot of memory and execution time **_exclude:_** node_modules exclude: /node_modules/ (required) A rule must have a 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 (last to first configured).It can have a property being a string or object. This value is passed to the loader, which should interpret it as loader options. For compatibility a property is also possible, which is an alias for the options property. Use the property instead. **_use:_** loader right to left options query options use: {loader: "babel-loader",options: {presets: ['env']}} Detail configuration setup for : module.rules https://webpack.js.org/configuration/module/#module-rules 6.2. Plugins The 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 plugins webpack.[plugin-name] Webpack has a plugin configuration setup in it’s config object with property. plugins 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# 7.1. clean-webpack-plugin (PLUGIN) Every time we want to see our production ready folder, we need to delete the previous one. Such a pain! is to remove/clean your build folder(s) before building. It’s very easy to setup: dist clean-webpack-plugin Install via npm > npm i -D clean-webpack-plugin Import into your file webpack.config.js 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 object. So, in our plugins property: config Every plugin needs to create an instance to be used into config object. plugins: [new CleanWebpackPlugin(['dist'])] Here, in the instance of we mention as an array element. Webpack now knows that we want to clean/remove folder every time before building our bundle. This instance can have multiple directory/path as array elements and multiple options as object. clean-webpack-plugin dist dist 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 looks like: webpack.config.js Webpack config file basic setup — 3 7.2. babel-loader (LOADER) 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. is taking that responsibility for us. We just need to include into our configuration through some easy steps. Babel babel-loader Install babel-loader > npm i -D babel-loader babel-core Create 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 file in your project root directory already) .babelrc .babelrc Install to use for environment dependent compilation babel-preset-env > npm i -D babel-preset-env In order to enable the preset you have to define it in your file, like this: .babelrc {"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: : to let the loader know which file format it’s going to work on : to let the loader know which directory it should work into : to let the loader know which directory should it avoid while parsing : to let the loader know which specific loader it’s using with and what’s it’s configuration options with test include exclude use use.loader use.options babel-loader_config Webpack config file basic setup — 4 Configuring .babelrc file 7.3. html-loader (LOADER) & html-webpack-plugin (PLUGIN) Since we want to edit our in directory and want to see the changes in the output folder, we need to create and update index.html into dist every time webpack compile our project. Well, we should remove that painful job! index.html src dist We need to use a loader and a plugin together to solve our problem. Because - : Exports HTML as string. HTML is minimized when the compiler demands. : Simplifies creation of HTML files to serve your webpack bundles. html-loader html-webpack-plugin 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 write something and then run some build commands : 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 tag : Watch yourself the process of building the output index.html and the changes applied in the dist/index.html src/index.html npm run dev npm run build:prod Resources:html-loader : , html-webpack-plugin : , webpack-doc github-doc webpack-doc github-doc 7.4. css-loader (LOADER), style-loader (LOADER), sass-loader (LOADER), extract-text-webpack-plugin (PLUGIN) 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 and then add the css to DOM. A lot of work, right? Not literally.. dist/index.html I’ve combined 3 loaders and 1 plugin to see them work together for our required output: : Adds CSS to the DOM by injecting a tag : Interprets and like import/require() and will resolve them (stay on the article, don’t go to twitter’s “@ import”. medium’s mistake, not mine 😒) : Loads a SASS/SCSS file and compiles it to CSS : 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 from a bundle, or bundles, into a separate file style-loader <style> css-loader [@](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") url() sass-loader node-sass extract-text-webpack-plugin Now install the dependencies > npm i -D sass-loader node-sass css-loader style-loader extract-text-webpack-plugin We need to import our into our to work to let webpack know about dependencies. So in our we’re going to write: app.scss app.js app.js import './assets/scss/app.scss'; Additionally to check that our sass modules work fine, add one more .scss file in our directory via src/assets/scss terminal > touch src/assets/scss/_colors.scss Import the newly created file into and add some styling with: _color.scss app.scss '_colors';body {background: $bgcolor;} @import And define $bgcolor into _color.scss file: $bgcolor : #e2e2e2; Import into config file extract-text-webpack-plugin const ExtractTextPlugin = require('extract-text-webpack-plugin'); We need to import itself into our to use the webpack’s built-in plugins into our project webpack webpack.config.js 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 First, we need to create an instance of into which we’ll define our output filename: For a single stylesheet (we’re working on this): ExtractTextPlugin 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 method (i.e. ) and inside the method we pass the required loaders as an argument and in a form of an object extract() extractPlugin.extract() 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 (which is ) into the section: ExtractTextPlugin extractPlugin plugins 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 as you want your stylesheets to have. We’re creating here two instances, one for just css and one for sass compiled ExtractTextPlugin 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 to see it action in your browser. npm run dev 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 of our html and file injected to before the end of the tag head app.bundle.js body 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 : , css-loader : , sass-loader : , style-loader : , webpack github webpack github webpack github webpack github 7.5. file-loader (LOADER) 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 declaration. The local directive will be picked up by webpack just as it was with the image [@font](http://twitter.com/font "Twitter profile for @font")-face url(…) Adding font-face to stylesheet -face {font-family: 'MyFont';src: url('./my-font.woff2') format('woff2'),url('./my-font.woff') format('woff');font-weight: 600;font-style: normal;} @font Now add a sample image in your directory and create an element into your to see it works src/assets/media img src/index.html Webpack config file basic setup — 7 Run and to see everything’s working fine and clear. npm run dev npm run build:prod 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 Since I promised you to give you the simplest cheat-sheet for you. Here it is: 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 1. Run these commands one after another in the terminal to create project folders and install all dependencies > 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 2. Add this snippet into .babelrc {"presets": ["env"]} 3. Configure package.json > scripts with this snippet "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"} 4. Import app.scss into your app.js import './assets/scss/app.scss'; 5. Populate your (with an image too) and src/assets/scss/app.scss and add the image into src/assets/media src/index.html 6. Copy and paste this configuration file into your webpack.config.js (make sure that project folders hierarchy to be same) Webpack Quickstarter Template — Final 7. Run npm scripts to see your application in action in browser and in production format. Don’t forget to inspect element in the browser console to make sure everything’s working great without any error. 🎉🎉🎉🎉 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 🙇
Share Your Thoughts