Spoiler: the reason I am trying to resolve paths is to load fresh modules runtime in polpetta. However, this might be a bad practice.
@WebReflection Lest I commit malpractice, I must tell you this is a terrible idea. Now warned, rock on with your bad self. Hack away :)Still, my point is that it might be handy to be able to resolve paths relatively form the invoker regardless the why, even if you should always ask yourself that ;)
This case is quite easy to misunderstand so I'll skip extra explanations now and put down some code.
relative.js
This example file aim is to log ASAP two different path resolutions: the one from the path, passing through theprocess.cwd()
, and the one from the relative.js
file itself.
Object.defineProperties(this, { parent: { get: function () { // used later on return module.parent; } }, resolve: { value: function (path) { // it will resolve from this file // not from the invoker return require.resolve(path); } } }); // path module resolves relatively // from the current process.cwd() console.log(require('path').resolve('.')); // require resolves relatively from // the current file console.log(require.resolve('./relative.js'));Running this from
node
terminal will most likely show something like:
require('./test/relative.js'); /Users/yourname/code /Users/yourname/code/test/relative.jsNeither logs or resolution are actually OK if we would like to resolve relatively from that path.
If we would like to use that module method, talking about the
resolve()
one, we cannot trust the current path.
// will throw an error require('./test/relative.js').resolve('./test/relative.js'); // will pass require('./test/relative.js').resolve('./relative.js');If we install that module through
npm
as global, or even local in some super folder, gosh knows where we should start the relative path resolution accordingly with the module itself, you know what I mean?
Being Relative To The Invoker Path
In order to be able to resolve relatively from the invoker, we need to know at least where is the invoker.Thankfully, this is easy but be aware of the caching problem:
// relative.js var path = require('path'), relativeDir = path.dirname( module.parent.filename ) ; this.resolve = function (module) { return require.resolve( path.join(relativeDir, module) ); };At this point we can invoke the method as expected without having erros, from the process folder.
// will log the right path require('./test/relative.js').resolve('./test/relative.js');Good, we are able to resolve module names, problem is ... only from the very first one that required
relative.js
due module caching so that module.parent
will be one, and only one, for any other module.
A Hacky Solution
In order to avoid the caching problem within the required module itself, I came up with such trick at the end of the file:// .. same content described above ... // remove the module itself from the cache delete require.cache[__filename];In this way every single module that will
require('./some-path/relative.js')
will have a fresh new version of that module so that module.parent
, and its filename
, will be always the right one: how cool is that?I am able to resolve relatively from any outer module its path the same way
require.resolve(path)
would do inside that module which is exactly needed and the goal of require-updated so that any module can use paths as if these were resolved from the file itself in order to require
some other file, relative, absolute, or globally installed.Still I believe there should be a better way to do this ... what do you say?
6 comments:
It's funny as I recently solved the exact opposite problem — of preventing specific file reloads by Node.js test runners and other reloaders to get slow initialization code to run only once.
In the vein of old #include guards and #pragma once's, named it Require Guard (https://github.com/moll/node-require-guard).
And just like your require-updated, with irony, it removes itself from the require cache. ^_^
nice :) ... this means there are at least two cases where such technique is needed .... and I believe more.
@izs said that without use cases it does not make sense to provide the ability to have such relative resolution ... well, let's see if somebody else has more cases.
One question though: I've used __filename instead of module.id, any reaso you would prefer the latter one?
No particular reason to prefer module.id other than possibly slightly better abstraction, but Node's lib/module.js itself quite freely uses paths, so...
Regarding having it in core, there's Issue #3285: expose a module.resolve method, or require.resolve(path, startPath).
I think in some case dropped cache can be a proper foot-gun as @izs said bot for these 2 cases, and probably others, is like: if you know what you are doing there's no harm.
I see this situation the equivalent of "stateless functions" VS "scope dependent closures" where first one could be easily serialized and deserialized without problems about the outer scope since independent.
In such case, it could be meaningful to exportOnce or something similar in order to flag the current module as "do not ever even bother caching it" which is basically what we both need, no matter what we do after that :)
This would be a cleaner solution, until that, I am actually happy that we can do this at runtime.
I actually think that not having it built-in (e.g with exportOnce), but having it possible imperatively, is the simpler and thereby cleaner solution.
Otherwise to solve the other side of what Require Guard does in a similar vein would need an exportTrulyForever to match exportOnce and that'll get quite messy.
The current common way to get a fresh closed over function sounds elegant enough — to have the caller invoke the exported function and to return a new function from there. Depending on requirers is something you and I only seldom need. :)
another use case: require()ing a whole file tree (http://npm.im/require-tree), currently you need (__dirname + '/path').
Post a Comment