RequireJS加载脚本进度

13

虽然冒着问一个愚蠢问题的风险,我会尝试一下。

在RequireJs加载依赖项时是否可能显示进度指示器?

例如:

require(['jquery'], function($) {

    // Well, jQuery loaded in quite some time due to a low-speed connection
    //
    // Or maybe I wanted to show an overlay to prevent user of clicking on UI widgets until the load is complete
});

如果有一些插件我在谷歌搜索中没有找到,我就不想开始修改RequireJS源代码。

谢谢你的帮助。

3个回答

13

可以显示旋转指示器或进度指示器,但不能显示进度条本身。我可以获取requirejs的状态(当前正在加载或空闲),但无法获取已加载/需要加载模块的%,因为它们的依赖关系在每个模块加载时解析,而不是在开始时。

但是,无论如何,页面上至少有一个简单的旋转指示器要比只有空白页面更好,这样用户就不至于无聊等待。

不需要对requirejs进行任何更改!

假设我们有一个名为require.conf.js的文件

require.config({...})
require(["app"], function () { 

// main entry point to your hugde app's code 
});

它是通过HTML加载的

<script data-main="require.conf" type="text/javascript" src="bower_components/requirejs/require.js"></script>

这是标准的requirejs场景。让我们为其添加指示器。

<div id="requirejs-loading-panel">
    <div id="requirejs-loading-status"></div>
    <div id="requirejs-loading-module-name"></div>
</div>

好的,让我们来学习一下requirejs的函数require.onResourceLoad并做所有必要的操作。每次模块加载时,requirejs都会调用它,并传递带有dep树和其他所有内容的requirejs上下文。我们将使用上下文来判断requirejs是否正在加载某些内容。我在定时器调用中完成了它,因为onResourceLoad()仅在加载时调用,而在加载完成后不会调用。此代码需要添加到require.js.conf中:

require.onResourceLoad = function (context, map, depMaps) {


    var loadingStatusEl = document.getElementById('requirejs-loading-status'),
        loadingModuleNameEl = document.getElementById('requirejs-loading-module-name');
    var panel = document.getElementById('requirejs-loading-panel');

    if (loadingStatusEl && loadingModuleNameEl) {


        if (!context) { // we well call onResourceLoad(false) by ourselves when requirejs is not loading anything => hide the indicator and exit

            panel.style.display = "none";
            return;
        }

        panel.style.display = ""; // show indicator when any module is loaded and shedule requirejs status (loading/idle) check
        clearTimeout(panel.ttimer); 
        panel.ttimer = setTimeout(function () {


            var context = require.s.contexts._;
            var inited = true;
            for (name in context.registry) {
                var m = context.registry[name];

                if (m.inited !== true) {
                    inited = false;
                    break;
                }

            } // here the "inited" variable will be true, if requirejs is "idle", false if "loading"

            if (inited) {
                require.onResourceLoad(false);  // will fire if module loads in 400ms. TODO: reset this timer for slow module loading
            }

        }, 400)
        if (map && map.name) { // we will add one dot ('.') and a currently loaded module name to the indicator

            loadingStatusEl.innerHTML = loadingStatusEl.innerHTML += '.'; //add one more dot character
            loadingModuleNameEl.innerHTML = map.name + (map.url ? ' at ' + map.url : '');
        }
    } else {


    }


};

一个问题是:我们无法确定需要加载多少模块,因此无法计算实际的加载进度百分比。但是,至少我们可以找出是否正在加载某些内容(并获取当前正在加载的模块名称),并将其显示给紧张的用户。


谢谢。我以前可能会用到这个,也许将来会用于开发。 - crissdev
这里有一个错误,我认为应该是:if (inited !== true) 应该是 m.inited !== true。否则你为什么需要 m 呢? - Xyz
另一件事是:让我们考虑最后一个模块加载并设置最后一个计时器的情况。如果模块在400毫秒后仍在加载中怎么办?那么我们就永远不会触发require.onResourceLoad(false)。 - Xyz
我将超时时间提取到一个名为“resetLoading”的函数中,并在结尾处添加了ELSE {setTimeout(resetLoading, 100);}。 - dampee
无论如何,查看最新的requirejs功能,使创建此加载指示器更加容易。谢谢! - AlataR

10
自从 v.2.1.19 版本后,RequireJS 有一个 onNodeCreated 配置属性。如果您将一个函数分配给它,每次在文档中添加 <script> 元素以加载模块时,该函数都会被调用。
这个函数将提供 node、config、moduleName 和 url 参数。通过向节点附加事件监听器,您可以检测脚本何时已加载或无法加载。
现在您可以检测加载过程何时开始和停止,并使用该信息通知用户:
require.config({
  /* ... paths, shims, etc. */
  onNodeCreated: function(node, config, moduleName, url) {
    console.log('module ' + moduleName + ' is about to be loaded');

    node.addEventListener('load', function() {
      console.log('module ' + moduleName + ' has been loaded');
    });

    node.addEventListener('error', function() {
      console.log('module ' + moduleName + ' could not be loaded');
    });
  },
});

对于IE<9,您需要额外的代码来附加事件监听器。

注意: 这仅适用于由RequireJS直接请求的模块。插件(如requirejs/text等)各自使用自己的加载机制,并且不会触发onNodeCreated。但是,一些插件会触发 onXhronXhrComplete,因此也可以处理它们:

require.config({
  /* ... paths, shims, etc. */
  config: {
    text: {
      onXhr: function(xhr, url) {
        console.log('file ' + url + ' is about to be loaded');
      },
      onXhrComplete: function(xhr, url) {
        console.log('file ' + url + ' has been loaded');
      }
    }
  }
});

感谢@keleran。根据https://github.com/jrburke/requirejs/issues/1361,似乎添加了该功能以支持[子资源完整性](https://w3c.github.io/webappsec-subresource-integrity/)。 - crissdev

8

不,这是不可能的。RequireJs通过在DOM中创建一个新的script标签并监听加载事件来加载模块,在开始和结束加载之间没有更新事件。因此,这不是RequireJs的限制,而是DOM的限制。


@explunit:如果你有什么要补充的,请在此答案下评论,不要编辑答案。 - user195488
如果您在Firebug或Chrome开发工具中检查html头部区域,您可以看到RequireJs对脚本标签的处理方式,并理解为什么不可能有进度指示器。 - explunit
谢谢你的帮助。这让我想到了一个更好的方法——不再依赖于“脚本已加载”事件,而是在应用程序逻辑中实现它。 - crissdev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接