同步安装后,节点找不到某些模块

时间:2018-11-12 21:03:14

标签: javascript node.js asynchronous npm npm-install

我有一个脚本,它可以在启动时同步安装非内置模块,就像这样

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...

当我卸载模块时,它们将在我再次启动服务器时成功安装。但是,当我卸载使用功能Cannot find module的列表的前两个模块时,脚本开始引发requireOrInstall错误。没错,仅当脚本必须安装前两个模块时才发生错误,而仅第二个模块需要安装时才发生错误。

在此示例中,当我卸载find-free-port时,将抛出该错误,除非,我将其require向下移动至少一个位置¯\ _(•_•) _ /¯

我还尝试过在同步安装后直接添加一个延迟,以通过以下两行为其提供更多的呼吸时间:

var until = new Date().getTime() + 1000
while (new Date().getTime() < until) {}

暂停在那里。它没有解决任何问题。

@velocityzen came with the idea to check the cache,我现在已将其添加到脚本中。它没有显示任何异常。

@vaughan's comment on another question指出,当需要两次模块时,会发生此确切错误。我已将脚本更改为使用require.resolve(),但错误仍然存​​在。

有人知道是什么原因吗?

修改

自从回答问题以来,我正在发布一线(139个字符!)。它没有全局定义child_modules,没有最后一个try-catch,并且在控制台中未记录任何内容:

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)}

函数的名称为req(),可以像在@alex-rokabilis' answer中那样使用。

3 个答案:

答案 0 :(得分:1)

我认为您最好的选择是:

  • (很难)在全球安装软件包,而不是在本地安装
  • (最佳解决方案?)在安装时以及需要时定义新的“软件包存储库安装”

首先,您可以考虑使用npm-programmatic软件包。

然后,您可以使用以下方式定义存储库路径:

const PATH='/tmp/myNodeModuleRepository';

然后,将您的安装说明替换为:

const npm = require('npm-programmatic');
npm.install(`${module}`, {
        cwd: PATH,
        save:true
}

最终,将您的故障回复要求说明替换为:

return require(module, { paths: [ PATH ] });

如果仍然无法正常工作,则可以更新require.cache变量,例如使模块失效,可以执行以下操作:

delete require.cache[process.cwd() + 'node_modules/bluebird/js/release/bluebird.js'];

在加载之前,您可能需要手动对其进行更新,以添加有关新模块的信息。

答案 1 :(得分:1)

似乎require之后的npm install操作需要一定的延迟。 同样,在Windows中问题更严重,如果模块需要为npm installed,它将总是失败。 就像在特定的事件快照中,已经知道需要什么模块,不需要什么模块。也许这就是在评论中提到require.cache的原因。不过,我建议您检查以下两种解决方案。

1)使用延迟

const cp = require("child_process");

const requireOrInstall = async module => {
  try {
    require.resolve(module);
  } catch (e) {
    console.log(`Could not resolve "${module}"\nInstalling`);
    cp.execSync(`npm install ${module}`);
    // Use one of the two awaits below
    // The first one waits 1000 milliseconds
    // The other waits until the next event cycle
    // Both work
    await new Promise(resolve => setTimeout(() => resolve(), 1000));
    await new Promise(resolve => setImmediate(() => resolve()));
    console.log(`"${module}" has been installed`);
  }
  console.log(`Requiring "${module}"`);
  try {
    return require(module);
  } catch (e) {
    console.log(require.cache);
    console.log(e);
  }
}

const main = async() => {
  const http = require("http");
  const path = require("path");
  const fs = require("fs");
  const ffp = await requireOrInstall("find-free-port");
  const express = await requireOrInstall("express");
  const socket = await requireOrInstall("socket.io");
}

main();

await始终需要一个承诺,但是并不需要显式地创建一个承诺,因为await会将承诺中没有等待的内容包装起来。 >

2)使用集群

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);
    process.exit(1007);
  }
}

const cluster = require("cluster");

if (cluster.isMaster) {
  cluster.fork();
  cluster.on("exit", (worker, code, signal) => {
    if (code === 1007) {
      cluster.fork();
    }
  });
} else if (cluster.isWorker) {
  // The real work here for the worker

  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");

  process.exit(0);
}

这里的想法是在缺少模块的情况下重新运行该过程。这样,我们就可以完全复制手册npm install,以便您猜对了!同样,似乎 more 是同步的,而不是第一种选择,但是有点复杂。

答案 2 :(得分:0)

cp.execSync是一个异步调用,因此请尝试检查该模块的回调函数中是否已安装该模块。我已经尝试过了,现在安装干净了:

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`)
            try {
                return require(module)
            } catch (e) {
                console.log(require.cache)
                console.log(e)
            }
        })

    }
    console.log(`Requiring "${module}"`)

}

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')

  

当node_modules尚不可用时:   enter image description here

     

当node_modules可用时:   enter image description here