Webpack

Learn how to build an app using webpack.

We'll cover the following

Packaging an app with webpack

In the beginning, preparing JavaScript for the browser was simple. You would simply put your page’s functionality in a single script and load it with a <script> tag. Then jQuery took off, and your script had a dependency. No problem: jQuery + app = two <script> tags. But then other libraries came around, which depended in turn on other libraries. Using Backbone.js meant you had to load it after Underscore.js. As more app functionality moved into the browser, the once simple act of loading code in the right order became a challenge. And what about concatenating and minifying scripts for performance? What about loading “below-the-fold” scripts asynchronously? What about development mode?

Only a few years ago, bespoke in-house tools were the norm for addressing these problems. But now, at last, there’s a Swiss army knife for building JavaScript: webpack (always lowercase). Raw scripts go into webpack and compiled, concatenated, minified bundles come out. With a little bit of configuration (and unfortunately, sometimes a lot), webpack can generate any bundle you can imagine.

In this section, you’re going to harness the power of webpack to build a small demo app for the carousel component.

  • To get started, open the test-driven-carousel project from the last chapter. Then install webpack as a dev dependency along with webpack-cli and a library called babel-loader that lets webpack use Babel to compile scripts:

    $ npm install --save-dev webpack@4.26.1 webpack-cli@3.1.2 babel-loader@8.0.4
    + webpack@4.26.1
    + webpack-cli@3.1.2
    + babel-loader@8.0.4
    
  • Then create a file called webpack.config.js in the root of the project:

    // webpack.config.js
    module.exports = {
      mode: 'development', // 1
      entry: {
        carousel: './src/Carousel.js', // 2
      },
      module: { // 3
        rules: [
          {
            test: /\.js$/,
            loader: require.resolve('babel-loader'), // 4
          },
        ],
      },
    };
    
  1. Using mode: 'development' provides good defaults for running code locally, optimizing for ease of debugging at the expense of bundle size and runtime performance. By contrast, mode: 'production' would give you a minified bundle.

  2. Each entry point defines a separate bundle for webpack to build. Here we’re telling webpack to build a bundle called carousel.js that contains src/Carousel.js and its dependencies.

  3. The module block is where we tell webpack how to treat different kinds of modules via an array of rules. A “module” in webpack can be any kind of file(JSON, CSS, even images)but we’re only concerned with JS modules, so we have just one rule.

  4. This rule says, “Use babel-loader for all .js files.” The loader will run each script through Babel with the config found in the root of our project.
  • The webpack-cli package added an executable named webpack. When it runs, it looks for a config file to tell it what to do. Although it should find the webpack.config.js in the root of the project by default, make the connection explicit by passing it a --config flag in a new build script:

    // package.json
    ...
    "scripts": {
      "test": "jest",
      "lint": "eslint . && prettier-eslint --list-different **/*.js",
      "format": "prettier-eslint --write **/*.js",
      "build": "webpack --config webpack.config.js"
    },
    ...
    
  • Now try building your project:

    $ npm run build
    Hash: ccc06a7f00b25a1780c4
    Version: webpack 4.26.1
    Time: 619ms
    Built at: 2018-12-02 18:48:29
           Asset     Size     Chunks             Chunk Names
    carousel.js  106 KiB  carousel  [emitted]  carousel
    Entrypoint carousel = carousel.js
    [./src/Carousel.js] 5.31 KiB {carousel} [built]
    [./src/CarouselButton.js] 269 bytes {carousel} [built]
    [./src/CarouselSlide.js] 1.46 KiB {carousel} [built]
        + 11 hidden modules
    

If you look in the dist dir of the project, you’ll see the carousel.js bundle. As the output indicates, that bundle includes Carousel, CarouselButton, and CarouselSlide.

Sharp-eyed readers will notice that the size of the bundle (106 KiB) is many times greater than the total size of the three carousel components. The gap is explained by the “hidden modules” mentioned at the end of the output. By default, webpack is silent regarding modules from node_modules. In this case, those modules are the imports from the react and prop-types packages. If you’re curious to see them, run webpack again with the --display-modules flag.

A bit of housekeeping before we continue. ESLint is unaware that dist/carousel.js is generated code, so it considers that code to be within its purview:

$ npx eslint .

/Users/tburnham/code/test-driven-carousel/dist/carousel.js
   16:24  error  Missing trailing comma                      comma-dangle
   45:48  error  'Symbol' is not defined                     no-undef
  ...

✖ 81 problems (81 errors, 0 warnings)
  36 errors and 0 warnings potentially fixable with the `--fix` option.

81 errors! Fortunately, ESLint allows you to ignore directories the same way that Git does, by listing patterns in an ignore file:

//.eslintignore
dist/

It’s also a good idea to tell Prettier to ignore that directory:

//.prettierignore
dist/

Additionally, if you’re using VS Code, you might consider adding dist to the files.exclude section of your Workspace Settings since you shouldn’t have to view or edit its contents directly:

//.vscode/settings.json
{
  "files.exclude": {
    "dist": true, 
    "node_modules": true, 
    "package-lock.json": true
  }, 
  "[javascript]": {
    "editor.tabSize":  
  },
  "editor.formatOnSave": true 
}

Get hands-on with 1400+ tech skills courses.