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 modules: import.meta.dirname
ב-ES modules אין __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 תמיד משתמשות ב-/ ויש להן כללים משלהן ל-.. ול-query strings. השתמשו במחלקה URL, כמו ב-new URL('../img/logo.png', 'https://example.com/docs/page').