Узел не может найти определенные модули после синхронной установки - PullRequest
0 голосов
/ 13 ноября 2018

У меня есть скрипт, который синхронно устанавливает не встроенные модули при запуске, который выглядит следующим образом

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

Пауза была там.Это ничего не исправило.

@ speedzen пришло с идеей проверить кэш , который я сейчас добавил в скрипт.Он не показывает ничего необычного. Комментарий

@ vaughan к другому вопросу отметил, что эта точная ошибка возникает, когда требуется модуль дважды.Я изменил сценарий для использования 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 'ответ .

Ответы [ 3 ]

0 голосов
/ 19 ноября 2018

Кажется, что операция require после npm install требует определенной задержки. Кроме того, проблема усугубляется в Windows, она будет всегда сбой , если модуль должен быть npm installed. Это как на конкретном событии снимок уже известно, какие модули могут потребоваться, а какие нет. Вероятно, поэтому в комментариях упоминалось require.cache. Тем не менее, я предлагаю вам проверить 2 следующих решения.

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, так что, как вы думаете, оно работает! Также кажется, что больше синхронно, скорее первый вариант, но немного сложнее.

0 голосов
/ 19 ноября 2018

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

0 голосов
/ 19 ноября 2018

Я думаю, ваш лучший вариант:

  • (некрасиво) для установки пакета глобально, а не локально
  • (лучшее решение?) Для определения ВАШЕЙ новой 'установки репозитория пакетов', при установке, И когда требуется

Во-первых, вы можете рассмотреть возможность использования 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'];

Возможно, вам придется обновить его вручную, чтобы добавить информацию о вашем новом модуле, прежде чем загружать его.

...