cnpm 安裝全局模塊引發的思考
起因
因爲工作的時候,公司提供的是 Windows 臺式機,因此一般都是在 Windows 環境下開發的。
但是最近在用 cnpm 安裝腳本的時候,忽然發現一個很有意思的問題:用 cnpm 安裝的全局腳本,比如 vue-cli 居然只能在 cmd 中運行,無法在 powerShell 中運行。
類似的問題,其實以前也碰到過,但是以前一般都是報着能用就用,不去深究原理的想法。
其實很多時候,自己內心深處還是有一個聲音不斷地在提醒自己:“知其然更知其所以然方能走的更遠”!
但是奈何,自己實力不允許,對很多東西都是一知半解,沒有形成一套體系,沒有擁有看透問題本質的能力。
但是現在,開始找到感覺了,於是現在碰到問題,多花點時間,稍微深究一下,事後收穫還是很大的。
閒話不多說,轉回我們的正題吧。
npm、cnpm、yarn 選擇問題
至於爲什麼會用 npm,我想前端的同學應該都深有感觸,特別是國內的前端同學。
我想或多或少都會經歷過一下場景:
同事1:反正大家都說 npm 不好用!
同事2:npm 垃圾玩意兒,下東西太慢了!
同事3:cnpm 下東西快,比 npm 強一萬倍!
同事4:你竟然還在用 npm,太老土了!
。。。
類似的情景對話,我想大家應該都經歷過。
但是究竟爲什麼 npm 不好用,cnpm 好用呢?
這種踩 npm 的情況,是從什麼時候開始的,現在還是這樣嗎?
npm 跟 cnpm 的差別是什麼呢?
npm 可以通過什麼方式變得跟 cnpm 一樣好用嗎?
其實這些問題,並沒有多少人去深究,特別是對於很多其前端初級選手來說,估計很多人都下意識的認爲,其實這兩個是一個東西,都是下 nodejs 模塊的嘛。
估計很多人都這樣想:這些問題關我鳥事,我研究的那麼透徹,老闆也不會給我漲工資啊!我前端,寫好頁面就行了,這些個框架,能用好就行了,我開車,幹嘛要了解汽車的原理呢!
如果你符合以上的思維,那麼請及時中斷往下看的念頭吧,這篇文章不適合你。
那麼究竟爲什麼大家都重口一詞的說 npm 不好用 cnpm 好用呢?
原因是就是,以前 npm 真的挺不好用的!
這篇博主的文章分析的非常好,有興趣的同學可以閱讀一下:https://blog.xgheaven.com/2018/05/03/npm-to-yarn-to-npm/
就因爲 npm 不好用,所以才催生出像 cnpm、yarn 等第三方包管理系統。
但是隨着 npm 6.x 版本的發佈,這些問題已經被被解決了,已經被掃進了歷史的垃圾堆裏了。
所以結論就是:現在用 nodejs 的包管理系統,首選 npm,實在不能用 npm 的情況下,再考慮用第三方的包管理系統。
比如 create-react-app 這個腳手架,就是隻支持 yarn 的,但是如果你非要用 npm,就很麻煩了。
但是有的童鞋會發問,npm 安裝模塊太慢了,不能忍。
說實話,我也忍不了,忍不了你就改個 npm 模塊的源唄,從鏡像站去下載模塊不久快很多麼。
這個問題其實不止是出現在 npm 上,很多包管理系統都會出現在這樣的問題。
比如 python 的 pip,Ubuntu 的 apt-get,homebrew,centos 的 yum 等等,都會因爲官方源服務器在國外,訪問起來太慢。
但是這個問題其實是有解決方案的,換成國內的鏡像源就能解決。
比如 npm 換成淘寶的國內鏡像源就能解決下載過慢的問題了,下面是配置方式:
# 換源
npm config set registry https://registry.npm.taobao.org
# 檢查是否改成功了
npm config get registry
第三方包管理工具
再聊聊爲什麼會出現第三方包管理工具。
其實之前就說過了,因爲 npm 在開始的時候不好用,所以後來社區就誕生出了更優秀的包管理工具。
但是 npm 也是在進步的,我們不能總是以一種陳舊的眼光去看待問題,以一種擁抱未來的姿態去剔除我們思想中的偏見成分。
npm 的這種歷史,有點類似於 JavaScript 的發展歷史。
以前 JavaScript 很多地方用起來不友好,所以催生出了很多優秀的 JavaScript 框架,十年前,風頭最盛的大概就是 jQuery 了吧。
甚至一度,有人豪言壯語的宣稱:不用學 JavaScript 了,學了 jQuery 就行了。
但是歷史總是這麼驚人的相似,隨着 es6 以及後續版本的出現,jQuery、lodash 等很多增強 JavaScript 語言功能的框架都漸漸的開始退出了歷史的舞臺了。
至於原因,我想大家因該也知道,同樣實現一種功能,自帶的肯定是更好用的,如果不好用,只能說明他還有提升的空間。
這個定律,在很多時候都是比較符合現實的表現的。
所以,爲什麼說代碼開源,有助於計算機行業的發展。
因爲同樣一個工具,總有人覺得不好用,覺得不好用,你拿出更好用的東西出來,大家都會學習你這種更加先進的理念。
正式因爲有了這個不斷循環往復的過程,才造就了近幾十年以來,互聯網行業的蓬勃發展。
深入剖析
說了太多對於這個話題的思考,還是讓我們回到這個問題的本身來吧。
究竟是什麼誘因,讓我奮筆疾書的寫下這篇文章的呢?
先來讓我們檢查一下 cnpm 的版本:
C:\Users\Administrator>cnpm --version
[email protected] (C:\Users\Administrator\AppData\Roaming\npm\node_modules\cnpm\lib\parse_argv.js)
[email protected] (C:\Users\Administrator\AppData\Roaming\npm\node_modules\cnpm\node_modules\npm\lib\npm.js)
[email protected] (C:\Program Files\nodejs\node.exe)
[email protected] (C:\Users\Administrator\AppData\Roaming\npm\node_modules\cnpm\node_modules\npminstall\lib\index.js)
prefix=C:\Users\Administrator\AppData\Roaming\npm
win32 x64 10.0.18362
registry=https://r.npm.taobao.org
不得不說,這個命令顯示的內容可真多,一下子將 nodejs、npm、cnpm 的版本都給暴露了,不過沒關係,這正是我們想要看到的結果。
爲了真實的情景再現,我接下來要安裝 vue-cli 了:
C:\Users\Administrator>cnpm install vue-cli -g
爲了文章的簡介,安裝過程的 log 就不黏貼上來了,反正沒有報錯,異常退出的話,vue-cli 就裝成功了。
下面我來運行一下 vue
:
C:\Users\Administrator>vue
C:\Users\Administrator>"node" "C:\Users\Administrator\AppData\Roaming\npm\\node_modules\vue-cli\bin\vue"
Usage: vue <command> [options]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init generate a new project from a template
list list available official templates
build prototype a new project
create (for v3 warning only)
help [cmd] display help for [cmd]
可以看到的是,我運行 vue
命令的時候,並沒有直接執行 node vue 腳本
這樣的命令,而是喚出了一串很字符來執行 vue:
"node" "C:\Users\Administrator\AppData\Roaming\npm\\node_modules\vue-cli\bin\vue"
爲什麼會這樣呢?
相信跟我以前一樣,沒怎麼思考過這個問題的人,肯定會誤以爲,我們安裝了某個模塊,是不是說我們就安裝了某個直接可以執行的二進制文件呢?
這個答案是否定的,其實我們安裝的 nodejs 模塊都是一些 nodejs 腳本,我們在調用像 vue 這樣的命令的時候,其實就是調用 nodejs 這個引擎,去執行對應的 nodejs 腳本。
這個問題,你往大了想,就能夠看透計算機的本質了。
我們計算機其實不能識別我們的編程語言,不說高級編程語言,即使是彙編、機器碼他也無法識別,他最原始的一面是,只能識別 0、1 兩個不同的電壓信號。
機器碼的作用,就是讓我們來驅動不同的電壓信號組合,來使計算機產生對應的反應。
所以簡而言之,我們寫代碼,其實都只是在按照編程語言提供給我們的規則,來創造一些複雜的組合邏輯,做一些看似很簡單的事情。
這個問題往深了說,就說到計算機組成原理、操作系統的本質等等方面了,我目前也只是略知一二,所以就不往這方面展開了。
我們只要明白,其實無論是我們全局安裝的模塊還是局部安裝的模塊,運行起來都是同一套邏輯。
甚至就連 npm 本身也就是一個模塊,這個模塊和其他的第三方模塊也沒有什麼本質方面的區別。
打開全局安裝的模塊的目錄,我們可以看到,有個 node_modules
文件夾,然後目錄裏面有我們全局安裝的模塊的命令行運行的腳本。
可以看到的是,與 vue 相關的腳本就有 3 個,這是因爲我用 cnpm 安裝的緣故,如果你用 npm 安裝的,應該就只有兩個腳本。
我們分別打開這三個腳本,看看裏面的內容:
首先是 vue:
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/node_modules/vue-cli/bin/vue" "$@"
ret=$?
else
node "$basedir/node_modules/vue-cli/bin/vue" "$@"
ret=$?
fi
exit $ret
可以看到,這是一個 bash 腳本,可以直接在 Linux 或者 Mac 下運行的。
其次是 vue.cmd:
@SETLOCAL
@IF EXIST "%~dp0\node.exe" (
@SET "_prog=%~dp0\node.exe"
) ELSE (
@SET "_prog=node"
@SET PATHEXT=%PATHEXT:;.JS;=;%
)
"%_prog%" "%~dp0\node_modules\vue-cli\bin\vue" %*
@ENDLOCAL
這是一個 Windows 批處理腳本,這個腳本也很簡單,就是拿到 node 的路徑,然後用 node 執行全局模塊中的 vue。
最後是 vue.psl:
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
& "$basedir/node$exe" "$basedir/node_modules/vue-cli/bin/vue" $args
$ret=$LASTEXITCODE
} else {
& "node$exe" "$basedir/node_modules/vue-cli/bin/vue" $args
$ret=$LASTEXITCODE
}
exit $ret
這是一個 powerShell 腳本,同樣也是拿到 node 的路徑,然後執行 vue 腳本。
這個寫法本身是沒問題的,但是會造成在某些電腦上無法使用,比如我的電腦,執行的過程中,會報這樣的錯誤:
接下來,讓我們看看用 npm 安裝的模塊,生成的一鍵運行的腳本,有何不同呢?
爲了公平起見,我們先將用 cnpm 安裝的 vue-cli 刪除掉:
E:\work2\caidademo>npm uninstall -g vue-cli
刪除成功以後,再用 npm 安裝一遍 vue-cli:
E:\work2\caidademo>npm install -g vue-cli
安裝成功以後,我們會發現,這次只生成了兩個腳本:
稍微想想就能明白,他們應該是分別運行在類 Unix 系統和 Windows 系統中的腳本。
vue 腳本我們就不看了,讓我們來研究下 vue.cmd 腳本與之前的有何差異:
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\node_modules\vue-cli\bin\vue" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\node_modules\vue-cli\bin\vue" %*
)
可以看到,差異不大,唯一的差異就是,這個腳本里面直接用 node
,來執行 vue,不是用 "node.exe"
,因爲這種寫法,在 cmd 中是支持的,但是在 powerShell 中是不支持的。
所以其實我們也能用 cnpm 來管理我們的 node 模塊,只是需要改一改 cnpm 給我們自動生成的腳本就行了。
又或許,這就是一個 bug,需要你給 npm 倉庫去貢獻代碼,修改生成腳本的邏輯。
但是 cnpm 的問題肯定不僅僅只是這一個,這個問題只是其中的一個小問題而已。
所以,就像之前說的那樣,如果可以的話,儘量用 npm 去管理 nodejs 模塊吧。
對於某些曾經推動歷史發展,後又淹沒在歷史的長河中的事務,我們同樣保持敬意。