這是一篇水文,講講踩坑的經歷。本文的 Demo 已經上傳到我的 GitHub
背景
起因是在寫 Node 時,受夠了 require ('../../../../helper.js')
這種相對路徑。不夠直觀不談,如果將來在別的地方用,都不能直接 copy 過來,還得重新計算相對路徑,因此希望用絕對路徑(換句話說就是永遠相對根路徑)來表示。
一種比較簡單的方案是封裝 require
函式:
global.rootRequire = function(name) {
return require(__dirname + '/' + name);
}
複製程式碼
在我們的 rootRequire
函式中,所有的路徑都會被加上 __dirname
的字首,也就實現了絕對路徑。
這麼做功能上沒有問題,然而似乎 VSCode 對這種寫法支援得不夠好(有了解的大佬還望指教),表現為以下兩個問題:
- 雖然我們把
rootRequire
定義為全域性的,但在別的檔案中輸入這個單詞時,並沒有自動補全 - 通過這種方式引入的模組,不能跳轉到模組的實現,也看不到模組的內部結構,如果用
require
引入則沒有問題。
經過更進一步的測試,甚至於這種寫法也是不行的:
let r = require
let a = r('../test/a')
複製程式碼
在 VSCode 中會發現 a
的型別為 any
並且丟失了很多資訊。
解決方案
換句話說,封裝 require
的路是行不通了,只能用原生的 require
函式,那麼只能看看有什麼辦法可以影響到模組查詢的流程了。
內建的那套流程和順序肯定是改不了,看起來只能從 NODE_PATH
這個全域性變數下手了。我們知道 require
函式會去 NODE_PATH
的目錄裡查詢模組,所以只要把它設定為工程的根路徑,就可以實現絕對路徑載入了。試驗一下,專案目錄如下所示:
project
|-----main
|------index.js
|-----util
|------utils.js
複製程式碼
很簡單的定義一下 utils.js
,就匯出一個物件:
// utils.js
module.exports = {
key:' value'
}
複製程式碼
在 index.js
中這麼寫:
let utils = require('utils/utils')
console.log(utils.key)
複製程式碼
然後執行 node main/index.js
,肯定會編譯失敗。
但如果指定了 NODE_PATH
就不一樣了,此時可以正常執行:
export NODE_PATH=$PWD && node main/index.js
複製程式碼
優化
直接在命令列中指定 NODE_PATH
有兩個問題:
- 改變了專案的啟動方式,別的開發者也會受到影響,不過這一點問題不大,因為一般都是通過命令來啟動的。
- 如果在不同的路徑下啟動 node,那麼
$PWD
是會變的,這種方式不夠安全。
所以比較好的做法是,在入口檔案中指定 NODE_PATH
,因為這個檔案的路徑一般不會改變。所以 index.js
可以改造成這樣:
let path = require('path')
process.env.NODE_PATH = path.resolve(__dirname, '../') ;
require('module').Module._initPaths();
let utils = require('utils/utils')
console.log(utils.key)
複製程式碼
這種寫法的好處在於,無論我們在哪裡執行 node path/to/index.js
都會得到正確的結果。
最後還需要修正一下寫 require
函式時,路徑補全的問題,只要在根目錄裡面加上一個 jsconfig.json
檔案並新增如下內容即可:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"baseUrl": "./",
},
"exclude": [
"node_modules"
]
}
複製程式碼
核心在於 "baseUrl": "./"
這一行。這樣當我們寫 utils
這個單詞的時候,就可以享受到自動補全了。
至此,無論是 Node 的執行,還是路徑補全,抑或是定義跳轉功能,都正常工作了。