Webpack 2 - 代码拆分顶级依赖

Webpack 2 - Code splitting top-level dependencies

最终编辑

tl;dr 解决这个问题是不可能的。虽然下面的最佳答案确实提供了一些很好的信息。


考虑下面的代码,来自 contacts.js。这是一个动态加载的模块,在代码的其他地方按需加载 System.import

如果 SharedUtil1 也用在其他模块 中,这些模块也是 动态加载 System.import,我将如何拥有 SharedUtility1 所有 这些模块中排除,并且仅在第一次 需要时按需加载?

SharedUtil1 的顶层 System.import 将不起作用,因为我的导出依赖于它:导出只能放在模块代码的顶层,不能以任何形式回调。

Webpack 可以吗?我正在使用 2.0.7 测试版。

import SharedUtil1 from '../../SharedUtilities/SharedUtility1';

class Contacts{
    constructor(data){
        this.data = data;
        this.sharedUtil1 = new SharedUtil1();
    }
}

export default Contacts;

更新 1

我认为 bundle loader 是我想要的,但不,它会将您导入的模块变成一个不同的函数,您可以通过回调调用该函数以到达实际模块,一旦它完成异步加载。这意味着您不能在不对代码进行重大更改的情况下透明地使模块 X 异步加载,更不用说您又回到了最初描述的问题,如果您的顶级模块依赖于 now-asynchronously加载的依赖项,没有办法导出它,因为导出必须在顶层。

在 Webpack 中是否没有办法表示依赖项 X 将在需要时按需加载,并且有任何导入它的导入模块透明地等待导入过程?我认为这个用例对于任何远程大型应用程序都是 sine qua non,所以我不得不认为我只是遗漏了一些东西。

更新 2

根据 Peter 的回答,我试图让重复数据删除工作,因为正如他提到的,commonChunk 插件与端点之间的共享代码有关,而且 require.ensure 将加载的代码放入回调中,从而阻止你来自 ES6 exporting 任何依赖于它的代码。

就重复数据删除而言,contacts.jstasks.js 都像这样加载相同的 sharedUtil

import SharedUtil1 from '../../sharedUtilities/sharedUtility1';

我尝试 运行 webpack 作为

webpack --optimize-dedupe

并添加

plugins: [
    new webpack.optimize.DedupePlugin()
]

到webpack.config。在这两种情况下,尽管 sharedUtil 代码仍位于联系人和任务包中。

如果我没理解错的话,您想要防止在不同的代码块将其声明为依赖项时多次加载相同的依赖项。

是的,这是可能的;如何做到这一点取决于应用程序中的上下文以及它是在 ES6 还是 ES5 中。

ECMA 脚本 5

Webpack 1 是在 ECMA Script 5 中构建的,通常使用 CommonJS 或 RequireJS 语法进行模块导出和导入。使用此语法时,可以使用以下功能来防止重复代码:

  • Deduplication防止重复文件包含在编译的 通过创建重复函数的副本而不是 重新定义它们。
  • Named Chunks 允许将块声明为依赖项但不立即求值;同一块的所有出现都将使用相同的实例。
  • CommonsChunkPlugin 允许跨多个入口点共享块(仅适用于多页面网站)。

去重

来自webpack documentation:

If you use some libraries with cool dependency trees, it may occur that some files are identical. Webpack can find these files and deduplicate them. This prevents the inclusion of duplicate code into your bundle and instead applies a copy of the function at runtime. It doesn’t affect semantics.

重点是我的,不是出处

如文档所述,代码拆分保持不变;每个需要 sharedUtil1 的模块都应该将 require 声明为正常。为了防止多次加载相同的依赖项,启用了一个 webpack 设置,使 webpack 在运行时包含文件之前明确检查文件是否重复。

此选项已启用 --optimize-dedupe 分别。 new webpack.optimize.DedupePlugin()

命名块

来自webpack documentation:

The require.ensure function accepts an additional 3rd parameter. This must be a string. If two split point pass the same string they use the same chunk... require.include can be useful if a module is in multiple child chunks. A require.include in the parent would include the module and the instances of the modules in the child chunks would disappear.

简而言之,模块的加载被延迟到编译的后期。这允许在包含重复定义之前将其删除。文档提供了示例。

通用块插件

来自webpack documentation:

The CommonsChunkPlugin can move modules that occur in multiple entry chunks to a new entry chunk (the commons chunk). The runtime is moved to the commons chunk too. This means the old entry chunks are initial chunks now.

这对于在多个页面之间共享块非常具体,在其他情况下不相关。

ECMA 脚本 6

对高级模块导入功能的支持……正在进行中。要了解事物的位置,请参阅以下链接:

下面是对 ES6 模块和 webpack 的一个很好的总结:ES6 Modules with TypeScript and Webpack

以上信息很可能很快就过时了。

建议

为了您的理智,我建议:

如果优化很重要: 当 Webpack 2 稳定并发布时,恢复到 CommonJS / RequireJS 语法并升级到 ECMA Script 6。

如果 ECMA Script 6 语法很重要: 使用标准的 ECMA Script 6 导入导出格式并添加可用的优化功能。

在不稳定的 webpack 2 中尝试和使用高级模块加载功能的流量太多了。等待事情稳定下来,等待一些非常好的插件可用,然后再尝试。

对于 Webpack 创建者来说,这是不可能的。干净利落。有关 Webpack 和 ES6 的许多其他有用信息,请参阅 Peter 的回答。

粘贴的图像是误会的结果。请参阅上面同一用户的回答。

看了你的博文post终于明白你的意思了。我对 "Top-level dependencies".

这个词有点困惑

您有两个模块(async-aasync-b),它们是从任何地方按需加载的(这里是模块 main),并且都有一个共享模块的引用( shared).

- - -> on-demand-loading (i. e. System.import)
---> sync loading (i. e. import)

main - - -> async-a ---> shared
main - - -> async-b ---> shared

默认情况下,webpack 创建一个像这样的块树:

---> chunk uses other chunk (child-parent-relationship)

entry chunk [main] ---> on-demand chunk 1 [async-a, shared]
entry chunk [main] ---> on-demand chunk 2 [async-b, shared]

shared < async-a/b 或同一用户同时使用 async-aasync-b 的可能性很低时,这很好。这是默认设置,因为它是最简单的行为,可能也是您所期望的:一个 System.import => 一个块。在我看来这也是最常见的情况。

但如果 shared >= async-a/b 并且 async-aasync-b 被用户加载的概率很高,则有一个更有效的分块选项: (有点难以形象化):

entry chunk [main] ---> on-demand chunk 1 [async-a]
entry chunk [main] ---> on-demand chunk 2 [async-b]
entry chunk [main] ---> on-demand chunk 3 [shared]

When main requests async-a: chunk 1 and 3 is loaded in parallel
When main requests async-b: chunk 2 and 3 is loaded in parallel
(chunks are only loaded if not already loaded)

这不是默认行为,但有一个插件可以将其存档:异步模式下的 CommonChunkPlugin。它在一堆块中找到 common/shared 模块并创建一个包含共享模块的新块。在异步模式下,它确实会与原始(但现在更小)块并行加载新块。

new CommonsChunkPlugin({
    async: true
})

// This does: (pseudo code)
foreach chunk in application.chunks
  var shared = getSharedModules(chunks: chunk.children, options)
  if shared.length > 0
    var commonsChunk = new Chunk(modules: shared, parent: chunk)
    foreach child in chunk.children where child.containsAny(shared)
      child.removeAll(shared)
      foreach dependency in chunk.getAsyncDepenendenciesTo(child)
        dependeny.addChunk(commonsChunk)

请记住,CommonsChunkPlugin 有一个 minChunks 选项来定义何时将模块线程化为 shared(随意为 select 模块提供自定义函数)。

这是一个详细解释设置和输出的示例:https://github.com/webpack/webpack/tree/master/examples/extra-async-chunk

还有一个配置更多:https://github.com/webpack/webpack/tree/master/examples/extra-async-chunk-advanced

System.import 已在 Webpack 中弃用。 Webpack 现在支持 import(),它需要一个 polyfill 来实现承诺。

Code Splitting - Using import()