Tutorial

Setting up webpack for Any Project

Draft updated on Invalid Date
    author

    O'Brian Kimo

    Setting up webpack for Any Project

    This tutorial is out of date and no longer maintained.

    Introduction

    Most developers have interacted with webpack while creating React projects and most see it as a tool for use in developing React projects rather than a general development tool.

    webpack is a powerful module bundler that can be very efficient if used correctly.

    In this tutorial, we will explore how to set up a project using webpack right from the folder structure to exploring different loaders, plugins and other interesting features that come with webpack. This will give you a different perspective on webpack and you will help in setting up future Javascript projects using webpack.

    Why webpack?

    An alternative to using webpack is using a combination of a task runner like grunt or gulp with a bundler like browserify. But what makes a developer opt for webpack rather than use the task runners?

    Webpack attacks the build problem in a fundamentally more integrated and opinionated manner. In browserify, you use gulp/grunt and a long list of transforms and plugins to get the job done. webpack offers enough power out of the box that you typically don’t need grunt or gulp at all.

    webpack is also configuration based unlike gulp/grunt where you have to write code to do your tasks. What makes it even better is the fact that it makes correct assumptions about what you want to do; work with different JS modules, compile code, manage assets, and so forth.

    The live-reload ability is also blazing fast. The ability to substitute output filenames with hash filenames enables browsers to easily detect changed files by including build-specific hash in the filename.

    Splitting of file chunks and extracting webpack’s boilerplate and manifest also contributes to fast rebuilds. These are just a few highlights out of many that make webpack a better choice.

    webpack Features

    The main webpack features which we will discuss further include:

    • Loaders
    • Plugins
    • Use of different configurations for different environments
    • Lazy loading of split chunks
    • Dead code elimination by tree shaking
    • Hot module replacement that allows code to be updated at runtime without the need for a full refresh
    • Caching by substituting filenames with hash filenames

    Project Setup

    Prerequisites

    To continue with this tutorial we will require Node JS on our machines which come bundled with the npm. We will then install yarn which is an alternative to npm that gives us additional functionalities and more speed during the installation of packages.

    1. npm install -g yarn

    Directory Structure

    We will begin by creating the following directory structure:

    Inside our main directory webpack-setup, we initialize our project with yarn init which will create for us package.json file. We will briefly explore what some of the directories and files will be used for.

    • src: Main project container.
    • src/app: Will host our javascript files.
    • src/public: Holds project assets and static files.
    • src/style: Holds the project’s global styles.
    • src/app/index.js: Main entry point into our project.
    • src/public/index.html: Main project template.

    Initial Configuration

    We will start by creating a simple webpack configuration that we will gradually develop by adding more functionality. This simple configuration will only contain one very important plugin, HtmlWebpackPlugin.

    The HtmlWebpackPlugin simplifies the creation of HTML files to serve your webpack bundles and can automatically inject our javascript bundle into our main HTML template. But before that, we will need to install some required modules; webpack which is the main bundler, and webpack-dev-server that provides a simple lightweight server for development purposes.

    1. yarn add webpack webpack-dev-server html-webpack-plugin -D

    The initial configuration presents a skeleton of our project with the following parts. You may follow the links to explore the different options available.

    • entry - Indicates which module webpack should use to begin building out its internal dependency graph.
    • output - Tells webpack where to emit the bundles it creates and how to name these files.
    • devServer - Set of options to be used by webpack-dev-server.

    We will explore more on plugins and loaders(module section).

    const HtmlWebpackPlugin = require('html-webpack-plugin'); // Require  html-webpack-plugin plugin
    
    module.exports = {
      entry: __dirname + "/src/app/index.js", // webpack entry point. Module to start building dependency graph
      output: {
        path: __dirname + '/dist', // Folder to store generated bundle
        filename: 'bundle.js',  // Name of generated bundle after build
        publicPath: '/' // public URL of the output directory when referenced in a browser
      },
      module: {  // where we defined file patterns and their loaders
          rules: [
          ]
      },
      plugins: [  // Array of plugins to apply to build chunk
          new HtmlWebpackPlugin({
              template: __dirname + "/src/public/index.html",
              inject: 'body'
          })
      ],
      devServer: {  // configuration for webpack-dev-server
          contentBase: './src/public',  //source of static assets
          port: 7700, // port to run dev-server
      }
    };
    

    HtmlWebpackPlugin basically informs webpack to include our javascript bundle in the body element of the provided template file. We will then add a simple statement in src/app/index.js and also populate our src/public/index.html file with simple HTML for demonstration. We then update package.json script with a start script.

    "scripts": {
        "start": "webpack-dev-server --history-api-fallback --inline --progress"
      }
    

    The above script will enable our server to server index.html in case of a 404 error and the --inline option allows for injection of a Hot Module Replacement script in our bundle while the --progres option simply shows console outputs of the running tasks. We can then start our application with:

    1. yarn start

    Looking at our console, we find the following logs which basically explain the devServer section.

    We can then navigate to http://localhost:7700/ to see our application.

    Loaders

    Loaders are special modules webpack uses to ‘load’ other modules (written in another language) into Javascript. They allow us to pre-process files as we import or “load” them.

    Thus, loaders are kind of like “tasks” in other build tools, and provide a powerful way to handle front-end build steps.

    Loaders can transform files from a different language (like TypeScript) to JavaScript, or sass to css. They can even allow us to do things like import CSS and HTML files directly into our JavaScript modules. Specifying loaders in our configuration’s module.rules section is the recommended way of using them.

    babel-loader

    This loader uses Babel to load ES2015 files. We install babel-core which is the actual babel used by babel-loader. We also include babel-preset-env; a preset that compiles ES2015+ down to ES5 by automatically determining the Babel plugins and polyfills you need based on your targeted browser or runtime environments.

    1. yarn add babel-core babel-loader babel-preset-env -D

    We then create a .babelrc file where we include the presets.

    .babelrc
    { "presets": [ "env" ] }
    

    We can now finally include our loader in our configuration to transform Javascript files. This will now allow us to use ES2015+ syntax in our code.

    Configuration

    webpack.config.js
    ...
    module: {
          rules: [
              {
                test: /\.js$/,
                use: 'babel-loader',
                exclude: [
                  /node_modules/
                ]
              }
          ]
      }
    ...
    

    Test Case

    src/app/index.js
    class TestClass {
        constructor() {
            let msg = "Using ES2015+ syntax";
            console.log(msg);
        }
    }
    
    let test = new TestClass();
    

    The above snippet results to the following in our browser console:

    This is a very common loader. We will further demonstrate a few loaders with popular frameworks including Angular(1.5+) and React.

    raw-loader

    It is a loader that lets us import files as a string. We will show this by importing an HTML template to use for an angular component.

    Configuration

    webpack.config.js
    ...
    module: {
          rules: [
             ...,
              {
                  test: /\.html/,
                  loader: 'raw-loader'
              }
          ]
      },
      ...
    

    Use

    src/app/index.js
    import angular from 'angular';
    import template from './index.tpl.html';
    
    let component = {
        template // Use ES6 enhanced object literals.
    }
    
    let app = angular.module('app', [])
        .component('app', component)
    

    We could alternatively use template: require('./index.tpl.html' instead of the import statement and have a simple HTML file.

    src/app/index.tpl.html
    <h3>Test raw-loader for angular component</h3>
    

    sass-loader

    The sass-loader helps us to use scss styling in our application. It requires node-sass which allows us to natively compile .scss files to CSS at incredible speed and automatically via a connect middleware. It is recommended to use it together with css-loader to turn it into a JS module and style-loader that will add CSS to the DOM by injecting a style tag.

    1. yarn add sass-loader node-sass css-loader style-loader -D

    Configuration

    webpack.config.js
    ...
    module: {
          rules: [
             ...,
              {
                test: /\.(sass|scss)$/,
                use: [{
                    loader: "style-loader" // creates style nodes from JS strings
                }, {
                    loader: "css-loader" // translates CSS into CommonJS
                }, {
                    loader: "sass-loader" // compiles Sass to CSS
                }]
              }
          ]
      },
      ...
    
    src/style/app.scss
    $primary-color: #2e878a;
    
    body {
        color: $primary-color;
    }
    

    Use

    We simply import it in the template as follows and the styling will kick in.

    src/app/app.js
    ...
    import '../style/app.scss';
    ...
    

    So far those are enough loaders to guide us in the right direction on how to add other loaders.

    Plugins

    Plugins are the backbone of webpack and serve the purpose of doing anything else that a loader cannot do.

    Loaders do the pre-processing transformation of any file format when you use them; they work at the individual file level during or before the bundle is generated. On the other hand, plugins are quite simple since they expose only one single function to webpack and are not able to influence the actual build process.

    Plugins work at bundle or chunk level and usually work at the end of the bundle generation process.

    Plugins can also modify how the bundles themselves are created and have more powerful control than loaders. The figure below illustrates where loaders and plugins operate.

    We have already used html-webpack-plugin and we will demonstrate how to use some more common plugins in our project.

    extract-text-webpack-plugin

    Extracts text from a bundle, or bundles, into a separate file. This is very important in ensuring that when we build our application, the CSS is extracted from the Javascript files into a separate file. It moves all the required CSS modules in entry chunks into a separate CSS file. Our styles will no longer be inlined into the JS bundle, but in a separate CSS file (styles.css). If our total stylesheet volume is big, it will be faster because the CSS bundle is loaded in parallel to the JS bundle.

    1. yarn add extract-text-webpack-plugin -D

    Configuration

    [label webpack.config.js}
    var ExtractTextPlugin = require('extract-text-webpack-plugin');
    ...
    {
      test: /\.css$/,
      use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: [
        { loader: 'css-loader'},
        { loader: 'sass-loader'}
      ],
      })
    },
    plugins: [
        new ExtractTextPlugin("styles.css"), // extract css to a separate file called styles.css
      ]
    ...
    

    DefinePlugin

    The DefinePlugin allows you to create global constants which can be configured at compile time. This can easily be used to manage import configurations like API keys and other constants that can be changed easily. The best way to use this plugin is to create a .env file with different constants and access them in our configuration using dotenv package then we can directly refer to these constants in our code.

    1. yarn add dotenv -D

    We can then create a simple environmental variable in our .env file.

    .env
    API_KEY=1234567890
    

    Configuration

    ...
    require('dotenv').config()
    ...
    plugins: [
      new webpack.DefinePlugin({  // plugin to define global constants
              API_KEY: JSON.stringify(process.env.API_KEY)
          })
    ]
    

    webpack-dashboard

    This is a rarely used CLI dashboard for your webpack-dev-server. The plugin introduces “beauty and order” in our development environment and instead of the normal console logs, we get to see an attractive easy to interpret dashboard.

    Installation

    1. yarn add webpack-dashboard -D

    Configuration

    webpack.config.js
    ...
    const DashboardPlugin = require('webpack-dashboard/plugin');
    ...
    plugins: [
          new DashboardPlugin()
      ],
    ...
    

    We then edit our start script to use the plugin.

    package.json
    ...
    "scripts": {
        "start": "webpack-dashboard -- webpack-dev-server --history-api-fallback --inline --progress"
      }
    ...
    

    After running our application, we see a very nice interface.

    Development Environments

    In this last section, we focus on how we can use webpack to manage different environment configurations. This will also include the use of some plugins depending on the environment which can either be testing, development, staging, or production depending on the provided environmental variables. We will rely on dotenv package to get our environment. Some of the things that can vary between these environments include devtool and plugins like extract-text-webpack-plugin, UglifyJsPlugin, and copy-webpack-plugin among others.

    • devtool- Controls if and how source maps are generated.
    • copy-webpack-plugin - Copies individual files or entire directories to the build directory. This is recommended for production to copy all assets to the output folder.
    • uglifyjs-webpack-plugin - Used to minify our Javascript bundle. Recommended to be used in production to reduce the size of our final build.

    Installation

    1. yarn add copy-webpack-plugin uglifyjs-webpack-plugin -D

    Configuration

    We will alter our configuration a bit to accommodate this functionality. We also remove DashboardPlugin which is known to cause some issues when minifying.

    webpack.config.js
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
    require('dotenv').config()
    
    const ENV = process.env.APP_ENV;
    const isTest = ENV === 'test'
    const isProd = ENV === 'prod';
    
    function setDevTool() {  // function to set dev-tool depending on environment
        if (isTest) {
          return 'inline-source-map';
        } else if (isProd) {
          return 'source-map';
        } else {
          return 'eval-source-map';
        }
    }
    ...
    const config = {
    ...
    devtool: setDevTool(),  //Set the devtool
    ...
    }
    
    // Minify and copy assets in production
    if(isProd) {  // plugins to use in a production environment
        config.plugins.push(
            new UglifyJSPlugin(),  // minify the chunk
            new CopyWebpackPlugin([{  // copy assets to public folder
              from: __dirname + '/src/public'
            }])
        );
    };
    
    module.exports = config;
    

    The difference between the bundle sizes before and after minification is clearly visible. We have managed to trim our code from 1.57MB to 327kB.

    Conclusion

    webpack is definitely a powerful tool for development and is easy to configure once you grasp the few concepts that are applied. Managing multiple configurations for multiple environments can be very cumbersome but webpack-merge provides us with the ability to merge different configurations and avoid the use of if statements for configurations. This article demonstrates just a few of the many different loaders and plugins that make using webpack fun. Feel free to play around with different plugins and frameworks to better understand the power of webpack.

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about our products

    About the authors
    Default avatar
    O'Brian Kimo

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
     
    Leave a comment
    

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Become a contributor for community

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    DigitalOcean Documentation

    Full documentation for every DigitalOcean product.

    Resources for startups and SMBs

    The Wave has everything you need to know about building a business, from raising funding to marketing your product.

    Get our newsletter

    Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

    New accounts only. By submitting your email you agree to our Privacy Policy

    The developer cloud

    Scale up as you grow — whether you're running one virtual machine or ten thousand.

    Get started for free

    Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

    *This promotional offer applies to new accounts only.