【问题标题】:How to split code in Webpack with require.ensure for production?如何使用 require.ensure 拆分 Webpack 中的代码以进行生产?
【发布时间】:2016-09-10 09:16:24
【问题描述】:

我正在使用 Webpack,我想将我的客户端代码分成几部分,并在用户需要时加载它们。

这是我的文件结构:

app.js       <-- Entry point as introduced to Webpack
Module.js    <-- To be loaded dynamically

app.jsModule.js 之间没有直接联系,而是第二个文件由第一个文件加载,如下所示:

require.ensure([], (require) => {
    let path = "Module";
    let module = require("./" + path).default;
});

我使用"./" + path 只是为了确保 Webpack 不会对我变聪明并尝试静态解析路径。无论如何,这段代码在 webpack-dev-server 的开发模式下工作。我的意思是Module.js 在触发事件以运行上述代码之前不会被下载。之后,它就被完美地加载和执行了。

但是当我打包项目进行生产时,它停止工作。它给出了以下错误(当我触发下载事件时在浏览器中)甚至没有尝试发送请求:

未捕获的错误:找不到模块“./Module”。

此外,当我动态组合路径时(如上面的代码),构建过程会发出以下警告:

./src/app/app.js 中的警告 关键依赖: 74:34-47 依赖的请求是一个表达式

为生产配置 Webpack 以支持动态下载代码拆分的正确方法是什么?


我已经测试了@wollnyst 给出的解决方案,结果如下。当我这样实现它时,它可以工作:

require.ensure(["./Module"], (require) => {
    let path = "Module";
    let module = require("./" + path).default;
});

但这不是我想要的,我想要这样:

let path = "Module";
require.ensure(["./" + path], (require) => {
    let module = require("./" + path).default;
});

现在它在浏览器中发出运行时错误:

Uncaught TypeError: webpack_require(...).ensure is not a function

还是没有运气!

【问题讨论】:

  • 你不需要为了让它动态加载而使模块路径动态。 require.ensure 负责处理。静态路径在您的情况下可以正常工作
  • @RaiyanMohammed 很抱歉,但我不关注!在上面的代码中,我动态地制作路径,因为在现实生活中我就是这样。在编写代码时,模块的路径是未知的。只有在运行时才会在变量中指定路径。
  • 这就是问题所在:与 node-require 相比,webpack 是静态的。它在构建时运行一次。因此,webpack 无法评估动态 requires。你到底有什么结构?也许定义一个自己的context 可能会有所帮助。
  • @wollnyst 我已经测试了您的建议并且它有效,但问题是它不会拆分代码!上下文是否应该拆分代码?您是否有关于如何动态加载拆分代码的工作示例?文档有点含糊!
  • 看看你的 webpack.config.js 可能是个好主意。当我开始使用 webpack 时,我已经看到了这一点,但是可能不相关,看看输出配置选项,它有一个 publicPath 设置,它告诉 webpack 查找文件的路径。来自documentation, output.publicPath:此选项指定在浏览器中引用时输出目录的公共 URL。

标签: webpack commonjs


【解决方案1】:

require.ensure 数组的第一个参数中动态放置您想要的路径是错误的,不应该这样做。该数组旨在用于您要动态加载的模块的依赖项,并且是回调中的异步代码安全运行所必需的。

理解 webpack 如何处理代码拆分的关键部分是你不能做完全动态的语句,因为 webpack 需要一些位置信息来解析你想要的模块,因此dependency is an expression 警告。即使您可以通过预先添加./ 来变得更聪明,最好使用静态模块路径字符串实际重复完整的ensure 语句,即使您有很多模块要动态加载并且有点讨厌,这样你就不会遇到任何问题。

另一种方法是创建一个专用于此目的的文件夹,例如splits,然后从这里解析所有模块。但这意味着您在此目录中需要的每个模块都将位于同一个拆分点,除了特定的用例之外,这并不是任何人想要的。


关于配置,您需要使用namedModulesPluginsCommonsChunkPlugin 之一。我个人所做的是拥有一个包含所有常见源的main.js,一个包含所有常见node_modulesvendor.js,以及一个runtime.js(webpack 需要)。然后,这些块被分离,如果一个依赖于特定的供应商依赖,它将被添加到这个特定的块而不是常见的vendor.js

plugins: [
  new webpack.NamedModulesPlugin(),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: module => module.context && module.context.indexOf('node_modules'),
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'runtime',
  }),
],

您还可以确保拥有类似于以下output.filename 的内容:

output: {
  filename: '[name]-[chunkhash].js',
}

否则,您的 vendor.js 的哈希值会在每次源更改时更改,即使您没有修改您的 deps,如果您担心缓存,这很糟糕。

最近的webpack visualizer 是一个非常好的工具,可以检查您的包和块的外观,并验证事情看起来是否正常或检测可以更好地分块的依赖项。如果使用StatsWriterPlugin 收集统计信息,您可能需要更改一些配置,以便它处理您的块:

new StatsWriterPlugin({
  fields: null,
  transform: (data, opts) => {
    const stats = opts.compiler.getStats().toJson({ chunkModules: true })
    return JSON.stringify(stats, null, 2)
  },
}),

还有一点需要注意,require.ensure 是特定于 webpack 的,并且正在被它现在也处理的 import() 所取代。由于这个问题是从 2016 年开始的,它可能没有我们现在使用 webpack 2 的相同元素,所以我将对其进行一些扩展。

现在,dynamic import 提案正在进入 ES,您可以在 webpack 中使用它。你需要一个 Promise polyfill 和类似 babel syntax-dynamic-import 插件(现在应该在 stage-3 preset 中)或 dynamic-import-webpack 如果这对你不起作用(基本上将 import() 转换为ensures)。

如果你想为每个模块获取一个块,你仍然需要像确保一样指定完整路径,但这是一种更好的语法,并且在未来更有针对性。

您可以在新的 webpack 文档页面上查看有关 code splitting 的许多其他资源。

【讨论】:

  • 另外,对于动态导入,模板字符串可以在模块路径中使用,我们只需要确保向 webpack 提供一些路径信息(就像你提到的那样)。
  • 是的,确实,我没有精确说明,因为它只是(很酷的)es6 替换字符串连接。
  • 当然。我没有看到任何关于字符串连接的提及,所以我想我提到它。
  • @EliranMalka 还有其他问题吗?
  • 你几乎都解决了它们:)thanx
【解决方案2】:

你需要在require.ensure的第一个参数中传递你想要的模块:

require.ensure(['./Module'], function(require) {
    const module = require('./Module');
});

【讨论】:

  • 您的解决方案有点工作,但有点不行!我已将结果包含在问题中。谢谢。
猜你喜欢
  • 1970-01-01
  • 2017-01-26
  • 1970-01-01
  • 2017-03-28
  • 1970-01-01
  • 2017-07-16
  • 2016-05-05
  • 2017-09-01
  • 2017-02-05
相关资源
最近更新 更多