Giải Mã var __dirname: string;: Tấm Bản Đồ Định Vị File Và Cú Vấp Ngã Xuyên Quốc Gia Giữa Windows và Linux
Chào anh em Viblo! 👋
Tiếp nối chuỗi bài viết mổ xẻ các tham số "quyền lực" được tiêm (inject) ngầm vào bên trong Module Wrapper Function của Node.js (sau khi chúng ta đã bóc tách require và module), hôm nay chúng ta sẽ cùng nhau gặp gỡ một biến toàn cục dạng chuỗi mà bất kỳ ai làm việc với file, đọc template HTML, hay nạp cấu hình hệ thống đều phải gõ: __dirname.
Nếu anh em mở file định nghĩa core của Node.js trong TypeScript (@types/node), biến này được khai báo vô cùng ngắn gọn:
declare var __dirname: string;
Nhiều bạn nghĩ: "Dào ôi, cái biến này chỉ chứa cái đường dẫn thư mục hiện tại thôi chứ có gì đâu mà phải viết bài học thuật phức tạp?". Đúng là nó chỉ chứa một chuỗi đường dẫn tuyệt đối, nhưng cách anh em tương tác với chuỗi này chính là nguyên nhân tạo ra những con bug "ảo ma" theo kiểu: Code chạy ở máy Local của em (Windows) thì mượt mà 100%, nhưng cứ hễ deploy lên server Production (Docker/Linux) là hệ thống báo lỗi không tìm thấy file và sập nguồn.
Hôm nay, hãy cùng mình làm chủ hoàn toàn __dirname và bỏ túi những kinh nghiệm xử lý đường dẫn chuẩn Senior nhé!
1. Bản chất của __dirname từ đâu mà có?
Như mình đã chia sẻ ở bài viết về module, trước khi Node.js thực thi một file JavaScript, nó sẽ bọc toàn bộ code của file đó vào một cái hàm ẩn danh. Biến __dirname chính là tham số thứ 4 được Node.js truyền vào file của bạn.
Định nghĩa chính xác: __dirname là một chuỗi (string) chứa đường dẫn tuyệt đối đến thư mục (directory) chứa cái file JavaScript hiện tại đang chạy câu lệnh đó.
Ví dụ, nếu file src/services/userService.js của bạn nằm ở thư mục /Users/project/src/services/userService.js, thì giá trị của __dirname bên trong file đó sẽ chính xác là chuỗi: /Users/project/src/services.
2. Cơn ác mộng phân biệt: __dirname vs process.cwd()
Đây là câu hỏi phỏng vấn cực kỳ bẫy và cũng là nơi nhiều dev Junior "vấp cỏ" nhiều nhất. Cả hai đều trả về một đường dẫn thư mục tuyệt đối, nhưng bản chất của chúng lại khác nhau hoàn toàn.
- __dirname (Phụ thuộc vào vị trí của FILE): Cho dù bạn đứng ở bất kỳ đâu để chạy ứng dụng, __dirname luôn cố định dựa trên vị trí địa lý của chính cái file chứa dòng code đó trên ổ cứng.
- process.cwd() (Phụ thuộc vào nơi bạn gõ LỆNH): cwd là viết tắt của Current Working Directory. Nó trả về đường dẫn của thư mục nơi bạn đứng để gõ lệnh node trên Terminal để khởi chạy ứng dụng.
Vết sẹo thực chiến: Tưởng tượng bạn có cấu trúc thư mục: /project/src/app.js. Bên trong app.js bạn gọi cả 2 lệnh.
- Trường hợp 1: Bạn mở Terminal tại đúng thư mục /project và gõ lệnh: node src/app.js.
- __dirname /project/src
- process.cwd() /project
- Trường hợp 2: Bạn cd src vào trong thư mục src rồi gõ lệnh: node app.js.
- __dirname /project/src (Không đổi)
- process.cwd() /project/src (Đã thay đổi!)
Bài học rút ra: Nếu bạn viết tính năng đọc một file cấu hình nằm cố định trong source code, hãy luôn dùng __dirname. Nếu bạn dùng process.cwd(), code của bạn sẽ chạy bằng "niềm tin" vì chỉ cần người vận hành (DevOps) đứng sai thư mục để gõ lệnh start server, đường dẫn sẽ bị lệch và app sẽ crash lập tức.
3. Những pha "vấp hố" kinh điển và cách bốc thuốc chuẩn Senior
Sai lầm 1: Cộng chuỗi đường dẫn thủ công bằng dấu / Vì __dirname trả về một chuỗi, nhiều bạn quen tay dùng toán tử cộng chuỗi để nối đường dẫn đến file cần đọc:
// ❌ CODE NGUY HIỂM: Gây sập hệ thống khi đổi hệ điều hành
const fs = require('node:fs');
const configPath = __dirname + '/config/settings.json';
const data = fs.readFileSync(configPath, 'utf8');
Tại sao lại toang?
Đoạn code trên chạy cực mượt trên macOS và Linux vì hệ điều hành này dùng dấu gạch chéo xuôi (/) để phân tách thư mục. Nhưng nếu một ngày dự án của bạn có một thành viên dùng Windows join vào team để code Local, máy Windows sử dụng dấu gạch chéo ngược () làm chuẩn phân tách. Chuỗi sinh ra trên Windows sẽ là C:\project\src/config/settings.json Hệ thống báo lỗi đường dẫn dị dạng và sập app.
Bốc thuốc: Hãy luôn tin dùng module node:path
Tuyệt đối không tự dùng tay cộng chuỗi ranh giới thư mục. Hãy giao việc đó cho hàm path.join() hoặc path.resolve(). Module này của Node.js cực kỳ thông minh, nó sẽ tự nhìn vào hệ điều hành hiện tại đang chạy để tự động chèn dấu / hoặc \ sao cho chuẩn chỉ nhất.
// ✅ Chuẩn Senior: Bất chấp mọi hệ điều hành
const path = require('node:path');
const fs = require('node:fs');
const configPath = path.join(__dirname, 'config', 'settings.json');
const data = fs.readFileSync(configPath, 'utf8');
4. Góc nhìn thời đại: Khi __dirname biến mất trong ES Modules (ESM)
Cũng giống như require và module, khi thế giới Node.js dịch chuyển lên chuẩn thời đại mới ES Modules (import/export), biến __dirname chính thức bị khai tử.
Nếu bạn cố tình cấu hình file là "type": "module" trong package.json và gọi __dirname, Node.js sẽ ném ngay lỗi ReferenceError: __dirname is not defined.
Cách tái tạo __dirname trong kỷ nguyên ESM chuẩn chỉ nhất Để có được một biến có giá trị tương đương __dirname trong môi trường ESM, chúng ta phải đi đường vòng thông qua đối tượng meta dữ liệu import.meta.url kết hợp với thư viện node:url:
import { fileURLToPath } from 'node:url';
import path from 'node:path';
// 1. Lấy ra đường dẫn tuyệt đối của FILE hiện tại (thay thế cho __filename cũ)
const __filename = fileURLToPath(import.meta.url);
// 2. Trích xuất ra đường dẫn thư mục chứa file đó (tái tạo lại __dirname)
const __dirname = path.dirname(__filename);
// Bây giờ bạn có thể ung dung xài __dirname như thời CommonJS cũ
const configPath = path.join(__dirname, 'config', 'settings.json');
Đúc Kết Lại
Khai báo var __dirname: string; là một mảnh ghép nhỏ nhưng không thể thiếu trong hành trình làm chủ hệ thống file của Node.js. Hiểu rõ sự khác biệt giữa phạm vi cố định của __dirname so với phạm vi động của process.cwd(), cũng như luôn sử dụng module node:path để bọc lót sẽ giúp bạn viết ra những dòng code Backend vững chắc như bàn thạch, bất chấp việc hệ thống được deploy trên Windows, Mac, Linux hay bên trong các container Docker.
Hy vọng bài viết này mang lại những kiến thức thực chiến bổ ích cho anh em trong các tác vụ xử lý file hàng ngày. Chúc anh em tối ưu code thành công! Happy Coding! 🚀💻
All rights reserved