前言#
昨天發了一條推特,是因為做工具鏈又碰到了 Windows 相關的兼容性問題,所以有感而發的一句吐槽。但似乎以推特的氛圍,只有這種「暴論」輸出才能引起莫名其妙的關注。引發了部分人的不滿,甚至引發了 Windows 與 macOS 之間的爭論。
那我們就來站在 Unix-like 系統的視角下,聊聊關於 Windows 在前端工具鏈(Node.js)中的「坑」。
路徑煩惱#
詭異的反斜杠#
在 Windows 中,一個常見的路徑看起來像 D:\path\to\file.txt
;而在 Unix-like 系統中,則為 /home/username/file.txt
。顯而易見,Windows 使用反斜杠 \
作為路徑層級之間的分隔符。而在絕大多數的編程語言中,正斜杠通常不需要特殊處理,而反斜杠則用於轉義字符。舉個例子:
console.log('\\') // 實際上只輸出一個反斜杠
console.log('\n') // 輸出換行符
console.log('/') // 沒有意外,輸出一個正斜杠
這意味著,如果我們只考慮在 Unix-like 系統中運行,基本可以忽略對路徑的特殊處理。
:
分隔符#
不僅如此,在 Windows 中還有盤符的概念;而在 Linux 則使用掛載(mount)來管理不同的物理硬碟和分區。這也是一處需要考慮兼容性的地方。
🌰 舉個例子#
說再多不如一個例子來得貼切。可以在 GitHub 中搜索到我被 Windows 折磨的記錄。
test: try fix path for windows#
這個 commit 旨在解決單元測試在 Windows 上的兼容性問題。我們需要傳遞一個 glob 表達式給 entry
選項。這在 Unix-like 系統中,可以直接傳遞文件的路徑 /path/to/file
來直接匹配確切的文件,這很好。
而 glob 是起源於 UNIX 的功能,在 Windows 中,使用反斜杠並不能在 fast-glob 中像在 Unix-like 系統中那樣正常工作。這就造成了一致性問題。雖然 fast-glob
提供了 convertPathToPattern
函數幫你轉義 Windows 的反斜杠,但仍有部分情況未能解決。
在這個 commit 中更坑的是,我在代碼使用了 4 個反斜杠,這是什麼呢?因為這段代碼被反引號 `
與 單引號 '
雙重包裹。因此 4 個反斜杠最終只會表示為一個真正的反斜杠。
fix: watch ignored
option for windows#
這也是一個常見的例子,我們用正則表達式來檢查某個路徑是否在 node_modules
文件夾內。為了兼容 Windows 我們需要寫 /[\\/]node_modules[\\/]/
,同樣這裡的雙重反斜杠最終只會匹配一個反斜杠。但更麻煩的是,對於第三方依賴,你似乎無法確定在 Windows 下它會傳遞反斜杠的路徑,還是會自動替換成正斜杠的路徑。在這個例子中,chokidar
的處理規則頗為奇怪,對於傳遞 function,它會傳遞正斜杠的路徑,對於靜態的數組,則會特殊處理。因此有了這個 commit。
fix: support Node 22/23 strip types feature#
還記得剛剛提到的 :
分隔符嗎?之前我也以為它似乎沒有兼容性問題。這樣想就太天真了!如果我們要在 Node.js 中導入一個絕對路徑,這在 Unix-like 系統很簡單。
require('/path/to/test.cjs')
平平無奇。然而,在 Windows 就會有大問題!
require('C:\\path\\test.cjs') // ✅
import('C:\\path\\test.cjs') // ❌
// Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'
這是因為 require
API 似乎只接受路徑 (path) 作為參數,而 import()
不僅支持路徑,還支持 File URL。所以 Node.js 會把 C:
當作一個 URL,它的 protocol
為 c:
。這與 http://path/test.cjs
本質上沒有太大的區別。File URL、路徑傻傻分不清 🤷
import('file://C:/path/test.cjs') // ✅
console.log(new URL('C:\\path\\test.cjs')) // protocol = 'c:'
import 語句#
import mod from 'a/b/n'
import mod from 'a\b\n'
在 ESM 中,必須使用正斜杠作為分隔符。直接使用反斜杠可能導致意外的轉義。在代碼生成相關庫需要額外注意。
🤨 怎麼辦?#
那麼如何解決呢?使用 pathe 或許是個不錯的選擇!即使在 Windows 環境下,它也會直接把路徑轉換為正斜杠。而正斜杠在 Windows 也會被視為反斜杠。所以對於大部分的情況,都能直接幫你解決一致性問題。但邊緣情況呢?還是有挺多的。
開源社區#
據我所知,我周圍開發和貢獻開源項目的大部分人都在使用 Unix-like 系統。而用戶可能大部分使用的是 Windows。我明白這與市佔率可能有一定關係。但對於作者與維護者來說,也是一件無可奈何的事情。
以我舉例,我的開源項目會借助 GitHub Actions,在 Windows 下跑單元測試。每次我 push 完代碼,收到 GitHub Action 的錯誤通知,一看又是 Windows 下跑不過,總會很苦惱。因為手頭上並沒有一台 Windows 電腦,還裝了開發測試環境。以至於被逼無奈,我現在在使用虛擬機調試 Windows 上的 bug。
而且單元測試不總是能覆蓋方方面面,總會有纰漏。這個就需要實際用戶來發現並提 issue 了!
兼容性問題一覽#
- 路徑
- 反斜杠
- 可能需要轉義
- 可能需要轉為正斜杠
- 盤符
:
- 可能需要轉為 File URL
- 反斜杠
- 無法使用
KEY=VALUE command
設置環境變量- 使用 pnpm 的
shell-emulator
- 可以使用
cross-env
- 使用 pnpm 的
- 沒有
rm -fr
命令- 使用
fs.rmSync(path, { recursive: true, force: true })
- 使用
rimraf
- 使用
種種情況可謂是數不勝數。小編也沒有辦法列舉出所有的情況,這些只是我印象中遇到的一些的坑。
後記#
你遇到了十個路徑問題,並不意味著只有十個,可能還有更多在路上 🛣️…… 恕無法接受「連路徑都處理不對」這樣的說法。
如果有注意到的話,本篇文章通篇都在使用 Unix-like
這樣的表述。這是因為世界上除了 Windows,還有許多其他操作系統。
本文與推文都無意引起 Windows 與 Linux、macOS 的「論戰」。Windows 在很多方面仍在發揮重要的作用(比如說打遊戲)。但就事論事來講,在跨平台領域確實造成許多的不便。
我的庫#
對於我自己的開源庫,我仍然會盡力兼容 Windows,添加 Windows 到 CI 運行。但對於一些邊緣情況,可能會有所忽略。如果你在使用我的庫時遇到了問題,歡迎提 issue 和 PR,我會盡力解決。
替代品#
為了珍惜寶貴生命,建議可以嘗試使用 Unix-like 系統做前端開發(包括 WSL 與第三方虛擬機)。當然,你也可以繼續使用 Windows,但遇到兼容性問題時,請更少抱怨並付出額外的時間、更積極地向開源社區反饋問題與貢獻。
碎碎念#
如果看完文章後,仍然認為這是「傲慢」或「無知」,請自便。