Webpack lazy-loading chunks

3 min read

It is not a good practice to build all frontend scripts into single big bundle. Webpack supports splitting files to separate modules out of a box. This note will show you how to start using Webpack chunks and avoid cache problems.


Chunked module will be saved to MODULE-NAME file in output.path directory.

e => import(/* webpackChunkName: "MODULE-NAME" */ './path/to/module').then(module => { // ... })

Base demo app

Project's file structure

untitled |-- node_modules/ |-- src/ | |-- modules/ | | |-- hello.js | | |-- world.js | |-- index.js |-- index.html |-- package.json |-- webpack.config.js |-- yarn.lock

Example package.json file with webpack package

{ "name": "untitled", "main": "src/index.js", "scripts": { "build": "webpack --mode production", "build:dev": "webpack --mode development --watch" }, "devDependencies": { "webpack": "^4.29.6", "webpack-cli": "^3.3.0" } }

Test page index.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script src="dist/index.bundle.js"></script> </body> </html>

Webpack config file webpack.config.js

const path = require('path'); module.exports = { entry: { index: './src/index.js' }, output: { filename: '[name].bundle.js', publicPath: 'dist/', path: path.resolve(__dirname, 'dist') } };

Main script file src/index.js

module.exports = (() => { /** * Make a button with a text and click callback */ const addButton = (text, callback) => { const button = document.createElement('BUTTON'); button.innerHTML = text; button.addEventListener('click', callback); document.body.appendChild(button); }; /** * Create a button with a callback from separate module */ addButton('Hello', e => import(/* webpackChunkName: "module-hello" */ './modules/hello').then(module => { module.default(); })); /** * Create another one button */ addButton('World', e => import(/* webpackChunkName: "module-world" */ './modules/world').then(module => { module.default(); })); })();

Two modules src/modules/hello.js and src/modules/world.js

export default () => { console.log('Hello'); };
export default () => { console.log('World'); };

Build bundles yarn build, open index.html file in browser, open browser's console and try to press buttons on the page.

Modules will be downloaded from server.

Cache issue

Browser caches all scripts by url identifier to reduce network load.

Developers usually add a hash identifier at the end of script url as a GET param. So it does not affect to static files content but it helps browser recognize cached/non-cached files.


PHP code from template

<script src="/public/build/bundle.js?v=<?= filemtime('public/build/bundle.js') ?>"></script>

Will generate something like this

<script src="/public/build/bundle.js?v=1558035869"></script>

On bundle updates modify time will be updated and url will be changed too. Then browser will download new script file.


In webpack.config.js file you can define name scheme for chunks.

chunkFilename: '[name].bundle.js?h=[chunkhash]'

Just add an imaginary GET param to filename. For chunk file saving the ending will be ignored so you can avoid bundles hell in git status.

const path = require('path'); module.exports = { entry: { index: './src/index.js' }, output: { filename: '[name].bundle.js', chunkFilename: '[name].bundle.js?h=[chunkhash]', publicPath: 'dist/', path: path.resolve(__dirname, 'dist') } };

Files have no ?h=... in their names. But main script knows their hashes.

This way you can resolve cache issue.