# 1.npm

## NPM

\[TOC]

## 一 概述

npm是世界上最大的js包管理工具。当然还有其他的管理工具,比如yarn.除此之外还可以通过cdn引入包,比如google的:[Google Hosted Libraries](https://developers.google.com/speed/libraries/)

### 2 历史

#### 2.2 npm5.2

增加了`npx`命令，见npx部分

### 3 常识

#### 3.1 淘宝镜像命令规则

比如我们打开node-sass\@4.9.0的链接:<https://npm.taobao.org/mirrors/node-sass/v4.9.0/>

可以看到新网页中有一个文件列表，后缀为.node的文件即位编译好的，文件名格式形如`[platform]-[architecture]-[nodejs version]_binding.node`:

* `platform` :操作系统平台 darwin为Mac OS X，linux为linux，win32为windows
* `architecture` :系统架构 目前有x64,ia32
* `nodejs version` :nodejs版本号，取全版本号的前两位，比如4.6.x为46，5.1.x为51

所以如果发现本网页没有所需要的二进制文件，镜像也需要从github拉取数据，此时安装node-sass是不会加速的。

#### 3.2 零依赖模块

### 4 文档

### 5 网址

* npm被依赖榜单:<https://www.npmjs.com/browse/depended>
* npm getting-started:<https://www.npmjs.com/browse/depended>

## 二 安装配置

安装前：

1. 先检查nodejs和npm版本

   ```bash
   node -v
   npm -v
   ```
2. 换源之后,想把源换回去用:`npm config set registry http://registry.npmjs.org`

### 1 win

1. 直接安装nodeJS，里面带有npm
2. 配置

   win的npm配置文件`.npmrc`的默认位置是`C:\\Users\\Administrator\\.npmrc`,包安装的默认位置是`C:\Users\xxx\AppData\Roaming\npm`

   1. 换源：`npm config set registry https://registry.npm.taobao.org`
3. win10安装后运行npm出现:log.progressEnabled = log.gauge.isEnabled()...TypeError: log.gauge.isEnabled is not a function...

   我当时是运行`sfc /scannow`后重新安装node解决的

### 2 linux

#### 2.1 ubuntu

1. `sudo apt install npm`
2. 配置源，虽然仓库没有被qiang，但是速度较慢：

   1. 临时使用
   2. 使用cnpm(本质是安装)
   3. 配置`~/.npmrc`(推荐，一劳永逸)：

   ```bash
   registry =https://registry.npm.taobao.org
   ```
3. 更新npm:`sudo npm i -g npm`

### 3 mac

安装最新的node就自带了npm。

查看全局路径（全局安装包的目录）:

1. `npm config get prefix`,比如我是brew安装的nvm，然后nvm安装的node，输入这个命令后输出的是`/Users/xushike/.nvm/versions/node/v10.11.0`
2. `npm root -g`:效果同上

配置淘宝镜像：mac下home目录默认没有`.npmrc`文件,使用`npm config set registry https://registry.npm.taobao.org`命令会生成并配置好`.npmrc`,一步到位.

### 4 配置

1. 全局安装路径
   1. 修改全局安装路径

      ```bash
      npm config set prefix pathA
      ```
2. (推荐)配置环境变量`NODE_PATH`：如果没有配置，则无法从全局目录里加载依赖包

## 三 基础

### 0 架构和常见词语

### 1 工具生态

1. Node版本管理工具：允许您在系统上安装和切换多个版本的 Node.js 和 npm
   1. OSX或Linux：nvm和n
   2. Windows：nodist和nvm-windows

      ```bash
      # 安装nvm-windows
      winget install --id CoreyButler.NVMforWindows -i
      # 用nvm下载安装lts版nodejs（会自动安装nodejs和对应版本的npm）
      nvm install lts
      # 切换nodejs版本（切换时会自动切换对应的npm，且清空全局node_modules？）
      nvm use lts
      ```

### 1 安装卸载命令

#### 1.1 安装模块

**npm install(npm i)**

该命令通常是用来安装依赖项，使用时会发生：

1. 安装项目所有的依赖项；
2. 如果使用 ^ 或 \~ 来匹配依赖项的版本时，则 npm 可能无法安装确切版本；
3. 利用 npm install 安装新依赖项时，会更新 package-lock.json。

很多模块里有一个或多个需要配置到PATH路径下的可执行模块，可执行安装全局模块时，如果(待整理)

安装位置：默认安装在当前目录的node\_modules文件夹下。带上参数`-g`则是安装到全局的node\_modules文件夹下,全局的node\_modules目录路径有默认值(通过`npm root -g`查看)，如果设置了环境变量`NODE_PATH`，则由`NODE_PATH`决定

```bash
# win下默认是
%USERPROFILE%\AppData\Roaming\npm
```

安装时的覆盖问题：如果某个模块已经安装过且和现在要安装的版本不一致，那么默认情况下会被现在的版本覆盖掉。想不覆盖，也就是同时存在多个版本的话可以：

1. 使用别名(npm must >= 6.9)

   ```bash
   # 语法npm i <alias>@npm:<packageName>@版本

   # 安装的例子
   npm i antd3@npm:antd@3
   npm i antd4@npm:antd@4
   # 使用的时候就用别名，比如antd3, antd4
   ```

常用操作:

1. 安装某个环境下的所有模块：一般有dependencies、devDependencies和optionalDependencies
   1. 安装所有环境的模块：不加参数使用，就会根据目录下的`package.json`配置文件下载所需的模块，默认是安装里面所有的依赖，包括dependencies、devDependencies、optionalDependencies（如果有的话）。
   2. 安装对应环境的模块：
      1. 只安装dependencies:`npm install --production`
      2. 只安装devDependencies：`npm install --dev`
   3. 安装除了某个环境的所有其他环境的依赖
      1. 除了optionalDependencies:`npm install --no-optional`
2. 安装的位置：本地或全局
   1. 本地：默认就是安装在本地，会安装在当前目录的node\_modules文件夹下(没有则在执行命令的目录下自动创建该文件夹)里。
   2. 全局：需要加上参数`-g`(`--global`):安装全局依赖，如果没有指定包名，则将当前目录中的包安装至全局。
3. 安装指定的模块

常用参数:

1. `--dry-run`:走一遍安装的流程并报告结果，但实际上没有安装任何依赖
2. `--save`(老的写法是`-S`,最新的写法是`--save-prod`):安装模块并写入到当前项目`package.json`文件的`dependencies`中
3. `--save-dev`(老的写法是`-D`):安装模块并写入到当前项目`package.json`文件的`devDependencies`中

   ```bash
   npm install -D mocha
   ```
4. `--verbose`:注意这里不像很多其他工具那样可以写成`-v`，这里的`-v`是打印NPM版本
5. `--save-exact`:会安装模块的确切版本(疑问:和不加有什么区别?)

关于模块名后的版本号(即版本发布时的tag)：包名后可以直接跟\[@version]即包的版本号来安装制定版本,也可以用通用的版本名称,如

* `@latest`或`@next`:最新版本

  ```bash
  npm install xxx@1.0.0
  npm install npm@latest -g
  ```

**npm ci**

从npm6开始新增了该命令，用于安装确切的依赖。执行时会发生：

1. 将会删除项目中的 node\_modules 文件夹；
2. 会依照项目中的 package.json 来安装确切版本的依赖项；
3. 不像 npm install ， npm ci 不会修改你的 package-lock.json 。但是它期望你的项目中有一个 package-lock.json 文件 - 如果你没有这个文件， npm ci 将不起作用，此时必须使用 npm install 。

如果你使用 npm ci ，你将获得 可靠 的构建。特别是当您在 Jenkins 或 GitLab CI 等 持续集成工具 中运行时，这将非常有用。

#### 1.2 卸载模块 npm uninstall

删除制定的依赖包并且完全移除为了该包而安装的任何文件,卸载非全局模块的时候,会同时从如果package.json中移除(如果有的话)

1. 常用参数，和install一样
2. 别名:`remove 、rm、r、un、unlink`
3. 之前卸载之后还需要执行`npm cache clean`清理,但是npm\@5之后还要加上`--force`,但是可能会导致一些不必要的问题,所以慎用,清理之后使用`npm cache verify`(待补充).具体见缓存部分.

#### 1.3 更新模块 npm update

升级所有依赖包至版本规则允许的最高版本,并安装缺失的依赖包

1. 常用参数
   * `-depth Infinity`:<npm@2.6.1k>开始，update命令默认只升级最顶层的依赖，即直接的依赖，加上该命令则升级所有依赖
   * `--save`:升级同时将升级的版本号记录到package.json

```bash
# 默认升级当前目录下安装的包，如果当前目录下未安装对应的包，则直接返回
npm update express 
# 加上 -g 升级全局目录下安装的包
npm update express -g
```

#### 1.4 清除未用到的模块`npm prune`

### 2 查看命令

1. 查看已安装模块:`npm list`(`npm ls`):显示模块名和版本信息,直接使用会显示无关的，缺少的和无效的软件包
   1. 查看某个模块的版本`npm list [moduleName]`:默认是从本地模块查找

      ```bash
      # 假如本地模块里安装了xlsx，则查看其版本用
      npm list xlsx 
      # output:
      # appA@1.1.1 /Users/xxx/appA
      # └── xlsx@0.14.5 
      ```
   2. `-g`查看全局安装的模块
   3. `--depth=[num]`:限制模块的层级，最顶级的从0开始，比如查看最顶层模块`npm list -g --depth=0`
2. 查看安装目录:`npm root`
   1. 直接使用:查看项目中模块所在的目录
   2. `-g`:查看全局安装的模块所在的目录
3. 查看模块的`package.json`相关信息
   1. 直接使用`npm view [moduleName]`
   2. `dependencies`:查看包对其他包依赖信息(但我试了没用,待补充)
   3. `version`:查看包
4. 查看过时的包:`npm outdated`
5. 查看模块的主页和文档
   * `npm home [moduleName]`
   * `npm docs [moduleName]`
6. 查看某个命令的文档:`npm help xxx`，比如查看install的文档`npm help install`,在windows下会默认打开网页

### 3 配置命令

1. 查看所有配置:`npm config ls -l`
2. 查看某个配置:`npm config get xxx`
   * 比如查看cache目录,可以用`npm config get cache`

### 4 缓存

在 npm\@5 以前，每个缓存的模块在 \~/.npm 文件夹中以模块名的形式直接存储，例如 koa 模块存储在 \~/.npm/koa 文件夹中。而 npm\@5 版本开始，数据存储在 \~/.npm/\_cacache 中，并且不是以模块名直接存放。 npm 的缓存是使用 pacote 模块进行下载和管理，基于 cacache 缓存存储。由于 npm 会维护缓存数据的完整性，一旦数据发生错误，就回重新获取。因此不推荐手动清理缓存，除非需要释放磁盘空间，这也是要强制加上 --force 参数的原因。

目前没有提供用户自己管理缓存数据的命令，随着你不断安装新的模块，缓存数据也会越来越多，因为 npm 不会自己删除数据。

1. `npm cache clean`

   删除缓存目录下的所有数据。从 npm\@5 开始，为了保证缓存数据的有效性和完整性，需要加上 --force 参数。
2. `npm cache verify`

   验证缓存数据的有效性和完整性，清理垃圾数据。

### 4 运行相关命令

1. 更改包后重建:`npm rebuild [moduleName]`

### 5 package.json

一般每个项目的根目录下都有该配置文件,可通过`npm init`交互式生成

#### 5.1 版本控制

npm的版本号遵循semver2.0规范([语义化版本2.0.0](https://semver.org/lang/zh-CN/))

1. semver的版本号简单概括是：

   XYZ （主版本号.次版本号.修订号/补丁版本号）修复问题但不影响API 时，递增修订号；API 保持向下兼容的新增及修改时，递增次版本号；进行不向下兼容的修改时，递增主版本号。
2. semver不仅仅是概念,还有自己的js包,为发布版本设计的
3. npm的版本限定(`semver-ranges`)
   * 波浪号`~`:不改变大版本和中版本号,且不低于指定版本的最新的版本.比如`~1.2.2`,则表示安装`1.2.x`的最新版本,不低于`1.2.2`且不超过`1.3.0`
   * 脱字符`^`
     * 如果大版本号不为0:不改变大版本号,其他类似`~`,比如`^1.2.2`,表示安装`1.x.x`的最新版本.
     * 如果大版本号为0:跟`~`一样
   * `latest`或`*`:安装最新版本
   * 大于,小于,等于
   * `x`:任意版本,如`1.2.x`
   * `||`和`&&`
   * `alpha`/`beta`/`rc`:

在初始开发阶段，怎么去处理 0.y.z 的版本号：一个简单的做法是， 使用`0.1.0`作为第一版初始开发版本号，然后为随后的发布包递增次版本号(minor version)

#### 5.2 常用属性说明

**name**

包名(模块名、或者说项目名)

**version**

包的版本号

**description**

包的描述

**homepage**

官网

**author**

作者

**contributors**

贡献者

**type**

如果要加载ES模块，需要设置为"module",此时引入包的语法需要使用ES的语法

```json
{
    "type": "module"

}
```

**scripts**

`npm run`是执行配置在package.json中的脚本,比如`npm run start`是运行名称为"start"的脚本。`dev`，`build`没有强制含义，注意理解这里“强制”的意思。也就是说，我们在通常情况下，约定当你使用dev时，指的是和开发环境相关的事情。当使用build时，通常就是指和“编译”相关的事，当使用dist时，通常就指和发布相关的事情。参考npm-hooks。

```json
"scripts": {
    "preinstall": "echo here it comes!",
    "postinstall": "echo there it goes!",
    "start": "node index.js",
    "test": "tap test/*.js"
}
```

**dependencies和devDependencies**

依赖包列表。其中键是模块名,值是模块的版本范围.

* dependencies: 是运行应用的基础,指定了项目运行所依赖的模块
* devDependencies: 只有在开发应用时才会用到,指定项目开发所需要的模块,你不必在生产环境的应用中部署它们，当然，就算部署了也没什么坏处。以Angular举例:
  * `@angular/cli`
  * `@angular/compiler-cli`
  * `@angular/language-service`:Angular 的语言服务会分析组件模板，并且提供类型信息和错误信息，那些支持 TypeScript 的编辑机器可以使用它们来提升开发体验。比如vscode的Angular Language Service插件,可以提示`*ngIf`,`(click)`,插值表达式等语法以及简单的语法错误检测.

**peerDependencies**

假如项目同时依赖A和B模块的1.0版本,但A模块又依赖B模块的2.0版本,大多数情况下B的两个版本可以共存,不会构成问题.但是，有一种情况，会出现问题，就是这种依赖关系将暴露给用户:最典型的场景就是插件.这个时候就需要提醒用户，如果A和B一起安装，那么B必须是2.0模块。如,

```json
{
"name": "chai-as-promised",
    "peerDependencies": {
        "chai": "1.x"
    }
}
```

上面代码指定，安装`chai-as-promised`模块时，主程序`chai`必须一起安装，而且`chai`的版本必须是1.x。如果你的项目指定的依赖是`chai`的2.0版本，就会报错。 注意npm3.0版开始，`peerDependencies`不再会默认安装了

**bin**

指定各个内部命令对应的可执行文件的位置。比如在项目appA内安装了webpack，一般情况下需要这样来执行`./node_modules/.bin/webpack -v`，或者使用npm bin这样来执行`npm bin /webpack -v`

**main**

指定了加载的入口文件，`require('moduleNameA')`时就会加载这个文件。这个字段的默认值是模块根目录下面的`index.js`。

**config**

用于添加命令行的环境变量,如下`package.json`,

```json
{
    "name" : "foo",
    "config" : { "port" : "8080" },
    "scripts" : { "start" : "node server.js" }
}
```

然后就可以在server.js中引用config字段的值,如

```javascript
http
    .createServer(...)
    .listen(process.env.npm_package_config_port)
```

**engines**

指明了该模块运行的平台，比如Node或npm的某个版本或者浏览器

### 6 package-lock.json

为什么需要该文件:npm的包依赖可以看成有两种树,逻辑树和物理树.逻辑树就是npm根据package.json的版本范围解析出来的依赖关系树;而物理树就是node\_modules文件夹夹结构.安装过程会有去重算法之类的,所以逻辑树结构和物理树结构可能不完全一样.(这里我觉得有两种可能的原因导致不一致:一是package.json只能锁定包自身的版本而不能锁定所依赖的包的版本;二是包的版本可能写得是一个范围,会导致解析出来的结果不一样)这个时候就可以用到该文件,它相当于是逻辑树和物理树的快照,可确保每次安装的结果都是一样的.

生成命令：从npm5开始，执行`npm i`会自动生成该文件。

自npm5.0版本发布以来,`npm i`的规则发生了三次变化:

1. npm 5.0.x 版本，不管package.json怎么变，npm i 时都会根据lock文件下载
2. 5.1.0版本后 npm install 会无视lock文件 去下载最新的npm
3. 5.4.2版本后:如果改了package.json，且package.json和lock文件不同，那么执行`npm i`时npm会根据package中的版本号以及语义含义去下载最新的包，并更新至lock。如果两者是同一状态，那么执行`npm i` 都会根据lock下载，不会理会package实际包的版本是否有新。

在npm5以后，其内容和npm-shrinkwrap.json一模一样.

注意:该文件需要提交到版本控制中.

### 7 其他文件

#### 6.1 npm-shrinkwrap.json

用于将项目的模块版本进行精确锁定,使用流程如下

1. `npm install ...`安装模块
2. `npm prune`:清除未使用的模块
3. `npm shrinkwrap`:生成`npm-shrinkwrap.json`
4. 提交该json文件到git,这样其他人clone项目之后执行`npm install`所还原的依赖树就是一样的.

#### 6.2 npm-debug.log

npm的错误报告

## 四 高级

### 1 npm-check

npm-check 是用来检查npm依赖包是否有更新，错误以及不在使用的，我们也可以使用npm-check进行包的更新。

### 2 发布包的步骤

1. npmadduser登录
2. npminit
   1. 提问，根据回答生成package.json文件
3. npm publish
   1. 版本tag:默认是`latest`
4. npm unpublish（不推荐使用）
5. `npm deprecate`:表示放弃一个包，该包在npm中没有取消，类似于过时的意思，依然可以被用户安装，安装该包的用户会看到警告信息“npm WARN deprecated xxx\@xxx”。
6. `npm view`：显示一个包(在npm上)的详细信息

注意:如果设置了淘宝镜像,npm publish 发布自己包的时候要把 registry 这一行给注释掉，否则就会发布到淘宝源上去了

### 3 npx

`npx`有什么用:npx想要解决的主要问题，就是调用项目内部安装的模块。npx的主要功能就是帮你执行依赖包里的二进制文件，比如某个项目内安装了Mocha.js，一般情况下，想调用Mocha，只能在项目脚本和 package.json 的scripts字段里面， 如果想在命令行下调用，就需要写成这样`node_modules/.bin/mocha --version`。如果使用npx，就可以很方便的这样调用`npx mocha --version`。npx的原理就是运行的时候，会到`node_modules/.bin`路径和环境变量$PATH里面，检查命令是否存在，所以系统命令也是可以调用的，比如`npx pwd`

## 五 经验

常见源

1. <https://registry.npmmirror.com>

## 六 问题

### 1 已解决

#### 1.1 用`npm list -g`命令出现大量的`npm ERR! extraneous:...`

当时我的npm版本是3.10.10，升级之后(5.6.0)问题解决

#### 1.2 Unexpected相关

**Unexpected end of JSON input while parsing near**

(待整理) 方法一:`npm cache clean --force` 方法二:删除package-lock.json

**unexpected end of file**

错误如下：

```
npm ERR! code Z_BUF_ERROR
npm ERR! errno -5
npm ERR! zlib: unexpected end of file
```

场景：某个模块安装不下来，然后出现这个错误

原因：环境问题，很可能是网络问题

解决：taobao镜像或者科学上网

#### 1.3 ubuntu中使用`sudo npm`命令时提示找不到命令

因为安全原因,ubuntu执行sudo命令会改变PATH.应该有几种解决办法:

方法一:`sudo apt install npm`重新安装npm

#### 1.4 如果提示创建xxx文件夹权限被拒绝

stackoverflow网友说:可能是因为npm有多个实例,可以强行指定用某个实例来运行`sudo /usr/bin/npm install xxx`

#### 1.5 Unsupported platform for fsevents\@1.2.0: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

因为fsevents是专用于mac系统的,所以在其他系统上安装都会出现类似的警告.

解决的办法是加上无可选参数,如`npm i --no-optional`(待测试)

#### 1.6 npm warn no such file or directory

可能一:缺少的是package.json

可能二:就是我删除package-lock.json,然后`npm i`报的警告,报了一大堆这样的,我的解决办法是重新`npm i`

#### 1.7 npm does not support Node.js x.x.x

重新安装对应node版本或npm版本

#### 1.8 重新安装的最佳实践

依次执行: rm -rf node\_modules rm package-lock.json npm cache clean npm install

#### 1.9 安装node-sass

有好几种报错,常见的有:

1. `SKIPPING OPTIONAL DEPENDENCY node-sass ...`

首先要知道的是，安装 node-sass 时在 node scripts/install 阶段会从 github.com 上下载一个 .node 文件，大部分安装不成功的原因都源自这里，因为 GitHub Releases 里的文件都托管在 s3.amazonaws.com 上面，而这个网址在国内总是网络不稳定，所以我们需要通过第三方服务器下载这个文件.

方法一(实测成功):用cnpm安装node-sass

方法二(实测未成功):`SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ npm install node-sass`

方法三(实测未成功):配置代理安装

#### 1.10 本地包和全局包的优先级

优先使用本地的命令.所以对于全局有而本地没有的参数,依然不会生效.

#### 1.11 运行npm search的时候出现 ... No search sources available

把源设置回官方的`npm config set registry https://registry.npmjs.org`

### 2 未解决

#### 2.1 node已经升级到9.x,为什么npm安装的时候还是提示;wanted xxx ,current 4.x

#### 2.2 ubuntu修改packa.json文件为什么需要管理官身份

#### 2.3 Missing write access to xxx路径(非常困扰)

#### 2.N

2. package-lock.json
3. 注册npm账户的好处,除了记录自己使用的仓库,还有什么?
4. 深入 Node 模块的安装和发布；<https://segmentfault.com/a/1190000004221514>
5. 更新时以.json文件为准还是以更新的 版本为准?
6. preinstall
7. linux下npm全局包的安装位置
