Introduction To Webpack with Node.js

August 7, 2020

Webpack is a static module bundler for JavaScript applications. It takes modules, whether that’s a custom file that we created or something that was installed through NPM, and converts these modules to static assets.

This enables you to take a fully dynamic application and package it into static files, which you can then upload and deploy to your server. We can also extend what webpack can do with Plugins and Loaders. A module is JavaScript code with a discrete chunk of functionality, and it abstracts and delegates functionality to libraries so that we don’t have to understand the complexity of it.

Why Use Module Bundlers and Webpack?

When we had very few JS scripts on a webpage, there were two ways to load JavaScript.

<html>
  <body>
    <script src="index.js"></script>
  </body>
</html>

and

<html>
  <body>
    <script>
      var foo = "bar";
      console.log(foo);
    </script>
  </body>
</html>

This method does not scale, and when we have many scripts, it becomes a problem to load these scripts because of network bottleneck. If we keep them all in one file, then it becomes unmaintainable and causes problems with the name and scope of variables.

Then came IIFE’s which solved scope issues for large projects, but changing even one file meant rebuilding the entire project.

JavaScript Modules (Node.js modules)

When JavaScript was bought server-side with Node.js, there were no HTML files to add <script> tags. CommonJS (an organisation) came out and introduced Common.js modules and require, which allows you to load and use a module in the current file.

//math.js
module.exports = function add(a,b){
  return a+b;
}
//index.js
var add = require("./add")

While this was great for Node.js projects, there is no browser support for CommonJS. Bundling tools like RequireJS and Browserify were created for this purpose.

ECMAScript Modules (ESM)

ECMAScript modules are the official standard format to package JavaScript code for reuse. Modules are defined using a variety of import and export statements.

//add.mjs
function add(a,b){
  return a+b;
}
//app.mjs
import {add} from "./add.mjs"
console.log(add(1,2));
// .mjs file extension used for using ES modules in Node

This is good news for web projects. However, browser support is incomplete, and bundling is still faster than early module implementations.

So, why webpack?

Webpack provides a great developer experience as it not only bundles JavaScript applications (supporting both EcmaScript Modules and CommonJS), but when paired with plugins and loaders it can be used for taking care of dependencies and assets, such as images, fonts, SASS, CSS, etx. Webpack goes through your project and builds a dependency graph based on what is imported and exported.

Getting Started with Webpack

Webpack provieds a Command Line Interface (CLI), which we can call as webpack filename.js target/index.js from the terminal; and Node.js API, which we can use in our Node.js application. With webpack 4 we can use it without any configuration.

Let’s create a dummy Node.js project and bundle it with webpack. You can use your project.

Install webpack

You need NPM and Node installed on your machine.

mkdir dummy-project
cd dummy-project
npm init -y
npm install webpack webpack-cli --save-dev

webpack without configuration

Create the following directory structure.

  webpack-demo
  |- package.json
  |- index.html
  |- /src
  |- index.js

Add the following code to index.js:

function component() {
  const element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'world'], ' ');
  return element;
}

document.body.appendChild(component());

In index.html:

<!doctype html>
<html>
  <head>
    <title>Basic Application</title>
    <script src="https://unpkg.com/lodash@4.16.6"></script>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

The above code works because we added a <script> loading lodash. If we run npx webpack in the dummy-project directory, webpack creates a bundle in the dist directory with filename main.js.

src is the “source” directory where we write and edit our code. The dist folder contains “distribution” code, which is the minimized and optimized output of the build process that will eventually be loaded in the browser.

If we inspect dist/main.js.

!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=0)}([function(e,n){document.body.appendChild(function(){const e=document.createElement("div");return e.innerHTML=_.join(["Hello","world"]," "),e}())}]);

Let’s install lodash locally and remove the <script> tag in index.html. We should also tweak the directory structure and move index.html into dist to use main.js file.

npm install --save lodash
src/index.js
  import _ from 'lodash';
  function component() {
  const element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'world'], ' ');
  return element;
  }

  document.body.appendChild(component());
dist/index.html
  <!doctype html>
  <html>
    <head>
      <title>Basic Application</title>
    </head>
    <body>
      <script src="main.js"></script>
    </body>
  </html>

If we now open index.html in the browser we see “Hello world”.

Using a configuration with webpack

Webpack doesn’t require any configuration, but most projects will need a more complex setup, which is why webpack supports a configuration file. Add a file with the name webpack.config.js in the dummy-project directory.

Example webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
  filename: 'main.js',
  path: path.resolve(__dirname, 'dist'),
  },
};

The following are some configurable concepts of webpack.

  • Entry. Entry defines the entry-point for the application. It is the first module that webpack will process to build its dependency graph.
module.exports = {
  entry: './path/to/my/entry/file.js'
};
  • Output. The output property tells webpack where to store the bundles it creates and how to name these files. The default location is ./dist/main.js for Javascript files and also stores other generated files in the ./dist folder.
module.exports = {
  output: {
  filename: 'my-first-bundle.js',
  pathname: __dirname + '/dist'
  }
}
  • Loaders. Out of the box, webpack only understands JavaScript and JSON files. Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph.
moduel.exports = {
  module: {
  rules: [
  {test: /\.txt$/, use: 'raw-loader'}
  ]
  }
}

The test property identifies which file should be transformed. The use property indicates which loader should be used.

  • Plugins. While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables. In order to use a plugin, you need to require() it and add it to the plugins array. Most plugins are customizable through options.
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  plugins: [
  new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};
  • Mode. The mode can be set to production or development with their usual meanings.

We can add webpack to npm scripts in package.json so as to ease our development process. Add "build": "webpack", to package.json and run the command npm run build.

Conclusion

A huge advantage of using webpack is its customizability and its features like Hot Module Reloading, we have discussed only some ways in which webpack can be configured please visit this link for more information. There are other tools which have emerged like Parcel Bundler, but webpack is still well suited for large and complex applications due to its features. Read about comparisons here.

References


About the author

Rohan Reddy

Rohan Reddy is an undergraduate student at the University of Hyderabad pursuing a degree in Computer Science. Rohan is particularly interested in machine learning and web development.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.