Output Management
So far we've manually included all our assets in our index.html file, but as your application grows and once you start using hashes in filenames and outputting multiple bundles, it will be difficult to keep managing your index.html file manually. However, webpack can take the page over for you.
Preparation
First, let's adjust our project a little bit:
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
│ ├── index.js
+ │ ├── print.js
└── /node_modulesLet's add some logic to our src/print.js file:
src/print.js
export default function printMe() {
console.log("I get called from print.js!");
}And use that function in our src/index.js file:
src/index.js
import _ from 'lodash';
+import printMe from './print.js';
function component() {
const element = document.createElement('div');
+ const btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ btn.innerHTML = 'Click me and check the console!';
+ btn.onclick = printMe;
+
+ element.appendChild(btn);
+
return element;
}
document.body.appendChild(component());Let's also update our dist/index.html file, in preparation for webpack to split out entries:
dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
- <title>Asset Management</title>
+ <title>Output Management</title>
+ <script src="./print.bundle.js"></script>
</head>
<body>
- <script src="bundle.js"></script>
+ <script src="./index.bundle.js"></script>
</body>
</html>Now adjust the config. We'll be adding our src/print.js as a new entry point (print) and we'll change the output as well, so that it will dynamically generate bundle names, based on the entry point names:
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
- entry: './src/index.js',
+ entry: {
+ index: './src/index.js',
+ print: './src/print.js',
+ },
output: {
- filename: 'bundle.js',
+ filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};Let's run npm run build and see what this generates:
...
[webpack-cli] Compilation finished
asset index.bundle.js 69.5 KiB [emitted] [minimized] (name: index) 1 related asset
asset print.bundle.js 316 bytes [emitted] [minimized] (name: print)
runtime modules 1.36 KiB 7 modules
cacheable modules 530 KiB
./src/index.js 406 bytes [built] [code generated]
./src/print.js 83 bytes [built] [code generated]
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.x.x compiled successfully in 1996 msWe can see that webpack generates our print.bundle.js and index.bundle.js files, which we also specified in our index.html file. if you open index.html in your browser, you can see what happens when you click the button.
But what would happen if we changed the name of one of our entry points, or even added a new one? The generated bundles would be renamed on a build, but our index.html file would still reference the old names. Let's fix that by handing the page to webpack.
Making the page an entry point
Webpack understands HTML natively behind experiments.html: point entry at an .html file and the page joins the build. Every <script src> it references becomes part of the module graph, and the emitted page has those URLs rewritten to the generated filenames — so the page and the bundles cannot drift apart.
Move index.html out of dist/ and into src/, next to the files it refers to:
project
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
├── /src
+ │ ├── index.html
│ ├── index.js
│ ├── print.js
└── /node_modulessrc/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Output Management</title>
- <script src="./print.bundle.js"></script>
+ <script src="./print.js"></script>
</head>
<body>
- <script src="./index.bundle.js"></script>
+ <script src="./index.js"></script>
</body>
</html>The tags point at the files you actually wrote, not at build output. Now adjust the config to build the page rather than the two scripts:
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
- entry: {
- index: './src/index.js',
- print: './src/print.js',
- },
+ entry: './src/index.html',
+ experiments: {
+ html: true,
+ },
output: {
filename: '[name].bundle.js',
+ htmlFilename: '[name].html',
path: path.resolve(__dirname, 'dist'),
},
};There is no list of entry points to keep in sync any more — the page states which scripts it needs, and output.htmlFilename says where the built page goes. Let's see what npm run build generates now:
...
[webpack-cli] Compilation finished
asset main1.bundle.js 69.1 KiB [emitted] [minimized] 1 related asset
asset main.bundle.js 1.39 KiB [emitted] [minimized]
asset index.html 183 bytes [emitted] [minimized] (auxiliary name: main)
runtime modules 3.13 KiB 6 modules
cacheable modules 534 KiB (javascript) 213 bytes (html)
modules by path ./src/*.js 487 bytes
./src/index.js 403 bytes [built] [code generated]
./src/print.js 84 bytes [built] [code generated]
./src/index.html 213 bytes [built] [code generated]
./node_modules/lodash/lodash.js 533 KiB [built] [code generated]
webpack 5.x.x compiled successfully in 3064 msWebpack has written dist/index.html for you, with both <script> tags rewritten to the bundles it just emitted. Rename an entry, add a script tag, or turn on hashed filenames, and the page follows along on the next build.
Cleaning up the /dist folder
As you might have noticed over the past guides and code example, our /dist folder has become quite cluttered. Webpack will generate the files and put them in the /dist folder for you, but it doesn't keep track of which files are actually in use by your project.
In general it's good practice to clean the /dist folder before each build, so that only used files will be generated. Let's take care of that with output.clean option.
webpack.config.js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.html',
experiments: {
html: true,
},
output: {
filename: '[name].bundle.js',
htmlFilename: '[name].html',
path: path.resolve(__dirname, 'dist'),
+ clean: true,
},
};Now run an npm run build and inspect the /dist folder. If everything went well you should now only see the files generated from the build and no more old files!
The Manifest
You might be wondering how webpack and its plugins seem to "know" what files are being generated. The answer is in the manifest that webpack keeps to track how all the modules map to the output bundles. If you're interested in managing webpack's output in other ways, the manifest would be a good place to start.
The manifest data can be extracted into a json file for consumption using the ManifestPlugin.
We won't go through a full example of how to use this plugin within your projects, but you can read up on the concept page and the caching guide to find out how this ties into long term caching.
Conclusion
Now that you've learned about dynamically adding bundles to your HTML, let's dive into the development guide. Or, if you want to dig into more advanced topics, we would recommend heading over to the code splitting guide.



