npm install 你看这一篇就够了

651 阅读9分钟

作为一名专业的切图仔,在键盘上敲下 npm i 的时候已经不需要思考了。直到有一天...

那一天,阳光很好,风也温柔。一篇wiki像是春天里的落英飘向平静的湖面,在我心里激起了阵阵涟漪。没想到天天用的 npm install 也有自己的秘密,看来有必要详细了解一下这个“老朋友”了。

官方文档介绍

docs.npmjs.com/cli/v6/comm…

npm install (with no args, in package dir)
npm install [<@scope>/]<name>
npm install [<@scope>/]<name>@<tag>
npm install [<@scope>/]<name>@<version>
npm install [<@scope>/]<name>@<version range>
npm install <alias>@npm:<name>
npm install <git-host>:<git-user>/<repo-name>
npm install <git repo url>
npm install <tarball file>
npm install <tarball url>
npm install <folder>

aliases: npm i, npm add
common options: [-P|--save-prod|-D|--save-dev|-O|--save-optional] [-E|--save-exact] [-B|--save-bundle] [--no-save] [--dry-run]

虽然文档上写了很多情况,但是我认为总结起来可以分为两种情况:npm installnpm install uri ,也就是说那一堆命令可以认为是:npm install [<uri>] 。 然后每种命令都可以添加一系列的配置参数,就是我们经常看到的 -g -D -S 这些。其中 -g 比较特殊,下面也会单独讲一下,其他的配置参数则是各种开关,用到的时候再去了解就行。

npm install

首先是不传 [<uri>],这个大家基本就是拉代码的时候会用到。作用就是安装依赖:安装项目需要的依赖或者将当前项目当做依赖安装到全局。

如果没传配置参数 -g--global) ,也就是项目初始化用的最多的 npm i 或者 npm install 则是安装当前项目需要的依赖,默认安装 package.json 中列出来的全部依赖。如果只需要安装 dependencies 中的依赖,则需要传入 --production 参数(或者 NODE_ENV 设置为 production ),也就是 npm i --production

这里插一句,平时安装依赖的时候,有些人是不区分 dependencies devDependencies 的,如果发现本地可以打包但是构建机无法打包,可以排查一下是不是有必需依赖安装到了 devDependencies ,此时可以 npm i --production=false 强制安装所有依赖试试。

如果传了配置参数 -g ,也就是在项目根目录下执行 npm i -g ,则会将当前项目安装到全局中。这个操作一般都用不到。

npm install [<uri>]

如果传入了一个 [<uri>] 则说明需要安装一个指定的依赖到当前项目,这个 [<uri>] 可以是一个本地路径、一个远程绝对路径、一个依赖包标识,而这些 [<uri>] 最终指向的都是一个 “使用符合规范的 package.json 描述的应用程序包” ,或者这样的包的后缀名为 .tar .tar.gz .tgz 的压缩包。

简单来说,只要是能够最终被解析为一个指向依赖包的地址就可以了。

下面针对 [<uri>] 的几种情况分别做一下说明。

本地路径

首先我们要知道,安装一个依赖,最终都是需要安装到本地磁盘上。那一个本地相对路径代表的资源,本来就已经在本地磁盘上了。这种情况安装一个 ”本地路径“ 代表的资源,没有必要再复制一次,只需要链接过去就行。

也就是说安装一个本地的依赖,会给当前项目添加一个符号链接。

# 举个例子,安装一个本地路径为 '../path/A' 的 A 依赖
npm i -D ../path/A
# 下面是命令执行的操作
# 1、在当前项目的 node_modules 安装 A 的依赖
# 2、在当前项目的 node_modules 文件夹中添加一个指向 A 的符号链接(快捷方式)  
# 3、在当前项目的 devDependencies 加上一个配置 "A": "file:../path/A"(依赖配置)  

简单讲一下 npm i [本地路径] 和 npm link 的区别:

npm i 会走完整的安装流程,执行preinstall/postinstall 钩子,而 npm link 不会

详情可以查看:stackoverflow.com/questions/5…

远程路径

是的,没错,可以直接指定依赖的绝对路径,这个路径可以是 http https ssh 协议。也就是说, 你可以将依赖“藏” 在一个犄角旮旯里面,然后用绝对路径来安装。

也就是说,一个依赖库,不用上传到 npm 也能使用,直接用项目地址安装就行了,是不是很方便。

# 安装一个 github 上的包
npm install https://github.com/indexzero/forever/tarball/v0.5.6

如果你有个私有依赖需要安装,又不想配置源,那么直接给个路径就很方便了。

依赖包标识

终于,回到了熟悉的部分。

大家最熟悉的肯定是 npm install <name> 这样的形式,例如:npm install react 安装最新版本的 react

好的,轻松愉快的环节结束了,现在我们继续聊一聊节。

首先,你有没有想过,为啥你写一个包名,就可以安装呢?

其实这个包名,是有前提的,指的是默认仓库里面的这个包的最新版本。这里面有两个前提:默认仓库、最新版本。这里面牵扯出两个概念:仓库 和版本。看似你写了一个包名,但是 npm 会将 “仓库” 和 “版本” 按默认规则给补全,这样才能得到一个唯一的标识。也就是说,最终还是通过一个 [<uri>] 来获取指定的包

仓库

为了方便大家下载依赖,npm 提供了一个公共仓库给开发来上传自己的包。而这个仓库,也是下载时的默认地址。基于这种情况,就无需记住冗长的仓库地址,直接使用包名来下载依赖。

默认情况下,这个仓库地址为:registry.npmjs.org/。

但是有些时候,比如啊,不是针对谁啊,就访问这个地址很费劲,下载贼拉慢,那就需要改一下这个地址了。比较常用的有:cnpm 源 r.cnpmjs.org/ 。这里推荐使用 nrm 来进行源管理,方便快捷。

私有仓库

大部分时候,公司、组织内部会有一些不可公开的库。这东西把,放到公共仓库显然是不合适的。但是把公共仓库全部备份下来把,更不合适。这个时候,就需要有种办法,能够指定某几个库使用一个源,另外的走公共仓库。

其中一种办法就是 [<@scope>] ,给这些特殊的包,加一个 ,一般都是使用公司、组织名。例如,我在的 banana 公司有个 cool 库,这个库如果上传到公共仓库领导就会让我不太 cool 了。于是我搞了一个私有 npm 仓库 http://precious ,上传一个 @banana/cool 。然后我需要在 .npmrc 文件中添加一行配置 @banana:registry=http://precious/ ,告诉 npm 只要是 @banana 域下的包,都去 http://precious/ 仓库中找。

好险,保住份工。

版本

版本应该都很熟悉了,版本可以是一个符合 语义化版本号 ,或者是一个 标记 。一般安装的时候,如果没有指明版本,使用的是 latest 标记,安装最新的稳定版本。

版本规则比较复杂,可以是一个确定的版本,可以是一个版本区间,了解一下即可。

常用的符号有 ~ ^

  1. 不带符号,只能安装指定版本:1.2.3 := 1.2.3
  2. ~ 符号,允许最后一个版本号变动:~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0
  3. ^ 符号,允许后两位版本号变动:^1.2.3 := >=1.2.3 <2.0.0

平时用的最多的就是 1和 3 了,但是由于 lock 文件的存在,其实每次安装都会按照 lock 文件中的准确版本号进行安装。

别名

大家有没有发现,前面我们在安装依赖的时候,其实没有指定安装依赖的名字。这种时候,默认使用的就是目标的 package.json 中配置的那个 name 。我们运行 npm i react 不但会将 react 下载到当前项目,同时也会在依赖列表里面添加一个 react 依赖。

你说这不是很正常嘛?这的确很正常。

但是,假如我想要给这个依赖取个别的名字呢?执行完了 npm i react 之后,我使用的时候想要 import React from 'vue' 行不行?

这要求听着挺难受的,但是也不是不能实现。

此时可以使用 npm install <alias>@npm:<name> 实现,也就是 npm install vue@npm:react 就可以使用一个“披着 vue 皮的 react ”。

Cache

前面讲了,npm install[<uri>] 本质是将 [<uri>] 指向的 package 安装到指定的地方——项目内或者全局,如果已经是本地资源则是创建一个符号链接。

但是我们在本地通常不是只有一个项目,各个项目直接的依赖可能会大同小异。每次都去远程下载很浪费时间和资源,这种情况下 cache 就是一个很合理的配置了。

前面提到过,运行 npm install [target] 实际会执行如下几个大致操作:

  1. 判断 target 是一个地址还是一个标识
  2. 如果是一个标识,则根据配置的源解析出对应的地址
  3. 通过这个最终的地址,去拿依赖包
  4. 如果拿到的是个指定后缀名的压缩包,则进行解压

其实在第 2 、第 3 步中间还有几个步骤,完整的情况应该是:

  1. 判断 target 是一个地址还是一个标识
  2. 如果是一个标识,则根据配置的源解析出对应的地址
  3. 查看当前缓存策略
  4. 如果可以使用缓存,则去缓存仓库拿对应的依赖包
  5. 如果不使用缓存,则通过这个最终的地址,去拿依赖包
  6. 如果拿到的是个指定后缀名的压缩包,则进行解压

通常情况下,我们都不需要管这个 cache,但是我们最好能够了解一下 cache 会造成的影响。

  1. 如果被缓存的包由于种种原因被本地修改过,则会导致本地和远程不一致;
  2. 如果本地包安装的时候指明了特殊的源,但是有没有将这个源写入配置中,则可能会导致远程无法成功安装依赖。

简单总结一下,如果本地能跑,而且删除了 node_modules 文件夹也还是能够正常安装,但是远程就是跑不起来,就可以尝试禁用本地 npm 缓存,很有可能就是本地有缓存,但是远程没有。