I've got a script that synchronously installs non-built-in modules at startup that looks like this
const cp = require('child_process')
function requireOrInstall (module) {
try {
require.resolve(module)
} catch (e) {
console.log(`Could not resolve "${module}"\nInstalling`)
cp.execSync(`npm install ${module}`)
console.log(`"${module}" has been installed`)
}
console.log(`Requiring "${module}"`)
try {
return require(module)
} catch (e) {
console.log(require.cache)
console.log(e)
}
}
const http = require('http')
const path = require('path')
const fs = require('fs')
const ffp = requireOrInstall('find-free-port')
const express = requireOrInstall('express')
const socket = requireOrInstall('socket.io')
// List goes on...
When I uninstall modules, they get installed successfully when I start the server again, which is what I want. However, the script starts throwing Cannot find module
errors when I uninstall the first or first two modules of the list that use the function requireOrInstall
. That's right, the errors only occur when the script has to install either the first or the first two modules, not when only the second module needs installing.
In this example, the error will be thrown when I uninstall find-free-port, unless I move its require
at least one spot down ¯\_(• _ •)_/¯
I've also tried adding a delay directly after the synchronous install to give it a little more breathing time with the following two lines:
var until = new Date().getTime() + 1000
while (new Date().getTime() < until) {}
The pause was there. It didn't fix anything.
@velocityzen came with the idea to check the cache, which I've now added to the script. It doesn't show anything out of the ordinary.
@vaughan's comment on another question noted that this exact error occurs when requiring a module twice. I've changed the script to use require.resolve()
, but the error still remains.
Does anybody know what could be causing this?
Edit
Since the question has been answered, I'm posting the one-liner (139 characters!). It doesn't globally define child_modules
, has no last try-catch
and doesn't log anything in the console:
const req=async m=>{let r=require;try{r.resolve(m)}catch(e){r('child_process').execSync('npm i '+m);await setImmediate(()=>{})}return r(m)}
The name of the function is req()
and can be used like in @alex-rokabilis' answer.
cp.execSync is an async call so try check if the module is installed in it's call back function. I have tried it, installation is clean now:
I think your best option is either:
First, you may consider using the npm-programmatic package.
Then, you may define your repository path with something like:
Then, replace your installation instruction with something like:
Eventually, replace your failback require instruction, with something like:
If it is still not working, you may update the require.cache variable, for instance to invalide a module, you can do something like:
You may need to update it manually, to add information about your new module, before loading it.
It seems that the
require
operation after annpm install
needs a certain delay. Also the problem is worse in windows, it will always fail if the module needs to benpm installed
. It's like at a specific event snapshot is already known what modules can be required and what cannot. Probably that's whyrequire.cache
was mentioned in the comments. Nevertheless I suggest you to check the 2 following solutions.1) Use a delay
await
always needs a promise to work with, but it's not needed to explicitly create one asawait
will wrap whatever it is waiting for in a promise if it isn't handed one.2) Use a cluster
The idea here is to re-run the process in case of a missing module. This way we fully reproduce a manual
npm install
so as you guess it works! Also it seems more synchronous rather the first option, but a bit more complex.