Webpack:将动态导入的模块拆分为单独的块,同时将库保留在主捆绑包中

时间:2019-11-17 14:01:33

标签: javascript webpack webpack-4

我正在开发一个Web应用程序,其中包含应使用dynamic imports延迟加载的小部件。 每个窗口小部件都应为其收到单独的捆绑包,并且它们的依赖关系仅在用户请求时才由浏览器获取。有一个例外:流行的窗口小部件以及它们使用或扩展的库类应包括在主捆绑包中。在下面,您将看到我打算实现的文件夹结构以及我想要的输出:

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)

要延迟加载小部件,我在create的{​​{1}}方法中使用以下表达式。效果很好,Webpack神奇地拾取了小部件模块。

WidgetFactory

我试图通过在Webpack中配置const Widget = await import(`../widgets/${name}/index`) 来解决代码拆分难题。通过提供optimization.splitChunks.cacheGroups函数,我将模块分配到testlibrary缓存组中。

widgets

我被卡住了!

  • 如何在小部件包中包括小部件依赖性?
  • 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 } } } } } 缓存组上使用chunks: 'async'可使某些库类进入library块,而另一些则保留在main块中。为什么?
  • 当我在library缓存组上使用chunks: 'all'时,所有模块都正确地合并到其中,但是我丢失了library块上的入口点并得到空白页。怎么样?
  • main缓存组重命名为library时,丢失了入口点as documented here。我不太了解为什么会这样,以及如何将main入口点和main合并到一个捆绑包中。

希望您可以通过Webpack阐明我的陡峭学习曲线。

2 个答案:

答案 0 :(得分:1)

根据我通常在Webpack(v4)配置中使用的配置,我将通过以下方式进行配置:

splitChunks: {
    chunks: 'async',
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    name: true,
    cacheGroups: {
        default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true,
        },
        library: {
            test: module => module => isInLibrary(module.identifier()),
            chunks: 'all',
            enforce: true
        },
    }
}

如果只是懒惰地需要窗口小部件,请不要将widgets添加到cacheGroups。

Webpack应该为每个异步需求解析库中的窗口小部件依赖项。

我没有尝试强制使用捆绑包名称,但我认为它的行为将相同。

答案 1 :(得分:1)

使用所需的配置创建了一个简单的github repo

  1. SplitChunks配置(如果需要,可以将testname函数更改为您的函数)
    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
                }
            }
    }
  1. 如果希望PopularWidget位于main中,则不应动态导入它。使用常规的import语句。因为webpack总是会为任何动态导入创建一个新块。因此,请看一下this文件。
    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
更改了配置,以使相关的node_modules与小部件保持在一起。
我为创建新的小部件块编写了两个简单的函数。
这里的技巧是module.issuer属性,这意味着谁导入了此文件。因此,函数isRelativeToWidget对于在小部件的文件结构的任何深度导入的任何依赖项(无论是否有node_modules)都将返回true。
可以找到代码here
产生的配置:

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,
    }
}