Webpack: Split dynamic imported modules into separate chunks while keeping the library in the main bundle

ffr*_*enz 5 javascript webpack webpack-4

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)
Run Code Online (Sandbox Code Playgroud)

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.

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)
Run Code Online (Sandbox Code Playgroud)

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.

const Widget = await import(`../widgets/${name}/index`)
Run Code Online (Sandbox Code Playgroud)

I am stuck!

  • How can I include widget dependencies in the widget bundles?
  • Using chunks: 'async' on the library cache group make some library classes go to the main chunk while others stay in the library chunk. Why?
  • When I use 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?
  • When I rename the 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.

nic*_*ock 5

使用您需要的配置创建了一个简单的 github存储库。

  1. SplitChunks 配置(如果需要,可以根据需要进行更改test和功能):name
    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
                }
            }
    }
Run Code Online (Sandbox Code Playgroud)
  1. 如果您希望PopularWidget位于 中main,则不应动态导入它。使用常规import语句。因为 webpack 总是会为任何动态导入创建一个新块。所以请看一下这个文件。
    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`)
        }
    }

Run Code Online (Sandbox Code Playgroud)

UPD
更改了配置以保留相关的 node_modules 与小部件。
我为新的小部件块创建编写了两个简单的函数。
这里的技巧是module.issuer属性,它意味着谁导入了这个文件。因此,isRelativeToWidget对于在小部件文件结构的任何深度导入的任何依赖项(无论是否为 node_modules),函数都将返回 true。代码可以在这里
找到。 结果配置:

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,
    }
}
Run Code Online (Sandbox Code Playgroud)