Create resilient require mechanism in Node.js - node.js

I have an application and I want it to be resilient against missing dependencies. Some sort of fallback mechanism, in case a dependency is missing or corrupted.
So I have this, which monkey-patches require() and in theory will re-install a dependency if it cannot be loaded the first time:
const cp = require('child_process');
const Mod = require('module');
const req = Mod.prototype.require;
Mod.prototype.require = function (d) {
try {
return req.apply(this, arguments);
}
catch (e) {
if (/^[A-Za-z]/.test(String(d))) {
console.error(`could not require dependency "${d}".`);
try {
var actualDep = String(d).split('/')[0];
console.error(`but we are resilient and we are attempting to install "${actualDep}" now.`);
cp.execSync(`cd ${_suman.projectRoot} && npm install ${actualDep}`);
return req.apply(this, arguments);
}
catch (err) {
console.error('\n', err.stack || err, '\n');
throw e;
}
}
else {
throw e;
}
}
};
but the above patch is not working as expected. The problem is that the initial try statement:
return req.apply(this, arguments);
it mostly works, but it's doing some strange things..for example, it starts installing
bufferutil
utf-8-validate
this doesn't make sense to me, because require() works without any errors normally, but when using the patch, require() suddenly fails.
Anyone know what might be going on? Super weird.
(Note what it's doing is only attempting to re-install dependencies that start with a character [a-zA-z], it won't attempt to install a dependency that starts with './' etc.)

Related

node require.cache delete does not result in reload of module

I'm writing tests for my npm module.
These tests require to install multiple versions of an npm module in order to check if the module will validate them as compatible or incompatible.
Somehow all uncache libraries or function I found on stackoverflow or the npm database are not working..
I install/uninstall npm modules by using my helper functions:
function _run_cmd(cmd, args) {
return new Promise((res, rej) => {
const child = spawn(cmd, args)
let resp = ''
child.stdout.on('data', function (buffer) {
resp += buffer.toString()
})
child.stdout.on('end', function() {
res(resp)
})
child.stdout.on('error', (err) => rej(err))
})
}
global.helper = {
npm: {
install: function (module) {
return _run_cmd('npm', ['install', module])
},
uninstall: function (module) {
decacheModule(module)
return _run_cmd('npm', ['uninstall', module])
}
}
}
This is my current decache function which should clear all modules caches (I tried others, including npm modules none of them worked)
function decacheModules() {
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key]
})
}
I am installing multiple versions of the less module (https://www.npmjs.com/package/less)
In my first test I am installing a deprecated version which does not have a render-function.
In some other test I am installing an up-to-date version which has the render-function. Somehow if I test for that function that test does fail.
If I skip the first test the other test succeeds. (render-function exists).
This makes me believe that the deletion of require.cache has no impact...
I am using node v4.2.4.
If there is a reference to the old module:
var less = require('less');
Clear module cache will not lead that reference cleared and reload the module.
For that to work, at least you don't store module to variable, use the "require('less')" in place everywhere.

Path resolution issues with NPM modules vs apps

I am writing a module that will be used as a dependency for Node.js apps. In some cases, it will be a dependency of a dependency, which means the path resolution will change, and currently I am having a problem with that. Namely, when my module is a dependency of a dependency, my module will still look to the app root, not the root of the dependency.
I think the shortest way to ask a question on how to solve this is to find out the best way to determine if the module is a dependency or not.
So the way to do that would be to get the __dirname of the file in the index of my module and then navigate up one directory to see if that directory is named node_modules.
Is there a better way to do this? Is there a better way to determine if the code being invoked is being invoked from a dependency of the app or from the app itself?
Visually speaking, it looks like this
--app
---/node_modules
-----/A
-----/B
my module is called A
A could be used by app, or it could be used by B
if it's used by app, I can use the app-root-path module to quickly determine the root. But if my module is used by B, how will I know that? It will matter for resolving paths.
Here is the entiriety of the code in my module:
var appRoot = require('app-root-path');
var path = require('path');
var configs = {};
function checkIfDependency(){
var temp = path.resolve(path.normalize(__dirname + '/../'));
return path.basename(temp) === 'node_modules';
}
module.exports = function (identifier, pathToProvider) {
if (String(identifier).indexOf('*') < 0) {
throw new Error('did not pass in an identifier to univ-config');
}
if (configs[identifier]) {
return configs[identifier];
}
else {
if (pathToProvider) {
try {
var configPath;
if (path.isAbsolute(pathToProvider)) { //consumer of this lib has been so kind as to provide an absolute path, the risk is now yours
configPath = path.normalize(pathToProvider);
}
else if(checkIfDependency()){ //univ-config is being invoked from a dependency
configPath = path.normalize(??? + '/' + pathToProvider);
}
else{ //univ-config is being invoked from an app
configPath = path.normalize(appRoot + '/' + pathToProvider);
}
var f = require(configPath);
return configs[identifier] = f();
}
catch (err) {
throw new Error('univ-config could not resolve the path to your config provider module - given as:' + pathToProvider);
}
}
else {
throw new Error('no config matched the identifier but no path to config provider was passed to univ-config');
}
}
};

How to check in node if module exists and if exists to load?

I need to check if file/(custom)module js exists under some path. I tried like
var m = require('/home/test_node_project/per');
but it throws error when there is no per.js in path.
I thought to check with
fs if file exists but I don't want to add '.js' as suffix if is possible to check without that.
How to check in node if module exists and if exists to load ?
Require is a synchronous operation so you can just wrap it in a try/catch.
try {
var m = require('/home/test_node_project/per');
// do stuff
} catch (ex) {
handleErr(ex);
}
You can just try to load it and then catch the exception it generates if it fails to load:
try {
var foo = require("foo");
}
catch (e) {
if (e instanceof Error && e.code === "MODULE_NOT_FOUND")
console.log("Can't load foo!");
else
throw e;
}
You should examine the exception you get just in case it is not merely a loading problem but something else going on. Avoid false positives and all that.
It is possible to check if the module is present, without actually loading it:
function moduleIsAvailable (path) {
try {
require.resolve(path);
return true;
} catch (e) {
return false;
}
}
Documentation:
require.resolve(request[, options])
Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.
Note: Runtime checks like this will work for Node apps, but they won't work for bundlers like browserify, WebPack, and React Native.
You can just check is a folder exists by using methods:
var fs = require('fs');
if (fs.existsSync(path)) {
// Do something
}
// Or
fs.exists(path, function(exists) {
if (exists) {
// Do something
}
});

Node.js customize require function globally

I am trying to modify require like this
require = function (path) {
try {
return module.require(path);
} catch (err) {
console.log(path)
}
}
However, scope of this modification is only in the current module. I want to modify it globally, so every module that is required by this module will also get the same copy of require function.
Basically, I want to catch SyntaxError to know which file has problem. I can't seem to find any other alternative. If I put module.require in try/catch block, I'll be able to get the file name which caused SyntaxError.
I managed to solve it by modifying prototype function require of Module class. I put this in the main script and its available to all the required modules.
var pathModule = require('path');
var assert = require('assert').ok;
module.constructor.prototype.require = function (path) {
var self = this;
assert(typeof path === 'string', 'path must be a string');
assert(path, 'missing path');
try {
return self.constructor._load(path, self);
} catch (err) {
// if module not found, we have nothing to do, simply throw it back.
if (err.code === 'MODULE_NOT_FOUND') {
throw err;
}
// resolve the path to get absolute path
path = pathModule.resolve(__dirname, path)
// Write to log or whatever
console.log('Error in file: ' + path);
}
}
Why don't you use a try-catch block inside your code and once an error occurs to check the stack trace. Check out these links
How to print a stack trace in Node.js?
http://machadogj.com/2013/4/error-handling-in-nodejs.html

Check if package is available?

I'd like to know if require(pkgName) would succeed, that is, if the package with name pkgName is available. How do I best test for that?
I know I can do
try {
require(pkgName)
} catch (err) {
available = false
}
but this swallows load errors, and I'd also like to avoid require'ing the package if possible.
The best way is to use require.resolve(), since it does not actually run any code contained in the module.
Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.
Just like require, resolve throws if the module is not found, so it needs to be wrapped in try/catch.
Don't think you can work around using require, but you can specifically check for MODULE_NOT_FOUND errors:
function moduleExists(mod) {
try {
require(mod);
} catch(e) {
if (e.code === 'MODULE_NOT_FOUND')
return false;
throw e;
};
return true;
}
I'm showing with the "swig" module. There might be better ways, but this works for me.
var swig;
try {
swig = require('swig');
} catch (err) {
console.log(" [FAIL]\t Cannot load swig.\n\t Have you tried installing it? npm install swig");
}
if (swig != undefined) {
console.log(" [ OK ]\t Module: swig");
}

Resources