從npm到cnpm再到npm,由cnpm 安裝全局模塊引發的思考 起因

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 真的挺不好用的!

這篇博主的文章分析的非常好,有興趣的同學可以閱讀一下:blog.xgheaven.com/2018/05/03/…

就因爲 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 本身也就是一個模塊,這個模塊和其他的第三方模塊也沒有什麼本質方面的區別。

image.png

打開全局安裝的模塊的目錄,我們可以看到,有個 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 <span class="hljs-string">"<span class="hljs-variable">$basedir</span>"</span>;;
esac

if [ -x basedir</span>/node"</span>];<spanclass="hljskeyword">then</span><spanclass="hljsstring">"<spanclass="hljsvariable">basedir</span>/node"</span> ]; <span class="hljs-keyword">then</span> <span class="hljs-string">"<span class="hljs-variable">basedir/node” basedir</span>/nodemodules/vuecli/bin/vue"</span><spanclass="hljsstring">"<spanclass="hljsvariable">basedir</span>/node_modules/vue-cli/bin/vue"</span> <span class="hljs-string">"<span class="hljs-variable">@
ret=?<spanclass="hljskeyword">else</span>node<spanclass="hljsstring">"<spanclass="hljsvariable">? <span class="hljs-keyword">else</span> node <span class="hljs-string">"<span class="hljs-variable">basedir/node_modules/vue-cli/bin/vue" "@</span>"</span>ret=@</span>"</span> 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</span>=<spanclass="hljsstring">""</span><spanclass="hljskeyword">if</span>(<spanclass="hljsvariable">exe</span>=<span class="hljs-string">""</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable">PSVersionTable.PSVersion -lt “6.0” -or KaTeX parse error: Expected '}', got '#' at position 50: …"hljs-comment">#̲ Fix case when …exe=".exe"
}
ret</span>=<spanclass="hljsnumber">0</span><spanclass="hljskeyword">if</span>(<spanclass="hljsbuiltin">TestPath</span><spanclass="hljsstring">"<spanclass="hljsvariable">ret</span>=<span class="hljs-number">0</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Test-Path</span> <span class="hljs-string">"<span class="hljs-variable">basedir/nodeKaTeX parse error: Expected '}', got '&' at position 25: …>"</span>) { &̲amp; <span clas…basedir/nodeexe</span>"</span><spanclass="hljsstring">"<spanclass="hljsvariable">exe</span>"</span> <span class="hljs-string">"<span class="hljs-variable">basedir/node_modules/vue-cli/bin/vue" args</span><spanclass="hljsvariable">args</span> <span class="hljs-variable">ret=KaTeX parse error: Expected 'EOF', got '}' at position 21: …XITCODE</span> }̲ <span class="h…exe" "basedir</span>/nodemodules/vuecli/bin/vue"</span><spanclass="hljsvariable">basedir</span>/node_modules/vue-cli/bin/vue"</span> <span class="hljs-variable">args
ret</span>=<spanclass="hljsvariable">ret</span>=<span class="hljs-variable">LASTEXITCODE
}
exit $ret
複製代碼

這是一個 powerShell 腳本,同樣也是拿到 node 的路徑,然後執行 vue 腳本。

這個寫法本身是沒問題的,但是會造成在某些電腦上無法使用,比如我的電腦,執行的過程中,會報這樣的錯誤:

image.png

接下來,讓我們看看用 npm 安裝的模塊,生成的一鍵運行的腳本,有何不同呢?

爲了公平起見,我們先將用 cnpm 安裝的 vue-cli 刪除掉:

E:\work2\caidademo>npm uninstall -g vue-cli
複製代碼

刪除成功以後,再用 npm 安裝一遍 vue-cli:

E:\work2\caidademo>npm install -g vue-cli
複製代碼

安裝成功以後,我們會發現,這次只生成了兩個腳本:

image.png

稍微想想就能明白,他們應該是分別運行在類 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 模塊吧。

對於某些曾經推動歷史發展,後又淹沒在歷史的長河中的事務,我們同樣保持敬意。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章