Skip to content
FLAVIO COPES
flaviocopes.com
2026

Introduction to Webpack

By

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

~~~

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:

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 tracks the current prerequisites.

Build your first bundle

Create src/index.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:

npx webpack --mode development

Then load the generated file from an HTML page:

<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:

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

Run them with:

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:

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:

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:

mode: 'development'

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

Use:

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:

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

Then add a rule:

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

Now JavaScript can import a stylesheet:

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 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:

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

Import an image from JavaScript:

import imageUrl from './logo.png'

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

The main asset types are:

See the Asset Modules guide.

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:

npm install --save-dev html-webpack-plugin
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 before adding build extensions.

Use the development server

Install the development server:

npm install --save-dev webpack-dev-server

Add configuration:

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

Run it:

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 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:

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 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:

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 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 for a focused setup.

Tagged: DevTools · All topics
~~~

Related posts about devtool: