Как реализовать автоматическую перезагрузку процесса нативными средствами при изменении кода?nodejs-41

1. Основной подход с использованием fs.watch

Node.js предоставляет нативный модуль fs для отслеживания изменений файлов. Вот базовая реализация:

const fs = require('fs');
const { spawn } = require('child_process');

let appProcess = startProcess();

function startProcess() {
  const child = spawn('node', ['app.js'], { stdio: 'inherit' });

  child.on('exit', (code) => {
    if (code === 100) { // Код 100 для перезагрузки
      console.log('Restarting process...');
      appProcess = startProcess();
    }
  });

  return child;
}

fs.watch('app.js', (eventType) => {
  if (eventType === 'change') {
    console.log('File changed, restarting...');
    appProcess.kill();
  }
});

2. Улучшенная версия с отслеживанием всей директории

Для реальных проектов нужно отслеживать всю директорию и вложенные файлы:

const path = require('path');
const fs = require('fs');

function watchDirectory(dir, callback) {
  fs.watch(dir, { recursive: true }, (event, filename) => {
    if (filename && !filename.includes('node_modules')) {
      const ext = path.extname(filename);
      if (['.js', '.json', '.mjs'].includes(ext)) {
        callback();
      }
    }
  });
}

3. Обработка зависимостей и очистка кэша

Важно правильно очищать кэш модулей при перезагрузке:

function restartApp() {
  // Очищаем кэш всех файлов кроме node_modules
  Object.keys(require.cache).forEach((modulePath) => {
    if (!modulePath.includes('node_modules')) {
      delete require.cache[modulePath];
    }
  });

  // Перезапускаем основной модуль
  require('./app');
}

4. Полноценная реализация с debounce

Добавим debounce чтобы избежать множественных перезагрузок:

const debounce = (fn, delay) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};

const restart = debounce(() => {
  console.log('Restarting server...');
  process.send({ type: 'RESTART' });
}, 300);

watchDirectory('.', restart);

5. Использование кластера для плавного перезапуска

Более продвинутый вариант с кластером:

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // Запускаем workers
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // Отслеживаем изменения файлов
  fs.watch('.', { recursive: true }, (event, filename) => {
    if (filename.endsWith('.js')) {
      Object.values(cluster.workers).forEach((worker) => {
        worker.send('restart');
      });
    }
  });
} else {
  require('./app');

  process.on('message', (msg) => {
    if (msg === 'restart') {
      process.exit(100); // Специальный код для перезагрузки
    }
  });
}

6. Оптимизации и подводные камни

  1. Игнорирование node_modules: Всегда исключайте папку node_modules из наблюдения
  2. Debounce: Используйте задержку 200-500мс для группировки изменений
  3. Очистка ресурсов: Убедитесь, что все таймеры, соединения и т.д. корректно закрываются
  4. Обработка ошибок: Добавьте обработку ошибок при запуске нового процесса

7. Альтернативные подходы

Хотя вопрос про нативные средства, стоит упомянуть популярные решения:

  • nodemon: Самый популярный инструмент
  • pm2: Продвинутый менеджер процессов с hot-reload
  • node-dev: Легковесная альтернатива

Резюмируем:

Node.js предоставляет все необходимые инструменты (fs.watch, child_process, cluster) для реализации автоматической перезагрузки. Ключевые аспекты - правильное отслеживание файлов, очистка кэша модулей и грамотная обработка перезапуска. Для production-решений лучше использовать специализированные инструменты, но понимание нативной реализации важно для глубокого понимания работы Node.js.