Require.ensure() 非阻塞

Require.ensure() non-blocking

如果我们有不同的包由 webpack 创建,我们 require.ensure 稍后动态 transfer+eval 它在某个时间点,它通过 jsonPadding 和一些 webpack js 魔术发生。如果我们有

require.ensure([ ], ( require ) => {
    console.log('before...');
    var data = require( './myModule.js' );
    console.log('after...');
  }, 'myModule')

"after..." 将在完全传输和评估该模块时遇到。如果碰巧这个 chunk / module 相当大,包含图像,css 等等,加载将非常多在 webpack javascript 代码解压缩包及其所有组件时锁定浏览器。

问题:有什么方法可以"hook"进入那个require魔法吗?例如,对于以下内容进行回调将是一个理想的场景:

等等,假设我们传输的包包含大量数据。总的来说,我很难有一个很好的选项来动态地异步传输整个包,但仍然必须以完全同步/阻塞的方式加载那个包。

Let me preface by saying I know this might be an 'annoying' answer, because it doesn't answer your question directly but offers an alternative, pragmatic, solution to the browser hanging problem. I used this pattern myself to manage asset loading within the context of a heavy 3D web game.

I'm writing this as an answer and not as a comment so it might serve others who come across the same problem. If this does answer your case, I'll be happy to provide actual code to implement and generify these sort of modules.

如果我理解正确,本质上你想要的是一种将 MyModule 分解为离散组件的方法,这些组件可以在一个 require.ensure 的上下文中自动加载和评估,但处理评估如此并不是所有的事情都是一次性评估的,导致浏览器挂起。

另一种看待这个问题的方法是使用 requireensure 方法本身作为 loading/evaluation 机制。考虑 MyModule.js,这是一个具有依赖项 Css1, Css2, ... CssN 以及 JS1, JS2, ... JSN 和图像的巨大加载模块。

我的建议是将其分解为 SuperMyModule.js,这需要 MyModuleLogic.js 以及所有 CSS、图像和 JS。

节点,在SuperMyModule.js你可以做:

let myModuleLogic = require("myModuleLogic");
console.log('JS was evaluated');

require.ensure(['image1.png'], ( require ) => {
    let data = require( './images1.png' );
    console.log('image[1] was evaluated');
    // register that resource was evaluated/fire event
})
require.ensure(['style1.css'], ( require ) => {
    let data = require( './style1.css' );
    console.log('css[1] was evaluated');
    // register that resource was evaluated/fire event
})

//after all resources evaluated/fire callback or event

然后在您的原始文件中,按照您的要求:

require.ensure([ ], ( require ) => {
    console.log('before...');
    let myModule = require( './superMyModule.js' );
    console.log('after...');
  })

如果您将模块实例设置为事件发射器,则可能会像这样挂载资源:

require.ensure([ ], ( require ) => {
    let myModule = require( './superMyModule.js' );
    myModule.on("loadResource", myCallback)
  })

您可以使用工作加载器卸载主线程并避免阻塞。

缺点:在 main window 和 worker 之间需要传递额外的消息。

https://github.com/webpack/worker-loader

您也可以尝试在大模块中发出加载事件以跟踪更精细的进度。

进一步参考:

我想我自己对这个话题也很困惑,所以我的问题可能不够精确,无法得到正确的回答。然而,我对整个 "commonJS dynamic module loading" 上下文的误解是,require.ensure() 只会传输 Module Code(分别是 Chunk 通过网络创建的 webpack)。之后,传输的 Chunk 基本上只是一个大的 ECMAscript 文件,只是位于浏览器中,缓存但尚未评估。整个 Chunk 的评估只发生在实际的 require() 调用上。

话虽如此,如何解耦和评估 Module / Chunk 的各个部分完全掌握在您的手中。例如,如果像我原来的问题一样,模块 requires() 在一些 CSS 文件 、一些 Images 和一些HTML,所有内容都在 require.ensure() 调用中异步传输。您以何种方式 require()(因此 评估 )这些部分完全取决于您,如果需要,您可以自己解耦这些调用。

例如,一个模块看起来像这样:

Module1.js

"use strict";
import { io } from 'socket.io-client';

document.getElementById( 'foo' ).addEventListener('click', ( event ) => {
    let partCSS = require( 'style/usable!./someCoolCSS.css' ),
        moarCSS = require( 'style/usable!./moarCoolCSS.css' ),
        tmpl    = require( './myTemplate.html' ),
        image1  = require( './foo.jpg' ),
        image2  = require( './bar.png' );
}, false);

当然,当其他模块调用时,所有这些文件都已经包含在传输到客户端的块中:

require.ensure([ 'Module1.js' ], ( require ) => {
}, 'Module1');

这是我的困惑。所以现在,我们可以自己玩 module1.js 中的 require() 调用。如果我们真的需要这样的大量文件,我们甚至可以使用 setTimeout / setImmediate 运行-away-timer 来解耦每个 require() 调用之间的同步评估,如果必要的或想要的。

实际上是一个很简单的故事的长答案。

TL;DR:require.ensure 通过网络传输整个数据块。该数据块包含所有文件,这些文件是确保模块内 require() 调用的一部分。但这些文件 得到自动评估。只有当实际的 require() 调用在 运行 时匹配时才会发生(此时由 webpackJSONP 调用表示)"

如果您想通过 require.ensure 以及其他 Promise 开始加载异步 JavaScript 捆绑包,您可以通过以下方式实现:

const requireEnsurePromise = new Promise((resolve) => {
  require.ensure(['./modulePath'], function (requireEnsure) {
    console.log('The module is fetched but not evaluated yet');
    resolve(requireEnsure.bind(null, require.resolve('./modulePath')));
  });
});

Promise.all([
  fetch('/api/relevant/stuff').then(response => response.json()),
  requireEnsurePromise,
]).then((values) => {
  if (values[0]) {
    // DO STUFF
  }
  console.log('right before module is evaluated');
  const evaluatedModule = values[1]();
});

Webpack 静态确定模块路径对应于Webpack 内部表示(可以是整数或字符串)。每当 Webpack 识别出 require.ensure([], fn) 模式时,它都会查看 fn 回调的函数体并执行此操作。为了在以 Promise 方式获取 JavaScript 包后延迟评估时间,require('./modulePath') 不能出现在 require.ensure 成功回调中,因为它将评估模块。 Webpack 将 require('./modulePath') 翻译成类似 __webpack_require__(2343423) 的东西,这就是为什么你想避免在这种情况下使用它。