从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 模块吧。

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

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