Модуль path — работа с путями
Зачем нужно
Пути к файлам на Windows (C:\Users\admin\file.txt) и Unix (/home/user/file.txt) различаются разделителями и форматом. Модуль path строит и разбирает пути кроссплатформенно. Без него код работает на одной ОС и ломается на другой.
Где используется
- Построение путей к файлам и директориям (fs-операции)
- Извлечение имени файла, расширения, директории
- Resolve относительных путей в абсолютные
- Конфигурация сборщиков (Webpack, Vite)
- CLI-инструменты: работа с путями пользователя
Предпосылки
- Что такое Node.js — модульная система Node.js
- Понимание файловой системы (абсолютные vs относительные пути)
Импорт
// CommonJS
const path = require('path');
// ESM
import path from 'path';
path.join — соединение сегментов
const path = require('path');
// Соединяет сегменты с правильным разделителем для ОС
path.join('src', 'utils', 'helpers.js');
// Linux/macOS: 'src/utils/helpers.js'
// Windows: 'src\\utils\\helpers.js'
// Нормализует лишние разделители и ../
path.join('src', '..', 'dist', 'bundle.js');
// 'dist/bundle.js'
path.join('/home', 'user', '..', 'admin', 'file.txt');
// '/home/admin/file.txt'
// ❌ Не делай так — ломается на Windows
const bad = 'src' + '/' + 'file.js';
// ✅ Всегда join
const good = path.join('src', 'file.js');
path.resolve — абсолютный путь
const path = require('path');
// resolve строит АБСОЛЮТНЫЙ путь
// Обрабатывает аргументы СПРАВА НАЛЕВО,
// останавливается когда получит абсолютный путь
path.resolve('src', 'index.js');
// '/home/user/project/src/index.js' (от cwd)
path.resolve('/home/user', 'docs', 'readme.md');
// '/home/user/docs/readme.md'
// Если встречает абсолютный путь — начинает от него
path.resolve('src', '/tmp', 'file.txt');
// '/tmp/file.txt' (абсолютный /tmp перекрыл src)
// Без аргументов — возвращает cwd
path.resolve;
// '/home/user/project' (= process.cwd)
join vs resolve
const path = require('path');
// join: просто соединяет сегменты
path.join('/foo', 'bar', 'baz'); // '/foo/bar/baz'
// resolve: строит абсолютный путь от cwd
path.resolve('foo', 'bar', 'baz'); // '/home/user/project/foo/bar/baz'
// Главное отличие — resolve всегда возвращает абсолютный путь
path.join('foo', 'bar'); // 'foo/bar' (относительный)
path.resolve('foo', 'bar'); // '/home/user/project/foo/bar' (абсолютный)
Разбор пути
basename — имя файла
const path = require('path');
path.basename('/home/user/docs/report.pdf');
// 'report.pdf'
// Без расширения
path.basename('/home/user/docs/report.pdf', '.pdf');
// 'report'
path.basename('C:\\Users\\admin\\file.txt');
// 'file.txt' (работает и с Windows-путями)
dirname — директория
const path = require('path');
path.dirname('/home/user/docs/report.pdf');
// '/home/user/docs'
path.dirname('/home/user/docs/');
// '/home/user/docs' (trailing slash — та же директория)
path.dirname('src/utils/helpers.js');
// 'src/utils'
extname — расширение
const path = require('path');
path.extname('report.pdf'); // '.pdf'
path.extname('archive.tar.gz'); // '.gz' (только последнее)
path.extname('.gitignore'); // '' (нет расширения)
path.extname('README'); // '' (нет расширения)
path.extname('photo.JPG'); // '.JPG' (регистр сохраняется)
parse — полный разбор
const path = require('path');
const parsed = path.parse('/home/user/docs/report.pdf');
// {
// root: '/',
// dir: '/home/user/docs',
// base: 'report.pdf',
// ext: '.pdf',
// name: 'report'
// }
// Обратная операция — format
path.format({
dir: '/home/user/docs',
name: 'report',
ext: '.pdf'
});
// '/home/user/docs/report.pdf'
Утилиты
sep — разделитель ОС
const path = require('path');
path.sep;
// Linux/macOS: '/'
// Windows: '\\'
// Разбить путь на сегменты
'src/utils/helpers.js'.split(path.sep);
// Linux: ['src', 'utils', 'helpers.js']
normalize — нормализация
const path = require('path');
// Убирает лишние разделители и разрешает ..
path.normalize('/home//user/../admin/./docs');
// '/home/admin/docs'
path.normalize('src\\\\utils\\..\\lib');
// 'src/lib' (Linux) или 'src\\lib' (Windows)
isAbsolute — проверка абсолютного пути
const path = require('path');
path.isAbsolute('/home/user'); // true
path.isAbsolute('C:\\Users'); // true (Windows)
path.isAbsolute('./src'); // false
path.isAbsolute('src/index.js'); // false
relative — относительный путь между двумя
const path = require('path');
path.relative('/home/user/project', '/home/user/docs');
// '../docs'
path.relative('/home/user/project/src', '/home/user/project/dist');
// '../dist'
__dirname vs import.meta.url
// === CommonJS ===
// __dirname и __filename доступны напрямую
const path = require('path');
console.log(__dirname); // '/home/user/project/src'
console.log(__filename); // '/home/user/project/src/index.js'
const configPath = path.join(__dirname, '..', 'config.json');
// === ES Modules ===
// __dirname и __filename НЕ существуют
// Нужно получать из import.meta.url
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// import.meta.url:
// 'file:///home/user/project/src/index.mjs'
// fileURLToPath → '/home/user/project/src/index.mjs'
const configPath = path.join(__dirname, '..', 'config.json');
Практические примеры
Фильтрация файлов по расширению
const fs = require('fs/promises');
const path = require('path');
async function findByExtension(dir, ext) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const results = ;
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory) {
results.push(...await findByExtension(fullPath, ext));
} else if (path.extname(entry.name) === ext) {
results.push(fullPath);
}
}
return results;
}
// Найти все .js файлы
const jsFiles = await findByExtension('./src', '.js');
console.log(jsFiles);
// ['src/index.js', 'src/utils/helpers.js', 'src/lib/db.js']
Безопасная работа с пользовательскими путями
const path = require('path');
// ❌ ОПАСНО: пользователь может выйти за пределы директории
function unsafeServefile(userPath) {
return path.join('/var/www/public', userPath);
// userPath = '../../etc/passwd' → '/var/www/etc/passwd'
}
// ✅ БЕЗОПАСНО: проверяем что путь остаётся внутри базовой директории
function safeServeFile(baseDir, userPath) {
const resolved = path.resolve(baseDir, userPath);
const normalized = path.normalize(resolved);
if (!normalized.startsWith(path.normalize(baseDir))) {
throw new Error('Path traversal detected');
}
return normalized;
}
safeServeFile('/var/www/public', 'images/logo.png');
// '/var/www/public/images/logo.png' ✅
safeServeFile('/var/www/public', '../../etc/passwd');
// Error: Path traversal detected ✅
Webpack-конфигурация
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src', 'index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@utils': path.resolve(__dirname, 'src', 'utils')
}
}
};
Частые ошибки
- Конкатенация строк вместо join —
dir + '/' + fileломается на Windows. Всегдаpath.join - Путают join и resolve —
joinвозвращает относительный путь,resolve— абсолютный - __dirname в ESM — не существует, нужно
fileURLToPath(import.meta.url)+path.dirname - Не нормализуют пользовательский ввод — path traversal атака (
../../etc/passwd) - Хардкод разделителя —
'src\\utils'работает только на Windows
Практика
- Написать функцию, возвращающую расширение файла в нижнем регистре (
.JPG→.jpg) - Построить абсолютный путь к файлу
config.jsonв родительской директории - Написать функцию
safeJoin(base, userInput), защищённую от path traversal - Реализовать рекурсивный поиск всех файлов с заданным расширением
- Написать ESM-модуль с корректным получением
__dirname
Связанные темы
- fs — файловые операции (используют path для путей)
- process — process.cwd и рабочая директория
- http — раздача статических файлов