Node.js 中的 __dirname、__filename,以及使用相對路徑產生的問題

使用相對路徑產生的問題

資料夾結構

1
2
3
4
5
test/
— nodejs/
∟ path.js
— lib/
∟ common.js

path.js 中的程式碼

1
2
3
4
5
6
fs.readFile('../lib/common.js','utf8' , function (err, data) {
if (err) {
return console.log('error: ', err)
}
console.log('OK')
})

在 nodejs 資料夾下執行 node path.js, 可以順利讀取檔案

退回一個資料夾, 在 test 資料夾下執行 node nodejs/path.js, 則是報錯了。
這是 Node 的機制所導致的, 在 Node 中, 文件裡的相對路徑代表的是執行 node 時所在的資料夾的相對路徑, 並不是你所執行的 JS 檔本身的相對路徑。

在這個例子中, 如果在 ~/DeskTop/test 這個資料夾執行 node nodejs/path.js 指令, 程式碼中 readFile 內參數路徑 ../lib/common.js 會被解釋成 ~/DeskTop/lib/common.js, 而顯然這個資料路徑是不存在的。

使用絕對路徑

在 Node.js 中, 大概有 __dirname__filenameprocess.cwd() 或者 ./../, 前三者都是絕對路徑, 為了便於比較 ./../ 我們使用 path.resolve('./') 來轉換為絕對路徑

和上例一樣的資料結構, 將 path.js 改成:

1
2
3
4
5
6
const path = require('path')

console.log('__dirname:', __dirname)
console.log('__filename:', __filename)
console.log('process.cwd():', process.cwd())
console.log('./:', path.resolve('./'))

在當下資料夾, 也就是 nodejs 資料夾執行命令 node path.js, 看看輸出結果:

1
2
3
4
__dirname:     C:\Users\Dylanliu\Desktop\test\nodejs
__filename: C:\Users\Dylanliu\Desktop\test\nodejs\path.js
process.cwd(): C:\Users\Dylanliu\Desktop\test\nodejs
./: C:\Users\Dylanliu\Desktop\test\nodejs

接著退回一個層級資料夾, 到了 test 資料夾執行命令 node nodejs/path.js, 看看輸出結果:

1
2
3
4
__dirname:     C:\Users\Dylanliu\Desktop\test\nodejs
__filename: C:\Users\Dylanliu\Desktop\test\nodejs\path.js
process.cwd(): C:\Users\Dylanliu\Desktop\test
./: C:\Users\Dylanliu\Desktop\test

再退一個層級資料夾, 到 Desktop 中執行命令 node test/nodejs/path.js, 看看輸出結果:

1
2
3
4
__dirname:     C:\Users\Dylanliu\Desktop\test\nodejs
__filename: C:\Users\Dylanliu\Desktop\test\nodejs\path.js
process.cwd(): C:\Users\Dylanliu\Desktop
./: C:\Users\Dylanliu\Desktop

由上面的實驗結果可以歸納以下結論:

1
2
3
4
- __dirname:     總是回傳被執行 js 檔所在資料夾的絕對路徑
- __filename: 總是回傳被執行 js 檔的絕對路徑
- process.cwd(): 總是回傳執行 node 指令時所在的資料夾之絕對路徑
- ./: 和 process.cwd() 一樣

正確路徑使用習慣

由上面的實驗可以知道, 在 Node 中使用相對路徑進行檔案讀取是很危險的, 建議一律都透過絕對路徑的方式來處理

將 path.js 改成這樣:

1
2
3
4
5
6
7
8
9
10
11
12
const fs = require('fs')
const path = require('path')

const absolutePath = path.join(__dirname, '../lib/common.js')
// C:\Users\Dylanliu\Desktop\test\lib\common.js

fs.readFile(absolutePath, 'utf8', function (err, data) {
if (err) {
return console.log('error: ', err)
}
console.log('OK')
})

在 nodejs 資料夾下執行 node path.js, 順利讀取檔案

退回一個資料夾, 在 test 資料夾下執行 node nodejs/path.js, 一樣順利讀取檔案

path.join() 方法

path.join() 是 path 核心模組提供的一個方法, 可以幫助開法者在拼接路徑字串時, 減少出錯機率

來做個實驗, 首先在終端機執行 node, 進入 REPL 模式, 輸入程式碼做測試

1
path.join('C:/file', 'fileB')

輸出:

1
'C:\\file\\fileB'

若在手動拚字串的過程中少了或多了一個 / 符號, 很容易導致路徑問題, 產生一些低級錯誤。如果使用 path.join() 這個方法就能有效避免失誤機率發生

1
path.join('C:/file', 'fileB', '//fileC', 'fileD')

輸出:

1
'C:\\file\\fileB\\fileC\\fileD'

require 的相對路徑

還記得我們在 Node 中使用 require 時好像也常常使用相對路徑來引入自己寫的 JS 檔, 對吧?這樣也會有一樣的問題嗎?來試試看吧

延續一樣的資料夾結構

1
2
3
4
5
test/
— nodejs/
∟ path.js
— lib/
∟ common.js

common.js 內容

1
exports.A = 'A'

在 path.js 撰寫程式碼, 透過 require 和 readFile 來測試, 讀取的檔案同樣是 common.js, 接著測試在不同資料夾執行腳本時, 兩者會不會有差異

1
2
3
4
5
6
7
8
9
var fs = require('fs')

var common = require('../lib/common.js')
console.log(common.A)

fs.readFile('../lib/common.js', function (err, data) {
if (err) return console.log(err)
console.log(data)
})

在 nodejs 資料夾下執行指令 node path.js, 想當然是不會報錯的。

退回一個資料夾, 在 test 資料夾下執行指令 node nodejs/path.js

這下問題來了, test 資料夾下執行指令時, 為什麼 require 是 OK 的, 只有 readFile 會報錯呢?

關於相對路徑的結論是

只有在 require() 中使用時, 效果相同於 __dirname , 不會因為啟動腳本的資料夾不同而改變, 在其他情況下則跟 process.cwd() 效果相同, 是相對於啟動腳本所在資料夾的路徑。因此, 只有在 require() 時才能使用相對路徑, 其他情況一律使用絕對路徑是最安全的方式。

參考資料

浅析 NodeJs 的几种文件路径