Menu

Module path trong Node.js: path.join và path.resolve

Module path ghép và tách đường dẫn tệp theo đúng quy tắc của hệ điều hành. path.join nối các đoạn lại với nhau; path.resolve biến chúng thành đường dẫn tuyệt đối, bắt đầu từ thư mục hiện tại.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

path.join() nối các đoạn đường dẫn bằng dấu phân cách đúng và dọn dẹp .. cùng các dấu gạch chéo bị lặp, nhưng giữ kết quả ở dạng tương đối nếu bạn bắt đầu bằng một đoạn tương đối. path.resolve() làm cùng việc đó theo chiều từ phải sang trái và luôn trả về đường dẫn tuyệt đối: nó bắt đầu từ thư mục làm việc hiện tại (current working directory), và bất kỳ đoạn tuyệt đối nào gặp trên đường đi cũng khiến đường dẫn bắt đầu lại từ đó. Dùng join để ghép một đường dẫn, và resolve khi bạn cần đường dẫn tuyệt đối.

Kết quả in ra là users/ada/notes.txt, rồi users/grace/todo.txt (.. đã bỏ ada), rồi /srv/app/public/index.html và src/lib/utils.js: dấu gạch chéo thừa và đoạn rỗng đều biến mất. join không bao giờ coi /public/ là điểm bắt đầu mới; với nó đây chỉ là đoạn tiếp theo.

path.join vs path.resolve

Sự khác biệt lộ ra khi một đoạn bắt đầu bằng / hoặc khi đoạn đầu tiên là đường dẫn tương đối:

Khi có /public ở giữa, join cho ra /srv/app/public/img, còn resolve bỏ hết mọi thứ phía trước và cho ra /public/img; cả hai xử lý .. giống nhau. Hai dòng cuối cho thấy join giữ public/index.html ở dạng tương đối, còn resolve biến nó thành tuyệt đối bằng cách đặt thư mục hiện tại lên trước.

path.joinpath.resolve
Kết quảTương đối hoặc tuyệt đối, theo đầu vàoLuôn tuyệt đối
Một đoạn bắt đầu bằng /Chỉ là một đoạn bình thườngBắt đầu lại đường dẫn từ đó
Điểm xuất phátKhông cóThư mục làm việc hiện tại
Không có đối số'.'Thư mục làm việc hiện tại
Cách dùng thường gặpGhép đường dẫn từ nhiều phầnBiến đầu vào của người dùng hoặc cấu hình thành đường dẫn tuyệt đối

basename, dirname, extname và parse

Các hàm này tách một đường dẫn thành từng phần. Chúng chỉ xem xét chuỗi: tệp không cần phải tồn tại.

Kết quả in ra là cat.final.jpg, cat.final, /home/ada/photos và .jpg: extname chỉ lấy phần sau dấu chấm cuối cùng. path.parse trả về tất cả cùng lúc (root, dir, base, ext, name), và path.format biến một đối tượng như vậy trở lại thành chuỗi.

__dirname và __filename

Trong một tệp CommonJS, __dirname là đường dẫn tuyệt đối của thư mục chứa tệp đó, còn __filename là đường dẫn của chính tệp. Một đường dẫn tương đối như 'data.json' được tính từ chỗ người dùng chạy node, nên sẽ hỏng ngay khi ai đó chạy script của bạn từ một thư mục khác. Thay vào đó, hãy ghép đường dẫn từ __dirname:

Kết quả in ra là config.json dark và index.js. Đường dẫn đầy đủ khác nhau trên mỗi máy, và đó chính là lý do bạn ghép chúng bằng path.join(__dirname, ...) thay vì gõ tay.

__dirname trong ES module: import.meta.dirname

ES module không có __dirname; dùng nó sẽ ném lỗi ReferenceError: __dirname is not defined in ES module scope. Từ Node.js 20.11, import.meta.dirname và import.meta.filename thay thế cho chúng:

Kết quả in ra là index.js và data/users.json. Với các phiên bản Node.js trước 20.11, hãy lấy thư mục từ URL của module: path.dirname(fileURLToPath(import.meta.url)), với fileURLToPath lấy từ node:url.

Vì sao không nối đường dẫn bằng chuỗi

'data' + '/' + file chạy được trên macOS và Linux, nhưng hỏng trên Windows, nơi dấu phân cách là \, và cách này cũng không xử lý .., dấu gạch chéo bị lặp hay bị thiếu. path dùng quy tắc của hệ thống mà nó đang chạy. Bạn có thể xem kết quả trên Windows từ bất kỳ máy nào bằng 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

Truyền kết quả của path.join vào fs.readFile và cùng một đoạn code sẽ đọc được tệp trên mọi hệ thống.

Lỗi thường gặp

Nghĩ rằng extname hiểu phần mở rộng kép. path.extname('archive.tar.gz') là '.gz', còn một dotfile như .env hoàn toàn không có phần mở rộng: path.extname('.env') là ''. Hãy tự kiểm tra phần cuối của tên khi bạn cần toàn bộ hậu tố.

Tin rằng join sẽ giữ người dùng ở trong một thư mục. path.join('/srv/app', '../../etc/passwd') là '/etc/passwd'. Trước khi đọc một tệp có tên đến từ request, hãy resolve đường dẫn và kiểm tra rằng nó vẫn bắt đầu bằng thư mục của bạn.

Câu hỏi thường gặp

__dirname và process.cwd() khác nhau thế nào?

__dirname là thư mục chứa tệp mã nguồn hiện tại và không bao giờ thay đổi. process.cwd() là thư mục mà người dùng đứng khi chạy node. Hai giá trị này chỉ trùng nhau khi chương trình được chạy từ chính thư mục của nó.

Nên dùng path.join hay path.resolve với __dirname?

Với path.join(__dirname, 'data.json') thì cách nào cũng được, vì __dirname vốn đã là đường dẫn tuyệt đối. Dùng resolve khi một đoạn có thể tự nó là đường dẫn tuyệt đối, chẳng hạn đường dẫn người dùng gõ vào, và bạn muốn đường dẫn đó được ưu tiên.

Dùng module path với URL như thế nào?

Đừng dùng: URL luôn dùng / và có quy tắc riêng cho .. cũng như query string. Hãy dùng lớp URL, ví dụ new URL('../img/logo.png', 'https://example.com/docs/page').

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu