5

I'm trying to create a require wrapper to load dependencies, but I found one thing difficult to make it works as the original require function. When the path is a relative one, the wrapper cannot resolve to the correct one since my loader and the caller files are not in a same folder. Here is a simplified description.

index.js
lib/
  loader.js
foo/
  bar.js
  baz.js

index.js

var loader = require('./lib/loader.js'),
    bar = require('./foo/bar.js');
bar(loader);

lib/loader.js

module.exports = function (path) {
  return require(path);
};

foo/bar.js

module.exports = function(loader) {
  var baz = loader('./baz.js');
  console.log(baz);
};

foo/baz.js

module.exports = 'baz';

Obviously, when the index.js is executed, baz.js file cannot be found. Is there any way to resolve to the correct file?

I've found a relative solution but it's not working.

Community
  • 1
  • 1
Rix
  • 1,688
  • 13
  • 20

3 Answers3

6

You can use module.require to call require from another module's context, so you could do this by passing that context into the loader method:

lib/loader.js

module.exports = function (moduleContext, path) {
  return moduleContext.require(path);
};

foo/bar.js

module.exports = function(loader) {
  var baz = loader(module, './baz.js');
  console.log(baz);
}
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • 1
    something along the lines of "module.parent.require" might also help – alex Jan 10 '14 at 02:23
  • @alex I was originally thinking the same way, but in the `loader` module, `module.parent` will always refer to index.js's module context as that's the file that `require`s it. So `parent` is the requirer, not the caller, which doesn't help here. – JohnnyHK Jan 10 '14 at 02:42
4

I agree with @JohnnyHK that passing the module context is a cool solution, but I still want to keep the calls simple. Finally I understand the answer I mentioned, and I get what I want.

loader.js

var getCaller, path;

path = require('path');

getCaller = function() {
  var stack, traceFn;
  traceFn = Error.prepareStackTrace;
  Error.prepareStackTrace = function(err, stack) {
    return stack;
  };
  stack = (new Error()).stack;
  Error.prepareStackTrace = traceFn;
  return stack[2].getFileName();
};

module.exports = function(file) {
  var base;
  base = path.dirname(getCaller());
  return require(path.resolve(base, file));
};

The getCaller function use error trace stack to get the filename of the caller of it's caller. I know it's a very tricky approach, and I don't recommend it as a common solution since its compatibility to different versions of Node.js has not been tested.

Note

This loader is used for preparing dependencies, so it doesn't need to iterate node_modules folders. The only two cases are relative path and absolute path, and they can all be correctly processed by this loader.

Community
  • 1
  • 1
Rix
  • 1,688
  • 13
  • 20
  • 1
    What is the performance like using this? I imagine relying on a error and passing a stack trace is sub-optimal – Greg K Jul 19 '18 at 13:34
1

Using __dirname

__dirname may be what you need here. You can use it to turn a relative path into an absolute path which will work with require.

var resolve = require('path').resolve;

var relativePathToModule = './a/b/c';

// resolve is not strictly necessary, but it does clean up the path for you.
var absolutePathToModule = resolve(__dirname + relativePathToModule);

You can pass absolutePathToModule to a require in any module and it will always find the right module.

Note that __dirname is the absolute path to the directory of the module in which it is used.

Using require.resolve

Alternatively, you can use the require machinery itself to resolve the module for you:

var relativePathToModule = './a/b/c';

var absolutePathToModule = require.resolve(relativePathToModule);

This has the benefit of throwing when no module can be resolved, and omitting the __dirname.

Note

These don't quite do what you want, but are close to the way require resolves paths. Both require and __dirname belong to the module they are used in, so anything else will mean passing the context of a module around to use the above machinery in another module (see JohnnyHK's answer).

qubyte
  • 17,558
  • 3
  • 30
  • 32
  • That would be my last option. My goal is to construct a replacement of require that can handle all valid situations for original require. However if I use your solution, I need to always use absolute path, which is not my ideal outcome. – Rix Jan 09 '14 at 14:44
  • `require` itself is a method of the module it is used in, and actually uses a similar mechanism to that which it uses to build `__dirname` in order to resolve relative paths. You may try looking at `module.parent`, but I think that's set by the *first* require only, making it useless for your purposes. – qubyte Jan 09 '14 at 15:05
  • 2
    By the way, what is your reason for replacing require? You may be succumbing to [NIH syndrome](http://en.wikipedia.org/wiki/Not_Invented_Here#In_computing). – qubyte Jan 09 '14 at 15:07
  • I added another way to do this, but again it must be called from the originating module. – qubyte Jan 09 '14 at 15:14
  • I just want to create a dependency loader just like [c9/architect](https://github.com/c9/architect) but a little different. It doesn't require any configuration but directly called in any file, just like require. It would be used all the time in my project in the global namespace so I want it to be as simple as it can. @MarkS.Everitt – Rix Jan 10 '14 at 04:28