path.join() соединяет части пути правильным разделителем и убирает .. и двойные слэши, но оставляет результат относительным, если первая часть была относительной. path.resolve() делает ту же работу справа налево и всегда возвращает абсолютный путь: он начинает от текущей рабочей папки, а любая абсолютная часть по пути начинает путь заново. Используйте join, чтобы собрать путь, и resolve, когда нужен абсолютный.
Программа вывела users/ada/notes.txt, затем users/grace/todo.txt (.. убрал ada), затем /srv/app/public/index.html и src/lib/utils.js: лишние слэши и пустые части исчезли. join никогда не считает /public/ началом нового пути, это просто следующая часть.
path.join и path.resolve
Разница видна, когда часть начинается с / или когда первая часть относительная:
С /public в середине join выдал /srv/app/public/img, а resolve отбросил всё, что было до неё, и выдал /public/img; .. оба обработали одинаково. Последние две строки показывают, что join оставил public/index.html относительным, а resolve сделал его абсолютным, подставив впереди текущую папку.
path.join | path.resolve | |
|---|---|---|
| Результат | Относительный или абсолютный, как на входе | Всегда абсолютный |
Часть, начинающаяся с / | Просто ещё одна часть | Путь начинается с неё заново |
| Точка отсчёта | Нет | Текущая рабочая папка |
| Без аргументов | '.' | Текущая рабочая папка |
| Типичное применение | Собрать путь из частей | Превратить ввод пользователя или конфиг в абсолютный путь |
basename, dirname, extname и parse
Эти функции разбирают путь на части. Они смотрят только на строку: файл может и не существовать.
Программа вывела cat.final.jpg, cat.final, /home/ada/photos и .jpg: extname берёт только часть после последней точки. path.parse возвращает всё сразу (root, dir, base, ext, name), а path.format превращает такой объект обратно в строку.
__dirname и __filename
В файле CommonJS __dirname это абсолютный путь к папке, где лежит файл, а __filename путь к самому файлу. Относительный путь вроде 'data.json' отсчитывается от того места, где пользователь запустил node, и ломается, как только кто-то запустит скрипт из другой папки. Собирайте пути от __dirname:
Программа вывела config.json dark и index.js. Полные пути на каждой машине разные, именно поэтому их собирают через path.join(__dirname, ...), а не пишут вручную.
__dirname в ES модулях: import.meta.dirname
В ES модулях нет __dirname; обращение к нему бросает ReferenceError: __dirname is not defined in ES module scope. Начиная с Node.js 20.11, их заменяют import.meta.dirname и import.meta.filename:
Программа вывела index.js и data/users.json. В версиях Node.js до 20.11 получите папку из URL модуля: path.dirname(fileURLToPath(import.meta.url)), где fileURLToPath берётся из node:url.
Почему не склеивать пути строками
'data' + '/' + file работает в macOS и Linux, но ломается в Windows, где разделитель \, и никак не обрабатывает .., двойные слэши или пропущенный слэш. path следует правилам системы, на которой работает. Результат для Windows можно увидеть на любой машине через path.win32:
const path = require('path');
path.posix.join('users', 'ada', 'notes.txt'); // 'users/ada/notes.txt'
path.win32.join('users', 'ada', 'notes.txt'); // 'users\\ada\\notes.txt'
path.sep; // '/' on macOS and Linux, '\\' on Windows
Передайте результат path.join в fs.readFile, и один и тот же код прочитает файл на любой системе.
Частые ошибки
Ожидать, что extname знает о двойных расширениях. path.extname('archive.tar.gz') равен '.gz', а у файла с точкой в начале вроде .env расширения нет вовсе: path.extname('.env') равен ''. Если важен весь суффикс, проверяйте конец имени сами.
Надеяться, что join удержит пользователя в папке. path.join('/srv/app', '../../etc/passwd') равен '/etc/passwd'. Прежде чем читать файл, имя которого пришло в запросе, сделайте resolve пути и проверьте, что он всё ещё начинается с вашей папки.
Часто задаваемые вопросы
Чем __dirname отличается от process.cwd()?
__dirname это папка текущего файла с кодом, и она никогда не меняется. process.cwd() это папка, из которой пользователь запустил node. Они совпадают, только когда программу запускают из её собственной папки.
path.join или path.resolve вместе с __dirname?
Для path.join(__dirname, 'data.json') подходит любой, потому что __dirname уже абсолютный. Берите resolve, когда какая-то часть сама может быть абсолютной, например путь, который ввёл пользователь, и вы хотите, чтобы победил именно он.
Как использовать модуль path с URL?
Не используйте: в URL всегда /, и у них свои правила для .. и строки запроса. Используйте класс URL, например new URL('../img/logo.png', 'https://example.com/docs/page').