const exec = require('child_process').spawnSync
try {
require.resolve('lodash.assign')
} catch (e) {
exec('npm', ['install', 'lodash.assign', '--save'], {
stdio: 'inherit'
})
}
//throw Cannot find module ‘lodash.assign’
require('lodash.assign')
Everything is ok if i remove require.resolve.
Why this code does not work?
I think that what you're trying to do is install by command a dependency when this dependency does not exists. require.resolve() is more usefull when you try to have the location of the module.
Take a look into this piece of code, that is currently working:
var _;
function installDeps(cb) {
try {
_ = require('lodash');
return cb();
} catch(err) {
childProcess.exec('npm install --save lodash', function() {
_ = require('lodash');
cb();
});
}
}
installDeps(() => {
console.log(_.isObject([]));
});
The problem cames when I try make this code sync with a code like this:
var _;
try {
_ = require('lodash');
} catch(err) {
childProcess.execSync('npm install --save lodash');
_ = require('lodash');
}
console.log(_.isObject([]));
This second code will install the module, but the require('lodash') at the catch will say that the module does not exists, even when you can check that is installed. I think that this is due to the way npm works for installing modules, if you try to do it sync then it fails.
Also, you say that everything is ok if you remove require.resolve. My personal feeling is that you executed with the require.resolve(), it failed but installed the module, so the second time you run it the module is installed.
@HSunboy In short, this occurs due to caching of module paths that were attempted to be loaded.
Long version:
Calling require.resolve('lodash.assign') the first time causes the internal stat cache to get initialized with a value of -2 for all the paths that were tried by the algorithm to load, the currently non-existing, lodash.assign (for example, /path/to/node_modules/lodash.assign -> -2, /path/to/node_modules/lodash.assign.js -> -2, /path/to/node_modules/lodash.assign.json -> -2, etc).
Later, even though you successfully install lodash.assign using spawnSync, when you try to require('lodash.assign'), the module system uses the cached values for all the attempted paths and thus fails to load the newly installed lodash.assign module.
In other words, this occurs since the the /path/to/node_modules/lodash.assign is checked against the internal stat cache and since there is an entry for that path, the path is never tested against the actual filesystem which is why, to the module system, it is as if lodash.assign was never installed.
I am unsure if this is a bug or the correct behaviour but if I were to guess, this is most definitely correct behaviour as there's no smart way to know when a stat cache entry should be invalidated in this case.
@HSunboy So, though there may be a better workaround, considering @zeusdeux's explanation you can use something like this:
const cp = require('child_process');
try {
cp.execSync("node -e require.resolve('lodash.assign')", { stdio: 'ignore' });
} catch (e) {
cp.spawnSync('npm', ['install', 'lodash.assign'], { stdio: 'inherit' });
}
require('lodash.assign');
I don't think Node ever officially supported installing npm modules during the execution of the program. Unless this is a common use case I don't think we should change the current behavior other than to perhaps document it.
This issue has been inactive for sufficiently long that it seems like perhaps it should be closed. Feel free to re-open (or leave a comment requesting that it be re-opened) if you disagree. I'm just tidying up and not acting on a super-strong opinion or anything like that.
@HSunboy So, though there may be a better workaround, considering @zeusdeux's explanation you can use something like this:
const cp = require('child_process'); try { cp.execSync("node -e require.resolve('lodash.assign')", { stdio: 'ignore' }); } catch (e) { cp.spawnSync('npm', ['install', 'lodash.assign'], { stdio: 'inherit' }); } require('lodash.assign');
This approach fails in my case. The process hangs there until I terminate it manually. I've also noticed that @HSunboy 's commit which references this issue did not pass the test.