起因
因为工作的时候,公司提供的是 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](https://upload-images.jianshu.io/upload_images/11245386-9f67c7db1f5485be.png&originHeight=433&originWidth=657&search=&size=65300&status=done&width=657?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
打开全局安装的模块的目录,我们可以看到,有个 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](https://upload-images.jianshu.io/upload_images/11245386-406ce7baed816d42.png&originHeight=118&originWidth=839&search=&size=11312&status=done&width=839?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
接下来,让我们看看用 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](https://upload-images.jianshu.io/upload_images/11245386-41564e02dcafb84d.png&originHeight=383&originWidth=631&search=&size=50224&status=done&width=631?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
稍微想想就能明白,他们应该是分别运行在类 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 模块吧。
对于某些曾经推动历史发展,后又淹没在历史的长河中的事务,我们同样保持敬意。