0
votes

I am trying to write some JS modules which should be usable in both a browser and node.js environment.

To facilitate this I'm writing these modules as AMD modules, and using requirejs in the Node.js environment to provide the define() function as well as the ability to load these modules.

However, I've encountered some behavior I do not understand.

I made a SSCCE that illustrates the problem:

├── bar.js
├── node_modules
│   └── foo
│       ├── foo.js
│       ├── index.js
│       ├── node_modules
│       │   └── requirejs
│       │       ├── bin
│       │       │   └── r.js
│       │       ├── package.json
│       │       ├── README.md
│       │       └── require.js
│       └── package.json
└── test.js

foo is a node module which wraps the AMD module foo.js

node_modules/foo/foo.js

define([], function() {
    return function() {
        console.log("This is foo!");
    };
});

node_modules/foo/index.js

var requirejs = require('requirejs');

requirejs.config({
    baseUrl: __dirname,
    nodeRequire: require
});

module.exports = requirejs('foo');

bar.js is another AMD module:

define([], function() {
    return function() {
        console.log("This is bar!");
    };
});

test.js is a script that wants to use both foo and bar:

var requirejs=require('requirejs');

requirejs.config(
    {
        baseUrl: __dirname, 
        nodeRequire: require
    }
);

var foo=require("foo");

foo();

var bar=requirejs("bar");

console.log("bar is:",bar);

If I run test.js, I get this:

This is foo!
bar is: undefined
/home/harmic/tmp/test_rjs/node_modules/foo/node_modules/requirejs/bin/r.js:393
        throw err;
              ^
Error: Mismatched anonymous define() module: function () {
        return function() {
                console.log("This is bar!");
        };
}
http://requirejs.org/docs/errors.html#mismatch
    at makeError (/home/harmic/tmp/test_rjs/node_modules/foo/node_modules/requirejs/bin/r.js:418:17)
    at intakeDefines (/home/harmic/tmp/test_rjs/node_modules/foo/node_modules/requirejs/bin/r.js:1501:36)
    at null._onTimeout (/home/harmic/tmp/test_rjs/node_modules/foo/node_modules/requirejs/bin/r.js:1699:25)
    at Timer.listOnTimeout (timers.js:119:15)

There are two things that don't make sense to me here.

  1. In test.js, the call to requirejs("bar") returns undef. In fact it appears that the loading of the module is deferred, as if there were some circular dependancy going on, because it is only after it has returned that the module definition for bar is being executed.

  2. Why does it think this is an anonymous define() ? My use case does not appear to meet the criteria in the given URL.

To supress the second issue I tried naming the define in bar.js, like this:

define('bar', [], function() {
...

That works in the sense that the exception is gone, but requirejs("bar") still returns undef.

The example is a simplified version of what I am trying to do - basically I will have a number of modules, which will contain some common components that can be used in browser and in node, and some node specific components that will only be used in node. There will be dependancies between the modules.

If there is some better way of doing this then I'm open to that also.

2
requirejs("foo") is requesting a module already loaded in memory. Try requirejs(["foo", "bar"], (foo, bar) => { foo() + bar() }); - Corey Alix
@CoreyAlix according to the requirejs node docs it says that under node, you can require modules synchronously using requirejs("foo"). This is pretty much a requirement when wrapping AMD modules for use in Node, since Node require() is synchronous. - harmic
Where is bar.js located relative to your other files? - Louis
@Louis in the project root dir, as shown in the first diagram. - harmic
Ah, well, that's embarrassing for me. Because by the time I got to the end of your question, I had forgotten that bit at the start. - Louis

2 Answers

1
votes

I've recreated locally the code and hierarchy you show in the question but I'm not able to get the exact error message you report getting. I'll say that looking at what you show in the question, I do not get how your code could result in that error message. When I run your code, the error I do get is:

This is foo!
/tmp/t1/node_modules/requirejs/bin/r.js:2604
                    throw err;
                    ^

Error: Tried loading "bar" at /tmp/t1/node_modules/foo/bar.js then tried node's require("bar") and it failed with error: Error: Cannot find module 'bar'
    at /tmp/t1/node_modules/requirejs/bin/r.js:2597:27

Which is exactly what I would expect: RequireJS running in node will try to use Node's own require if it cannot find a module through its own machinery.

Now, the problem you ran into is not a problem with running RequireJS in Node but a problem with how you use RequireJS. You could run into the exact same issue in a browser. The problem is that you are running requirejs.config twice, without using contexts so one config overwrites the other. (RequireJS is able to merge configs but the configs you are using are such that they'll overwrite one another.) I can get the code to run by changing test.js so that:

  1. The configuration of RequireJS uses a context value.

  2. I save the return value of requirejs.config and use this to require modules.

The end result is:

var requirejs=require('requirejs');

var r = requirejs.config(
    {
        context: "me",
        baseUrl: __dirname,
        nodeRequire: require
    }
);

var foo=require("foo");

foo();

var bar= r("bar");

console.log("bar is:",bar);

Ideally, the code in index.js should also use a context.

This being said, I've written code that runs in Node and the browser as AMD modules for years. It is rare that I need to load AMD modules with RequireJS in Node. One rare case where I'd want to do it is if I'm testing how a module gets its configuration through module.config. I've never ever found the need to use amdefine. What I do use when I want to load AMD modules in Node is amd-loader (aka node-amd-loader). It just hooks into Node's module loading machinery to make it able to load AMD modules. I guess the downside of amd-loader is that projects that want to use your code then have to depend on having a loader installed like amd-loader.

0
votes

In the end I did not get any satisfactory answer to this problem.

I have worked around it by using amdefine when using these modules in the node.js environment, instead of trying to use requirejs.

The main drawback to this approach is that you have to add some boilerplate to the front of all the AMD modules so that they will load amdefine if needed, but other than that, amdefine is working for my use case at least.

I also found the UMD project, which provides some other alternatives.