JS Tower
Node.jsМодуль 3: Файловая система

Работа с директориями

Создание, удаление и обход директорий

Цель урока

В этом уроке ты научишься:

  • Создавать и удалять директории
  • Читать содержимое директорий
  • Рекурсивно обходить файловую систему

Создание директорий

Одна директория

const fs = require('fs/promises');

await fs.mkdir('new-folder');

Вложенные директории

// recursive: true создаёт все промежуточные папки
await fs.mkdir('path/to/deep/folder', { recursive: true });

С проверкой существования

async function ensureDir(dir) {
  try {
    await fs.mkdir(dir, { recursive: true });
  } catch (error) {
    if (error.code !== 'EEXIST') throw error;
  }
}

Удаление директорий

Пустая директория

await fs.rmdir('empty-folder');

Директория с содержимым

// Node.js 14.14+
await fs.rm('folder', { recursive: true, force: true });

// force: true не выбрасывает ошибку если папки нет

Чтение содержимого

Простой список

const files = await fs.readdir('folder');
console.log(files);  // ['file1.txt', 'file2.txt', 'subfolder']

С информацией о типе

const entries = await fs.readdir('folder', { withFileTypes: true });

for (const entry of entries) {
  if (entry.isFile()) {
    console.log('Файл:', entry.name);
  } else if (entry.isDirectory()) {
    console.log('Папка:', entry.name);
  }
}

Рекурсивный обход

Все файлы

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

async function getAllFiles(dir) {
  const files = [];
  const entries = await fs.readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    
    if (entry.isDirectory()) {
      files.push(...await getAllFiles(fullPath));
    } else {
      files.push(fullPath);
    }
  }
  
  return files;
}

const allFiles = await getAllFiles('./src');
console.log(allFiles);

С фильтрацией

async function getFilesByExtension(dir, ext) {
  const files = await getAllFiles(dir);
  return files.filter(file => path.extname(file) === ext);
}

const jsFiles = await getFilesByExtension('./src', '.js');

Генератор (эффективно по памяти)

async function* walkDir(dir) {
  const entries = await fs.readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    
    if (entry.isDirectory()) {
      yield* walkDir(fullPath);
    } else {
      yield fullPath;
    }
  }
}

// Использование
for await (const file of walkDir('./src')) {
  console.log(file);
}

Наблюдение за изменениями

const fs = require('fs');

const watcher = fs.watch('folder', { recursive: true }, (eventType, filename) => {
  console.log(`${eventType}: ${filename}`);
});

// Остановка наблюдения
watcher.close();

chokidar (рекомендуется)

npm install chokidar
const chokidar = require('chokidar');

const watcher = chokidar.watch('src', {
  ignored: /node_modules/,
  persistent: true
});

watcher
  .on('add', path => console.log(`Добавлен: ${path}`))
  .on('change', path => console.log(`Изменён: ${path}`))
  .on('unlink', path => console.log(`Удалён: ${path}`));

Практические примеры

Копирование директории

async function copyDir(src, dest) {
  await fs.mkdir(dest, { recursive: true });
  const entries = await fs.readdir(src, { withFileTypes: true });
  
  for (const entry of entries) {
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    
    if (entry.isDirectory()) {
      await copyDir(srcPath, destPath);
    } else {
      await fs.copyFile(srcPath, destPath);
    }
  }
}

Размер директории

async function getDirSize(dir) {
  let size = 0;
  const entries = await fs.readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    
    if (entry.isDirectory()) {
      size += await getDirSize(fullPath);
    } else {
      const stats = await fs.stat(fullPath);
      size += stats.size;
    }
  }
  
  return size;
}

const size = await getDirSize('./node_modules');
console.log(`Размер: ${Math.round(size / 1024 / 1024)} MB`);

Практика

Задание 1: Список файлов

Задача: Выведи все .js файлы в директории.

Решение:

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

async function listJsFiles(dir) {
  const entries = await fs.readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    if (entry.isFile() && path.extname(entry.name) === '.js') {
      console.log(entry.name);
    }
  }
}

Задание 2: Очистка директории

Задача: Удали все файлы в директории, но оставь папки.

Решение:

async function clearFiles(dir) {
  const entries = await fs.readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    if (entry.isFile()) {
      await fs.unlink(path.join(dir, entry.name));
    }
  }
}

Проверь себя

  1. Как создать вложенные директории?
  2. Как удалить директорию с содержимым?
  3. Что возвращает readdir с withFileTypes: true?