I am working on a web app containing widgets that should be lazily loaded using dynamic imports. Each widget should receive its separate bundle for it and their dependencies to only be fetched by the browser once it is being requested by the user. There's an exception to this: Popular widgets should be included in the main bundle as well as the library classes they use or extend. Below you see the folder structure I intend to achieve and the output I aim for:
File Structure Desired chunk
src/
├── widgets/
│ ├── popular-widget/index.ts main
│ ├── edge-case-widget/index.ts edge-case-widget
│ └── interesting-widget/index.ts interesting-widget
├── library/
│ ├── Widget.ts main
│ └── WidgetFactory.ts main
└── index.ts main (incl. entry point)
dist/
├── edge-case-widget.app.js edge-case-widget
├── interesting-widget.app.js interesting-widget
└── main.app.js main (incl. entry point)
To lazily load widgets I am using following expression in a create
method in WidgetFactory
. This works fine, the widget modules get magically picked up by Webpack.
const Widget = await import(`../widgets/${name}/index`)
I was trying to solve the code splitting challenge by configuring optimization.splitChunks.cacheGroups
in Webpack. Providing a test
function I allocate modules into either the library
or widgets
cache group.
module.exports = {
entry: './src/index.ts',
[...]
optimization: {
splitChunks: {
cacheGroups: {
library: {
test: module => isInLibrary(module.identifier()),
name: 'library',
chunks: 'all',
minSize: 0
},
widgets: {
test: module => isWidgetBundle(module.identifier()),
name: module => widgetNameFromModuleId(module.identifier()),
chunks: 'async',
minSize: 0
}
}
}
}
}
I am stuck!
chunks: 'async'
on the library
cache group make some library classes go to the main
chunk while others stay in the library
chunk. Why?chunks: 'all'
on the library
cache group all modules properly get combined in it but I lose the entry point on the main
chunk and get a blank page. How?library
cache group to main
I lose the entry point, as documented here. I don't quite understand why this is the case and how I manage to combine the main
entry point with the library
into a single bundle.Hopefully you can shed some light onto my steep learning curve with Webpack.
created a simple github repo with configuration you need.
test
and name
functions to yours if you want): splitChunks: {
cacheGroups: {
widgets: {
test: module => {
return module.identifier().includes('src/widgets');
},
name: module => {
const list = module.identifier().split('/');
list.pop();
return list.pop();
},
chunks: 'async',
enforce: true
}
}
}
main
, you should not import it dynamically. Use regular import
statement. Because webpack will ALWAYS create a new chunk for any dynamic import. So please take a look at this file. import { Widget } from '../widgets/popular-widget';
export class WidgetFactory {
async create(name) {
if (name === 'popular-widget') return await Promise.resolve(Widget);
return await import(`../widgets/${name}/index`)
}
}
UPD
Changed configuration to keep related node_modules with widgets.
I wrote two simple functions for new widgets chunk creation.
The trick here is module.issuer
property which means who imported this file. So function isRelativeToWidget
will return true for any dependencies (node_modules or not) imported at any depth of widget's file structure.
Code can be found here.
Resulting configuration:
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false,
edgeCaseWidget: getSplitChunksRuleForWidget('edge-case-widget'),
interestingWidget: getSplitChunksRuleForWidget('interesting-widget')
}
}
function isRelativeToWidget(module, widgetName) {
if (module.identifier().includes(widgetName)) return true;
if (module.issuer) return isRelativeToWidget(module.issuer, widgetName)
}
function getSplitChunksRuleForWidget(widgetName) {
return {
test: module => isRelativeToWidget(module, widgetName),
name: widgetName,
chunks: 'async',
enforce: true,
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With