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
cnpm@6.1.0 (C:\Users\Administrator\AppData\Roaming\npm\node_modules\cnpm\lib\parse_argv.js)
npm@6.12.0 (C:\Users\Administrator\AppData\Roaming\npm\node_modules\cnpm\node_modules\npm\lib\npm.js)
node@10.16.3 (C:\Program Files\nodejs\node.exe)
npminstall@3.23.0 (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
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 "$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 脚本。

这个写法本身是没问题的,但是会造成在某些电脑上无法使用,比如我的电脑,执行的过程中,会报这样的错误:


image.png
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
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 模块吧。

对于某些曾经推动历史发展,后又淹没在历史的长河中的事务,我们同样保持敬意。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容