Key Takeaways
- Webpack bundles a JavaScript entry point and the modules it depends on; Babel separately transforms JavaScript syntax.
- Browser targets determine which Babel transformations are needed. Transpiling syntax does not automatically supply missing runtime APIs.
- This project keeps HTML and bundle paths aligned, with npm commands for development, watch mode, and production builds.
- This is a small learning setup, not a complete production build pipeline.
To set up Webpack, install it locally, configure a JavaScript entry point and output directory, and load the resulting bundle from HTML. Add Babel through babel-loader when you need syntax transformations for your supported browsers.
This Webpack tutorial starts with a bare bundle, then adds Babel without changing how you run the project. Webpack's core job is building a dependency graph: following references between modules and assembling the output your browser loads.
Creating the Project
Use an up-to-date Node.js 24 LTS release with npm. On that release line, Babel 8 requires Node.js 24.11.0 or later. If you need help installing the runtime, start with our Node.js environment setup guide.
mkdir webpack-tutorial
cd webpack-tutorial
npm init -yReplace the generated package.json with this starting file. The installation commands below will add dependency entries automatically.
{
"name": "webpack-tutorial",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "webpack --mode production --no-devtool",
"build:dev": "webpack --mode development",
"dev": "webpack serve --mode development",
"watch": "webpack --mode development --watch"
}
}The type setting lets us write the Webpack configuration using ES module imports and exports. npm scripts also let everyone run the locally installed tools using the same commands, as demonstrated in Webpack's getting-started guide.
Installing and Configuring Webpack
Install Webpack, its command-line interface, and its development server:
npm install --save-dev "webpack@^5.111.0" webpack-cli webpack-dev-serverThe minimum version here is intentional: Webpack 5.111 introduced output.copy, which we'll use to put the HTML file beside the bundle without an extra plugin. See the Webpack 5.111 release notes.
Choose Browser Targets
Create .browserslistrc in the project root:
last 2 Chrome versions
last 2 Firefox versions
last 2 Safari versions
last 2 Edge versionsBrowserslist expresses which browsers your application supports. This deliberately narrow learning example selects the last two versions of four desktop browsers; replace it with your actual support policy, including mobile browsers where needed. The resolved versions can change when browser compatibility data is updated.
Create the HTML and JavaScript Entry Point
Create a public directory and add public/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Getting Started with Webpack</title>
<script src="bundle.js" defer></script>
</head>
<body>
<h1>Webpack is awesome</h1>
</body>
</html>Create index.js in the project root:
console.log('hello world');The HTML references the generated bundle.js, not the source index.js. Our configuration will copy the HTML into the output directory, keeping that relative URL correct.
Add the Webpack Configuration
Create webpack.config.js in the project root:
import { fileURLToPath } from 'node:url';
export default {
mode: 'development',
entry: './index.js',
target: 'browserslist',
devtool: 'source-map',
output: {
filename: 'bundle.js',
path: fileURLToPath(new URL('./dist/', import.meta.url)),
publicPath: '/',
clean: true,
copy: 'public',
},
devServer: {
host: '127.0.0.1',
port: 8080,
static: false,
},
};Read this configuration as a short set of instructions:
entrystarts the dependency graph atindex.js.output.pathselects the on-disk output directory;filenamenames the bundle.copy: 'public'copies the contents ofpublicinto that output directory.clean: trueremoves obsolete output files. Keep authored files outsidedist.publicPath: '/'makes generated assets available from the server root in this example.
The output configuration reference documents these settings. Separately, target: 'browserslist' tells Webpack which JavaScript features it may use in its generated runtime—the supporting code around your modules. It does not replace Babel's source transformations.
Build and Serve the Bare Bundle
Run a development build:
npm run build:devYour project should now have this structure, excluding node_modules:
webpack-tutorial/
|-- .browserslistrc
|-- index.js
|-- package.json
|-- package-lock.json
|-- webpack.config.js
|-- public/
| `-- index.html
`-- dist/
|-- index.html
|-- bundle.js
`-- bundle.js.mapStart the development server:
npm run devOpen http://127.0.0.1:8080/ and check the browser's developer console for hello world. You should also see a successful request for bundle.js in the Network panel.
The server serves compiled assets from memory rather than updating the files in dist. With static: false, it doesn't additionally serve a static source directory. Webpack's development guide explains the in-memory workflow, and the dev-server reference covers its configuration.
Basic Use Case | Transpiling ES6 with Babel
Babel is a separate compiler. @babel/core runs transformations, @babel/preset-env selects transformations for your targets, and babel-loader connects Babel to Webpack.
Stop the development server with Ctrl+C, then install the Babel dependencies:
npm install --save-dev babel-loader@10 @babel/core@8 @babel/preset-env@8The babel-loader documentation describes the integration and compatibility requirements. Keep the Babel core and preset on compatible release lines rather than mixing versions from unrelated tutorials.
Add the babel-loader Rule
Add the following module property alongside output and devServer in your existing configuration. This is a fragment, not a replacement file:
// Fragment: merge module into your existing exported configuration object.
export default {
// ... existing configuration properties
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
};This rule sends matching JavaScript files through Babel while skipping installed dependencies. Create babel.config.json in the project root:
{
"presets": [
["@babel/preset-env", { "debug": true }]
]
}@babel/preset-env reads our existing Browserslist configuration. Its debug option reports the resolved targets and enabled transformations, making it easier to see what Babel is doing.
index.js and imported JavaScript
|
v
Babel via babel-loader
|
v
Webpack bundle + runtime
|
v
browser
public/index.html --copied--> dist/index.htmlTest It Out: Verify the Babel Transformation
Replace index.js with the original compact example:
const name = 'Alice';
setTimeout(() => console.log(`Bundling successful, ${name}`), 300);This combines const, an arrow function, and a template literal. If those constructs are unfamiliar, review our JavaScript ES6 introduction.
- Run
npm run build:devand inspect Babel's target and transformation diagnostics in the terminal. - Open the generated
dist/bundle.jsand findBundling successfulto locate your application code. - Run
npm run dev, reload the page, and confirm that the console printsBundling successful, Alice.
Modern targets may already support all three constructs, so Babel can legitimately leave them unchanged. An unchanged arrow function is not, by itself, evidence that your loader failed.
For a visible teaching demonstration, temporarily add "forceAllTransforms": true beside "debug": true in the preset options and rebuild. The application portion can then look like this; formatting and surrounding bundle code may differ:
// Representative transformed application code, not the complete bundle.
var name = 'Alice';
setTimeout(function () {
return console.log("Bundling successful, ".concat(name));
}, 300);Remove forceAllTransforms afterward so your browser targets select the necessary transformations. Also remove debug when you no longer need the diagnostics. Remember that Webpack generates its own runtime separately: transforming this source snippet is not a guarantee that the entire bundle is ES5-compatible.
Syntax Transforms Are Not Polyfills
A syntax transform rewrites language constructs, such as an arrow function. A polyfill supplies a missing runtime feature, such as Promise or an array method. This configuration adds no polyfills.
Development, Watch, and Production Builds
Webpack's mode setting selects optimization defaults. Development mode keeps useful module names and doesn't minimize JavaScript by default; production mode enables minimization and additional optimizations.
| Command | Behavior in this project |
|---|---|
npm run build:dev | Writes a development build and source map to dist. |
npm run dev | Serves an in-memory development build and rebuilds as you edit. |
npm run watch | Rebuilds files on disk as you edit, without starting an HTTP server. |
npm run build | Writes a production build and disables source maps. |
The CLI options override corresponding configuration settings. That's why the production script switches away from the configuration's development mode and uses --no-devtool to disable source maps. You don't need a separate task runner for these basic workflows.
What This Learning Setup Does Not Cover
This project bundles JavaScript and copies HTML. It doesn't demonstrate image optimization or a complete styling pipeline. Webpack has native CSS support as well as configurable loaders and module types; don't assume every asset requires a loader, or that installing Webpack automatically optimizes every file.
Before shipping a real application, test your supported browsers, review dependency compatibility and polyfill needs, and choose a deployment and caching strategy. A fixed bundle.js filename is convenient here; content-hashed filenames are useful when designing long-term browser caching. Continue with our JavaScript performance tips after the basic build works.
When Should You Choose Webpack?
Choose Webpack when you need explicit control over module rules and output, or when you're maintaining an application that already uses it. Complexity alone isn't a reason to choose it: Rollup and Parcel are alternatives worth comparing. Our JavaScript build tools guide provides the broader context.
Common Mistakes
- Editing generated files: change
index.jsorpublic/index.html, not their built counterparts indist. - Confusing disk output with server output: inspect
dist/bundle.jsafter a disk build, not as proof of what the development server is currently serving. - Assuming excluded dependencies are transformed: this Babel rule skips
node_modules. If a dependency uses syntax your targets don't support, adjust the rule deliberately. - Treating a successful build as compatibility testing: verify behavior in the browsers you actually support.
FAQ
What is the difference between Webpack and Babel?
Webpack assembles a dependency graph into bundles. Babel transforms JavaScript syntax, and babel-loader lets Webpack run those transformations on matching source files.
How do I set up Webpack with Babel?
Install babel-loader, @babel/core, and @babel/preset-env, then add a JavaScript loader rule to your Webpack configuration. Configure browser targets through Browserslist or Babel's targets option.
How do I choose browser targets for Babel?
Choose targets from your application's browser support policy and user requirements. A shared .browserslistrc file can guide Babel's syntax transformations and Webpack's generated runtime syntax, but it does not replace browser testing.
Why can't my HTML file find the Webpack bundle?
The script URL must match where the bundle is served, not merely where Webpack writes it on disk. This tutorial copies index.html beside bundle.js and serves both from the same URL root.
How do I run and automate a Webpack development build?
In this project, npm run build:dev creates a one-off development build, npm run dev starts the development server, and npm run watch rebuilds files on disk without starting a server. The npm scripts explicitly select development mode.
Further Reading
- Webpack getting started — entries, modules, configuration, and npm scripts.
- Webpack output configuration — filenames, output paths, cleaning, and copying files.
- Webpack development guide — source maps, watch mode, and in-memory serving.
- Webpack mode reference — development and production optimization defaults.
- babel-loader documentation — integration, options, and compatibility requirements.
- Babel preset-env reference — target-based transformations and diagnostics.
- Upgrade to Babel 8 — runtime requirements and removed configuration options.
- Browserslist documentation — browser queries and shared target configuration.
