Webpack is a module bundler, but its real magic is turning your messy, interconnected JavaScript modules into a single, optimized file that browsers can actually understand.

Let’s see it in action. Imagine you have two files:

src/index.js:

import { greet } from './utils';

console.log(greet('World'));

src/utils.js:

export function greet(name) {
  return `Hello, ${name}!`;
}

You want to bundle these. First, install Webpack and webpack-cli:

npm install webpack webpack-cli --save-dev

Then, create a webpack.config.js file in your project root:

const path = require('path');

module.exports = {
  mode: 'development', // Or 'production' for optimizations
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

Now, run Webpack from your terminal:

npx webpack

This creates a dist/bundle.js file. If you open dist/index.html (you’ll need to create this) and include <script src="bundle.js"></script>, your browser will execute the bundled code, and you’ll see "Hello, World!" in the console.

The core problem Webpack solves is managing dependencies in a project that grows beyond a few simple scripts. As your project scales, manually linking script tags becomes a nightmare, and you lose control over how your code is loaded and optimized. Webpack automates this by treating your entire project – JavaScript, CSS, images, fonts – as a graph of modules. It starts at an "entry point" (like src/index.js) and follows all import and require statements, building a dependency tree.

The mode option (development or production) tells Webpack how to optimize the output. development provides a faster build and better debugging experience, while production enables minification, tree-shaking, and other optimizations for smaller file sizes and faster runtime performance. The entry points are the starting files Webpack will process. You can have multiple entry points, which is useful for creating separate bundles for different parts of your application (e.g., admin dashboard vs. public site). The output section tells Webpack where to put the bundled files (filename) and in which directory (path). path.resolve(__dirname, 'dist') is a standard Node.js way to create an absolute path to the dist folder relative to your current file (webpack.config.js).

Webpack’s power comes from its ecosystem of loaders and plugins. Loaders transform non-JavaScript files into modules that Webpack can process. For example, babel-loader transpiles modern JavaScript into older versions for broader browser compatibility. Plugins extend Webpack’s capabilities, allowing you to perform tasks like generating an index.html file automatically (html-webpack-plugin) or optimizing CSS.

The most surprising thing most developers don’t realize is that Webpack’s module resolution isn’t limited to just the node_modules directory. By default, it looks in node_modules for packages, but you can configure resolve.modules to include other directories, or even use resolve.alias to map specific module names to different paths, allowing you to create custom module structures or abstract away complex file paths.

Once you have your basic bundle, the next logical step is to explore how to handle assets like CSS and images.

Want structured learning?

Take the full Webpack course →