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.
Code
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.
Example
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.
output.chunkFilename
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.
Links
- Lazy Loading // webpack.js.org
- Code Splitting / Dynamic Imports // webpack.js.org