Dynamic require in RequireJS, getting "Module name has not been loaded yet for context" error?
Asked Answered
M

2

68

Is there a way to define a module that "dynamically" load other modules in RequireJS? If yes, how the optimizer (r.js) understands how/when a module has to be included?

For example, let dynModules a module which defines name/path pairs:

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

Another module is going to load modules dynamically, based on the array. This will not work:

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

    // ...
});

... gives me:

Uncaught Error: Module name "moduleA" has not been loaded yet for context: _. Use require([]) http://requirejs.org/docs/errors.html#notloaded

I can solve the error, but it's not "dynamic" anymore:

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

    // ...
});
Marabout answered 3/7, 2013 at 11:33 Comment(1)
Seems like you're using sugar syntax, and this explains why it only applies to String but not to variables: https://mcmap.net/q/296703/-how-to-achieve-lazy-loading-with-requirejsMandymandych
C
77

The limitation relates to the simplified CommonJS syntax vs. the normal callback syntax:

Loading a module is inherently an asynchronous process due to the unknown timing of downloading it. However, RequireJS in emulation of the server-side CommonJS spec tries to give you a simplified syntax. When you do something like this:

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

What's happening behind the scenes is that RequireJS is looking at the body of your function code and parsing out that you need 'foo' and loading it prior to your function execution. However, when a variable or anything other than a simple string, such as your example...

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

...then Require is unable to parse this out and automatically convert it. The solution is to convert to the callback syntax;

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

Given the above, here is one possible rewrite of your 2nd example to use the standard syntax:

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...
        }
    });

});

EDIT: From your own answer, I see you're using underscore/lodash, so using _.values and _.object can simplify the looping through arguments array as above.

Connor answered 3/7, 2013 at 13:5 Comment(7)
Thanks for your time, I've found a solution after searching on RequireJS website. See my answer.Marabout
The only question I have is: using require(_.values(Config), ...) is async code, right? That means that I need to use the callback style when require ends, right?Marabout
Yes, and you are using the callback syntax in your answer. require(_.values(Config), function () { is roughly equivalent to require(dynModules, function(){ in mine. Both use an array of strings as the first parameter and supply a function for callback as the 2nd parameter.Connor
Note that this will break requireJS's compiling functionality unless you set { findNestedDependencies: true } in your options.Tiddlywinks
what if i want to add - require("somemodule"); where somemodule is a json fileHandicapped
is 'dyn_modules' present in the node_modules folder? I am getting the same error but I don't know how to use the above answer to resolve it !Kenweigh
But why does the example tell me to use require('requirejs') if it fails?Stillhunt
M
8

Answering to myself. From the RequireJS website:

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

This fails because requirejs needs to be sure to load and execute all dependencies before calling the factory function above. [...] So, either do not pass in the dependency array, or if using the dependency array, list all the dependencies in it.

My solution:

// 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'
    };
});

The loader:

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

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

        // Use deps...
    });
});
Marabout answered 3/7, 2013 at 13:6 Comment(4)
Nope, modules is supposed to be defined in another module, i.e. a configuration file. It works fine btw.Marabout
looks like the key is still the conversion from CommonJS synchronous syntax to the RequireJS callback syntax.Connor
@explunit can you explain better what do you mean? I just need to make some modules (for the browser) available inside Jade (as helpers), and I need this to be dynamic.Marabout
updated my answer to clarify. Both answers will work, just wanted to explain for any future readers what's really happening with RequireJS behind the scenes.Connor

© 2022 - 2024 — McMap. All rights reserved.