在RequireJS中使用动态require时,出现“模块名称尚未加载到上下文中”的错误?

68

有没有一种方法可以在RequireJS中定义一个模块,以“动态”方式加载其他模块?如果有,优化器(r.js)如何理解何时需要包含模块?

例如,让dynModules是一个定义名称/路径对的模块:

define([], function () {
    return ['moduleA', 'moduleB']; // Array of module names
});

另一个模块将根据数组动态加载模块,但这 不起作用

define(['dyn_modules'], function (dynModules) {
    for(name in dynModules) {   
        var module = require(path); // Call RequireJS require
    }

    // ...
});

...给了我:

未捕获的错误:模块名“moduleA”尚未为上下文加载。请使用require([])。 http://requirejs.org/docs/errors.html#notloaded

我可以解决这个错误,但它不再是“动态的”了:

define(['dyn_modules', 'moduleA', 'moduleB'], function (dynModules) {
    for(name in dynModules) {   
        var module = require(path); // Call RequireJS require
    }

    // ...
});

1
看起来你正在使用糖语法,这就解释了为什么它只适用于字符串而不适用于变量:https://dev59.com/tWgv5IYBdhLWcg3wBMIK#10914329 - Roger Huang
2个回答

77

这个限制涉及到简化的CommonJS语法与普通回调语法之间的区别:

由于下载模块的时间不确定,因此加载模块本质上是一个异步过程。然而,RequireJS在模拟服务器端的CommonJS规范时尝试给出一种简化的语法。当您执行以下操作时:

var foomodule = require('foo');
// do something with fooModule

幕后发生的事情是,RequireJS查看您的函数代码体并解析出您需要'foo',然后在函数执行之前加载它。但是,当变量或其他任何不是简单字符串的东西(例如您的示例)...

var module = require(path); // Call RequireJS require

如果不进行转换,Require 无法解析出这个代码并自动进行转换。解决方法是转换为回调函数语法。

var moduleName = 'foo';
require([moduleName], function(fooModule){
    // do something with fooModule
})

考虑到上述情况,以下是使用标准语法重新编写第二个示例的一种可能方式:
define(['dyn_modules'], function (dynModules) {
    require(dynModules, function(){
        // use arguments since you don't know how many modules you're getting in the callback
        for (var i = 0; i < arguments.length; i++){
            var mymodule = arguments[i];
            // do something with mymodule...
        }
    });

});

编辑:从你自己的回答中,我看到你正在使用underscore/lodash,因此使用_.values_.object可以简化上述循环遍历参数数组的过程。


2
感谢您的时间,我在 RequireJS 的网站上搜索后找到了解决方案。请查看我的回答。 - gremo
我唯一的问题是:使用require(_.values(Config), ...)是异步代码,对吗?这意味着当require结束时,我需要使用回调风格,对吗? - gremo
是的,你在回答中使用了回调语法。require(_.values(Config), function () { 大致相当于我的 require(dynModules, function(){。两者都使用字符串数组作为第一个参数,并提供回调函数作为第二个参数。 - explunit
1
请注意,除非您在选项中设置{ findNestedDependencies:true },否则这将破坏requireJS的编译功能。 - Ash Blue
1
如果我想添加 - require("somemodule");,其中 somemodule 是一个 JSON 文件,该怎么办? - Smitha
显示剩余2条评论

8

回答自己的问题。来自RequireJS网站:

//THIS WILL FAIL
define(['require'], function (require) {
    var namedModule = require('name');
});

这个错误的原因是requirejs需要确保加载并执行所有依赖项后才调用上面的工厂函数。所以,要么不传递依赖项数组,要么使用依赖项数组,并在其中列出所有依赖项。
我的解决方法是:
// Modules configuration (modules that will be used as Jade helpers)
define(function () {
    return {
        'moment':   'path/to/moment',
        'filesize': 'path/to/filesize',
        '_':        'path/to/lodash',
        '_s':       'path/to/underscore.string'
    };
});

加载器:
define(['jade', 'lodash', 'config'], function (Jade, _, Config) {
    var deps;

    // Dynamic require
    require(_.values(Config), function () {
        deps = _.object(_.keys(Config), arguments);

        // Use deps...
    });
});

不,modules 应该在另一个模块中定义,即配置文件。顺便说一下,它运行得很好。 - gremo
看起来关键仍然是从CommonJS同步语法转换为RequireJS回调语法。 - explunit
@explunit,你能更好地解释一下你的意思吗?我只需要将一些模块(用于浏览器)作为Jade中的帮助程序可用,并且我需要这个是动态的。 - gremo
1
我更新了我的答案以澄清。两个答案都可以,只是想为任何未来的读者解释一下 RequireJS 在幕后的真正运作方式。 - explunit

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