【问题标题】:Webpack: Split dynamic imported modules into separate chunks while keeping the library in the main bundleWebpack:将动态导入的模块拆分为单独的块,同时将库保留在主包中
【发布时间】:2020-03-13 00:51:16
【问题描述】:

我正在开发一个 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)

为了延迟加载小部件,我在WidgetFactorycreate 方法中使用了以下表达式。这很好用,小部件模块会被 Webpack 神奇地拾取。

const Widget = await import(`../widgets/${name}/index`)

我试图通过在 Webpack 中配置 optimization.splitChunks.cacheGroups 来解决代码拆分挑战。提供test 函数我将模块分配到librarywidgets 缓存组中。

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

我被困住了!

  • 如何在小部件包中包含小部件依赖项?
  • library 缓存组上使用chunks: 'async' 使一些库类进入main 块,而其他库类留在library 块中。为什么?
  • 当我在library 缓存组上使用chunks: 'all' 时,所有模块都正确组合在其中,但我丢失了main 块上的入口点并得到一个空白页。怎么样?
  • 当我将library 缓存组重命名为main 时,我丢失了入口点as documented here。我不太明白为什么会这样,以及我如何设法将main 入口点与library 组合成一个包。

希望您能对我使用 Webpack 的陡峭学习曲线有所了解。

【问题讨论】:

    标签: javascript webpack webpack-4


    【解决方案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,
        }
    }
    

    【讨论】:

    • 谢谢!在工厂直接和动态导入之间切换完美!现在假设 Edge Case Widget 需要一个 NPM 库,当前的 Webpack 配置会为它创建一个单独的包。你有没有办法让 Webpack 在同一个包中包含一个小部件的依赖项?
    • @ffraenz 我明天早上看看怎么处理
    • 是的,module.issuer 绝对是正确的选择。我想它需要一些调整来处理 Core JS,但我会对其进行排序。谢谢!
    【解决方案2】:

    根据我在 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 应该为每个异步需求解析库中的小部件依赖项。

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

    【讨论】:

    • 我已经按照您的建议进行了更改。 src/library/Widget.ts 被每个小部件模块扩展,因此被捆绑多次。有没有机会将这些模块移到主包中?
    • 我想我是倒着读的。小部件由 main 延迟加载并依赖于库,因此请尝试将库保留在 cacheGroup 中并从中删除小部件。我已经更新了答案。如果只是懒惰地需要小部件,则不应将它们捆绑在 main 中。
    • 是的,小部件只能动态导入。更新 Webpack 配置后,我遇到了多个问题:(1) 库块现在再次与主块分离。 (2) 我在第一条评论中提到的小部件类接收一个自己的块,而不是被添加到主块中。 (3) 显然,widget bundles 不再命名。我的用例对缓存组策略无效吗?
    猜你喜欢
    • 1970-01-01
    • 2020-05-05
    • 1970-01-01
    • 2015-06-02
    • 2017-03-13
    • 2021-05-01
    • 1970-01-01
    • 2018-03-01
    • 1970-01-01
    相关资源
    最近更新 更多