# Introduction to Webpack

> Learn what webpack does and how to configure entry points, output, modes, loaders, plugins, assets, a development server, source maps, and code splitting.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-01-27 | Updated: 2026-07-18 | Topics: [DevTools](https://flaviocopes.com/tags/devtool/) | Canonical: https://flaviocopes.com/webpack/

Webpack builds a dependency graph from your application's modules and produces files that a browser can load.

It can bundle JavaScript, process CSS and other assets, transform source files through loaders, and extend the build through plugins.

For a tiny page with one script, you may not need a bundler. Webpack becomes useful when an application has shared modules, npm dependencies, assets, and a production build pipeline.

## Install webpack locally

Create a project and install webpack plus its command-line interface:

```bash
npm init -y
npm install --save-dev webpack webpack-cli
```

Install build tools locally instead of globally. This keeps the expected versions in `package.json` and lets every contributor run the same setup.

The official [getting started guide](https://webpack.js.org/guides/getting-started/) tracks the current prerequisites.

## Build your first bundle

Create `src/index.js`:

```js
const heading = document.createElement('h1')
heading.textContent = 'Hello from webpack'
document.body.appendChild(heading)
```

Webpack uses `src/index.js` as its default entry and writes `dist/main.js` by default.

Run a readable development build:

```bash
npx webpack --mode development
```

Then load the generated file from an HTML page:

```html
<script src="./main.js"></script>
```

Webpack's defaults are useful for a quick test, but a real project should make its build choices explicit.

## Add npm scripts

Put repeatable commands in `package.json`:

```json
{
  "private": true,
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack serve --mode development"
  }
}
```

Run them with:

```bash
npm run build
npm run dev
```

Marking an application package as private helps prevent accidental publication to npm.

## Create a webpack configuration

Create `webpack.config.js`:

```js
const path = require('node:path')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  }
}
```

The four important parts are:

- `mode` selects development or production defaults
- `entry` tells webpack where to start building the dependency graph
- `output.filename` names the generated bundle
- `output.path` selects the absolute output directory

`output.clean: true` removes old generated files before each build. You do not need a separate clean plugin for this common case.

Webpack can also load ESM configuration. Match the syntax to the module type used by your Node project.

## Development and production modes

Use:

```js
mode: 'development'
```

while developing. It favors useful debugging output and faster iteration.

Use:

```js
mode: 'production'
```

for deployable builds. It enables production-oriented optimizations such as minification.

Do not ship a development bundle by accident. Keep separate scripts or configuration files when the two environments need different settings.

## Loaders

Webpack understands JavaScript and JSON modules directly. Loaders transform other file types, or preprocess JavaScript, as webpack builds the graph.

For example, install the common CSS loaders:

```bash
npm install --save-dev css-loader style-loader
```

Then add a rule:

```js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}
```

Now JavaScript can import a stylesheet:

```js
import './styles.css'
```

Loaders in `use` run from right to left. Here `css-loader` resolves CSS imports and URLs, then `style-loader` injects the result into the page.

For production, many applications extract CSS into separate files instead of injecting it at runtime.

The [loader documentation](https://webpack.js.org/concepts/loaders/) explains rules and ordering.

## Asset Modules

Webpack can handle images, fonts, and text without the old `file-loader`, `url-loader`, or `raw-loader` packages.

Use Asset Modules:

```js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource'
      }
    ]
  }
}
```

Import an image from JavaScript:

```js
import imageUrl from './logo.png'

const image = new Image()
image.src = imageUrl
document.body.appendChild(image)
```

The main asset types are:

- `asset/resource` emits a separate file and exports its URL
- `asset/inline` exports a data URL
- `asset/source` exports the source text
- `asset` chooses between inline and separate output based on size

See the [Asset Modules guide](https://webpack.js.org/guides/asset-modules/).

## Plugins

Loaders transform individual modules. Plugins hook into the wider build process.

Plugins can generate HTML, define environment constants, extract CSS, analyze bundles, or customize emitted files.

For example, `html-webpack-plugin` generates an HTML file that includes the correct bundle:

```bash
npm install --save-dev html-webpack-plugin
```

```js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: 'My application'
    })
  ]
}
```

Plugins are JavaScript objects created with `new`, while loaders are configured under `module.rules`.

Read the [plugin concepts guide](https://webpack.js.org/concepts/plugins/) before adding build extensions.

## Use the development server

Install the development server:

```bash
npm install --save-dev webpack-dev-server
```

Add configuration:

```js
module.exports = {
  devServer: {
    static: './dist',
    open: true
  }
}
```

Run it:

```bash
npx webpack serve --mode development
```

The development server keeps generated files in memory and reloads as source files change. It is a development tool, not a production web server.

Webpack's [development guide](https://webpack.js.org/guides/development/) also covers watch mode and middleware.

## Source maps

Bundled code is harder to debug because the browser sees generated files. Source maps connect generated code back to the original modules.

For a development build:

```js
module.exports = {
  mode: 'development',
  devtool: 'eval-source-map'
}
```

Source-map settings trade build speed, rebuild speed, output quality, and whether source content is exposed. Choose a production setting deliberately, especially for private source code.

See the [`devtool` reference](https://webpack.js.org/configuration/devtool/) for the current options and their tradeoffs.

## Code splitting

A single large bundle makes the browser download code it may not need immediately.

Dynamic `import()` creates a separate chunk:

```js
button.addEventListener('click', async () => {
  const { showDialog } = await import('./dialog.js')
  showDialog()
})
```

Webpack loads that chunk when the click happens.

Webpack can also split shared dependencies between entry points. Start with the official [code splitting guide](https://webpack.js.org/guides/code-splitting/) and measure the result rather than creating chunks blindly.

## Babel and webpack solve different problems

Webpack follows imports and bundles modules. It does not automatically transform every modern JavaScript feature for older target environments.

Use a loader such as `babel-loader` when your supported browsers or runtimes need syntax transforms. Configure browser targets once through Babel and Browserslist so the build does only the work your audience requires.

Read the [Babel guide](https://flaviocopes.com/babel/) for a focused setup.
