Webpack: A Beginner's Guide





Good day, friends!



I present to you the translation of the article "Webpack: A gentle introduction" by Tyler McGinnis.



Before exploring a new technology, ask yourself two questions:



  1. Why is this tool needed?
  2. What tasks does it perform?


If you cannot answer these questions, you may not need the technology you are studying. Let's try to answer these questions in relation to Webpack.



Why do you need a webpack?



Webpack is a module builder. It parses the modules of the application, creates a dependency graph, then assembles the modules in the correct order into one or more bundles, which can be referenced by the "index.html" file.



App.js ->       |
Dashboard.js -> | Bundler | -> bundle.js
About.js ->     |


What problems does webpack solve?



Usually, when creating a JavaScript application, the code is split into several parts (modules). Then, in the "index.html" file, you must specify a link to each script.



<body>

    ...
    
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="libs/react.min.js"></script>
    <script src='src/admin.js'></script>
    <script src='src/dashboard.js'></script>
    <script src='src/api.js'></script>
    <script src='src/auth.js'></script>
    <script src='src/rickastley.js'></script>
</body>


This is not only tedious but also error prone. It is important not only not to forget about any script, but also to arrange them in the correct order. If you load a script that depends on React before loading React itself, your application will break. Webpack solves these problems. You don't have to worry about including all scripts in sequence.



<body>

    ...
    
    <script src='dist/bundle.js'></script>
</body>


As we will soon learn, collecting modules is just one aspect of how a webpack works. If necessary, you can force the webpack to do some module transformations before adding them to the bundle. For example converting SASS / LESS to regular CSS, or modern JavaScript to ES5 for older browsers.



Installing a webpack



After initializing the project with npm, for the webpack to work, you need to install two packages - webpackand webpack-cli.



npm i webpack webpack-cli -D


webpack.config.js



After installing these packages, the webpack needs to be configured. To do this, a file is created webpack.config.jsthat exports the object. This object contains webpack settings.



module.exports = {}


The main task of a webpack is to analyze modules, transform them optionally and intelligently combine them into one or more bundles, so the webpack needs to know three things:



  1. Application entry point
  2. Conversions to be performed
  3. Where to place the generated bundle


Point of entry



No matter how many modules an application contains, there is always a single entry point. This module includes the rest. Typically, this file is index.js. It might look like this:



index.js
  imports about.js
  imports dashboard.js
    imports graph.js
    imports auth.js
      imports api.js


If we tell the webpack the path to this file, it uses it to create the application's dependency graph. To do this, you need to add a property entryto the webpack settings with the value of the path to the main file:



module.exports = {
    entry: './app/index.js'
}


Conversion with loaders



After adding the entry point, you need to inform the webpack about the transformations that need to be performed before generating the bundle. For this, loaders are used.



By default, when generating operator-based dependency graphs, the import / require()webpack is only able to process JavaScript and JSON files.



import auth from './api/auth' // 
import config from './utils/config.json' // 
import './styles.css' // ️
import logo from './assets/logo.svg' // ️


It's unlikely that you dare to limit yourself to JS and JSON files in your application, most likely you will also need styles, SVGs, images, etc. That's where loaders come in. The main task of loaders, as their name suggests, is to provide a webpack with the ability to work with more than just JS and JSON files.



The first step is to install the loader. Since we want to load SVG, use npm to install svg-loader.



npm i svg-inline-loader -D 


Next, add it to the webpack settings. All loaders are included in an array of objects module.rules:



module.exports = {
    entry: './app/index.js',
    module: {
        rules: []
    }
}


Loader information consists of two parts. The first is the type of files being processed ( .svgin our case). The second is the loader used to process this type of file ( svg-inline-loaderin our case).



module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' }
    ]
  }
}


We can now import SVG files. But what about our CSS files? For styles used css-loader.



npm i css-loader -D 


module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: 'css-loader' }
    ]
  }
}


We can now import both SVG and CSS files. However, in order for our styles to work correctly, we need to add another loader. Thanks to css-loaderus, we can import CSS files. But that doesn't mean they will be included in the DOM. We want to not only import such files, but also place them in a tag <style>so that they apply to DOM elements. For this you need style-loader.



npm i style-loader -D 


module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }
    ]
  }
}


Note that since two loaders are used to process CSS files, the property value useis an array. Also pay attention to the order of the loaders, first style-loader, then css-loader. It is important. The webpack will apply them in reverse order. It is used first css-loaderfor imports './styles.css', then style-loaderfor injecting styles into the DOM.



Loaders can be used not only to import files, but also to convert them. The most popular is converting next generation JavaScript to modern JavaScript using Babel. It is used for this babel-loader.



npm i babel-loader -D 


module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /\.(js)$/, use: 'babel-loader' }
    ]
  }
}


There are loaders for almost any type of file.



Exit point



Now the webpack knows about the entry point and loaders. The next step is to specify the directory for the bundle. To do this, you need to add a property outputto the webpack settings.



const path = require('path')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /\.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  }
}


The whole process looks like this:



  1. The webpack receives an entry point located at ./app/index.js
  2. It parses statements import / requireand creates a dependency graph
  3. The webpack starts building the bundle by converting the code using the appropriate loaders
  4. He collects a bundle and places it in dist/index_bundle.js


Plugins



We have seen how to use loaders to process individual files before or during the generation of a bundle. Unlike loaders, plugins allow you to perform tasks after building a bundle. These tasks can concern both the bundle itself and other code. You can think of plugins as more powerful, less limited loaders.



Let's take an example.



HtmlWebpackPlugin



The main task of the webpack is to generate a bundle that can be referenced in index.html.



HtmlWebpackPlugincreates index.htmlin the directory with the bundle and automatically adds a link to the bundle in it.



We named the bundle index_bundle.jsand placed it in dist. HtmlWebpackPluginwill create a new file index.htmlin the directory distand add a link to the bundle in it - <script src='index_bundle.js'></script>. Great, isn't it? Since it is index.htmlgenerated HtmlWebpackPlugin, even if we change the exit point or the name of the bundle, it HtmlWebpackPluginwill receive this information and change the content index.html.



How do we use this plugin? As usual, you need to install it first.



npm i html-webpack-plugin -D 


Next, add the property to the webpack settings plugins.



const path = require('path')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /\.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: []
}


We create an instance HtmlWebpackPluginin an array plugins.




const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /\.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
}


EnvironmentPlugin



If you are using React, you will want to set this process.env.NODE_ENVto a value productionbefore deploying the application. This will allow React to build in production by removing development tools such as warnings. The webpack allows you to do this through a plugin EnvironmentPlugin. It is part of the webpack, so you don't need to install it.



const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /\.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new webpack.EnvironmentPlugin({
      'NODE_ENV': 'production'
    })
  ]
}


Now, anywhere in our application, we can set the production mode with process.env.NODE_ENV.



HtmlWebpackPluginand EnvironmentPluginis just a small part of the webpack plugin system.



Mode (mode)



In the process of preparing an application for production, there are several steps to take. We've just covered one of them - setting process.env.NODE_ENVto value production. Another action is to minify the code and remove comments to reduce the size of the bundle.



There are special plugins for solving these tasks, but there is an easier way. In the webpack settings, it can be set modeto developmentor productiondepending on the development environment.



const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './app/index.js',
  module: {
    rules: [
      { test: /\.svg$/, use: 'svg-inline-loader' },
      { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
      { test: /\.(js)$/, use: 'babel-loader' }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin()
  ],
  mode: 'production'
}


Please note that we have removed EnvironmentPlugin. The fact is that after setting modethe value, the productionwebpack automatically assigns the process.env.NODE_ENVvalue production. It also minifies the code and removes warnings.



Launching a webpack



At the moment we know how the webpack works and how to configure it, it remains to run it.



We have a file package.jsonthat we can create scriptto run webpack.



"scripts": {
  "build": "webpack"
}


Now, when you run the command npm run buildin the terminal, a webpack will be launched, which will create an optimized bundle index_bundle.jsand place it in dist.



Development and production modes



All in all, we are done with the webpack. Finally, let's take a look at how to switch between modes.



When building for production, we want to optimize everything as much as possible. In the case of development mode, the opposite is true.



To switch between modes, you need to create two scripts in package.json.



npm run buildwill build the production bundle.



npm run startwill start the development server and watch for file changes.



If you remember, we set this modeto a value productionin the webpack settings. However, we don't need it now. We want the environment variable to have an appropriate value depending on the command being executed. Let's change the script buildin package.json.



"scripts": {
  "build": "NODE_ENV='production' webpack",
}


If you have Windows, the command would be: "SET NODE_ENV='production' && webpack".



Now in the webpack settings we can change the value modedepending on process.env.NODE_ENV.



...

  mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
}


To build a ready-made bundle for our application, we simply run it npm run buildin the terminal. The directory distfiles are created index.htmland index_bunlde.js.



Development server



When it comes to developing an application, speed is of the essence. We don't want to restart the webpack and wait for a new build with every change. This is where the package comes in handy webpack-dev-server.



As the name suggests, this is a webpack server for development. Instead of creating a directory dist, it stores data in memory and processes it on a local server. What's more, it supports live reboot. This means that any change will webpack-dev-serverrebuild the files and restart the browser.



Install the package.



npm i webpack-dev-server -D 


All that's left to do is add the script startto package.json.



"scripts": {
  "build": "NODE_ENV='production' webpack",
  "start": "webpack-dev-server"
}


We now have two commands: one to start the development server, and the other to build the finished bundle.



I hope this article was helpful to you. Thank you for attention.



All Articles