到目前為止,如果你在 Linux、Mac 平臺做開發,所有的 npm script 都會順利運行,但是 Windows 下面的同學可能就比較痛苦了,因為不是所有的 shell 命令都是跨平臺兼容的,甚至各種常見的文件系統操作也是不兼容的。
可能有部分同學處理過 npm script 跨平臺兼容的問題,比如粗暴的為兩種平臺各寫一份 npm script,像下面這樣:
```
{
"name": "hello-npm-script",
"scripts": {
"bash-script": "echo Hello $npm_package_name",
"win-script": "echo Hello %npm_package_name%"
}
}
```
有技術追求的工程師肯定不會滿足上面的解決方案,實際上社區中已經有非常多的小工具可以幫我們優雅的實現跨平臺的 npm script,下面我們探索下如何實現跨平臺的文件系統操作、變量引用、環境變量設置。
**特別說明,windows 環境的同學建議使用 git bash 來運行 npm script,使用 windows 自帶的 cmd 可能會遇到比較多的問題**
## 文件系統操作的跨平臺兼容
npm script 中涉及到的文件系統操作包括文件和目錄的創建、刪除、移動、復制等操作,而社區為這些基本操作也提供了跨平臺兼容的包,列舉如下:
* [rimraf](https://github.com/isaacs/rimraf) 或 [del-cli](https://www.npmjs.com/package/del-cli),用來刪除文件和目錄,實現類似于 `rm -rf` 的功能;
* [cpr](https://www.npmjs.com/package/cpr),用于拷貝、復制文件和目錄,實現類似于 `cp -r` 的功能;
* [make-dir-cli](https://www.npmjs.com/package/make-dir-cli),用于創建目錄,實現類似于 `mkdir -p` 的功能;
使用上面這幾個小工具改造 npm script 的具體步驟如下:
第 1 步,添加開發依賴:
```
npm i rimraf cpr make-dir-cli -D
# npm install rimraf cpr make-dir-cli --save-dev
# yarn add rimraf cpr make-dir-cli -D
```
第 2 步,改造涉及文件系統操作的 npm script:
```
"scripts": {
- "cover:cleanup": "rm -rf coverage && rm -rf .nyc_output",
- "cover:archive": "cross-var \"mkdir -p coverage_archive/$npm_package_version && cp -r coverage/* coverage_archive/$npm_package_version\"",
+ "cover:cleanup": "rimraf coverage && rimraf .nyc_output",
+ "cover:archive": "cross-var \"make-dir coverage_archive/$npm_package_version && cpr coverage/* coverage_archive/$npm_package_version -o\"",
"cover:serve": "cross-var http-server coverage_archive/$npm_package_version -p $npm_package_config_port",
"cover:open": "cross-var opn http://localhost:$npm_package_config_port",
- "postcover": "npm-run-all cover:archive cover:cleanup --parallel cover:serve cover:open"
+ "precover": "npm run cover:cleanup",
+ "postcover": "npm-run-all cover:archive --parallel cover:serve cover:open"
},
```
對改動的幾點說明:
* `rm -rf` 直接替換成 `rimraf`;
* `mkdir -p` 直接替換成 `make-dir`;
* `cp -r` 的替換需特別說明下,`cpr` 默認是不覆蓋的,需要顯示傳入 `-o` 配置項,并且參數必須嚴格是 `cpr <source> <destination> [options]` 的格式,即配置項放在最后面;
* 把 `cover:cleanup` 從 `postcover` 挪到 `precover` 里面去執行,規避 `cpr` 沒歸檔完畢覆蓋率報告就被清空的問題;
> **TIP#13**:任何改動之后記得重新運行 npm run cover,確保所有的 npm script 還是按預期工作的
## 用 cross-var 引用變量
[2.2 在 npm script 中使用變量](https://juejin.im/book/5a1212bc51882531ea64df07/section/5a12146951882531bb6c68fe) 介紹了如何使用內置和預定義變量減少代碼重復的技巧,如本節開頭的例子,Linux 和 Windows 下引用變量的方式是不同的,Linux 下直接可以用 `$npm_package_name`,而 Windows 下必須使用 `%npm_package_name%`,我們可以使用 [cross-var](https://www.npmjs.com/package/cross-var) 實現跨平臺的變量引用,具體步驟如下:
第 1 步,安裝 cross-var 為開發依賴:
```
npm i cross-var -D
# npm install cross-var --save-dev
# yarn add cross-var -D
```
第 2 步,改寫引用變量 npm script,具體 diff 如下:
```
"scripts": {
"cover:cleanup": "rm -rf coverage && rm -rf .nyc_output",
- "cover:archive": "mkdir -p coverage_archive/$npm_package_version && cp -r coverage/* coverage_archive/$npm_package_version",
- "cover:serve": "http-server coverage_archive/$npm_package_version -p $npm_package_config_port",
- "cover:open": "opn http://localhost:$npm_package_config_port",
+ "cover:archive": "cross-var \"mkdir -p coverage_archive/$npm_package_version && cp -r coverage/* coverage_archive/$npm_package_version\"",
+ "cover:serve": "cross-var http-server coverage_archive/$npm_package_version -p $npm_package_config_port",
+ "cover:open": "cross-var opn http://localhost:$npm_package_config_port",
"postcover": "npm-run-all cover:archive cover:cleanup --parallel cover:serve cover:open"
},
```
因為 cover:serve 和 cover:open 命令都比較簡單,直接在原始命令前增加 cross-var 命令即可,而 cover:archive 內含了兩條子命令,我們需要用引號把整個命令包起來(注意這里是用的雙引號,且必須轉義),然后在前面加上 cross-var。
此外,細心的同學可能發現引入 cross-var 之后,它竟然給我們安裝了 babel,如果想保持依賴更輕量的話,可以考慮使用 [cross-var-no-babel](https://www.npmjs.com/package/cross-var-no-babel)。
## 用 cross-env 設置環境變量
在 node.js 腳本和 npm script 使用環境變量也是比較常見的,比如我們在運行測試時,需要加上 `NODE_ENV=test`,或者在啟動靜態資源服務器時自定義端口號。因為不同平臺的環境變量語法不同,我們可以使用 [cross-env](https://www.npmjs.com/package/cross-env) 來實現 npm script 的跨平臺兼容,具體步驟如下:
第 1 步,添加 cross-env 到開發依賴:
```
npm i cross-env -D
# npm install cross-env --save-dev
# yarn add cross-env -D
```
第 2 步,改寫使用了環境變量的 npm script:
```
"scripts": {
- "test": "NODE_ENV=test mocha tests/",
+ "test": "cross-env NODE_ENV=test mocha tests/",
},
```
上面的改動更簡單,直接在設置了環境變量的命令前面加上 cross-env 即可。
## 再多說幾句
關于 npm script 的跨平臺兼容,還有幾點需要大家注意:
* 所有使用引號的地方,建議使用雙引號,并且加上轉義;
* 沒做特殊處理的命令比如 eslint、stylelint、mocha、opn 等工具本身都是跨平臺兼容的;
* 還是強烈建議有能力的同學能使用 Linux 做開發,只要你入門并且熟練了,效率提升會驚人;
* 短時間內繼續擁抱 Windows 的同學,可以考慮看看 Windows 10 里面引入的 [Subsystem](https://msdn.microsoft.com/en-us/commandline/wsl/about),讓你不用虛擬機即可在 Windows 下使用大多數 Linux 命令。
> **TIP#14**:如果你在編寫 npm script 過程中有更多的跨平臺兼容需求,基本思路是去 [npmjs.com](https://www.npmjs.com/search?q=cross%20platform) 上找對應的包,關鍵詞自然少不了 `cross platform`,你遇到的問題,肯定很多其他人遇到過,相信我,你并不孤獨!
* * *
> 本節用到的代碼見 [GitHub](https://github.com/wangshijun/automated-workflow-with-npm-script/tree/06-add-cross-platform-support),想邊看邊動手練習的同學可以拉下來自己改,注意切換到正確的分支 `06-add-cross-platform-support`。
* * *
- 為什么選擇 npm script
- 入門篇 01:創建并運行 npm script 命令
- 入門篇 02:運行多個 npm script 的各種姿勢
- 入門篇 03:給 npm script 傳遞參數和添加注釋
- 進階篇 01:使用 npm script 的鉤子
- 進階篇 02:在 npm script 中使用環境變量
- 進階篇 03:實現 npm script 命令自動補全
- 高階篇 01:實現 npm script 跨平臺兼容
- 高階篇 02:把龐大的 npm script 拆到單獨文件中
- 高階篇 03:用 node.js 腳本替代復雜的 npm script
- 實戰篇 01:監聽文件變化并自動運行 npm script
- 實戰篇 02:結合 live-reload 實現自動刷新
- 實戰篇 03:在 git hooks 中運行 npm script
- 實戰篇 04:用 npm script 實現構建流水線
- 實戰篇 05:用 npm script 實現服務自動化運維