npm 学习笔记

时间:2024-01-25 20:06:07

一、介绍


1、是什么

npm 全称是 Node Package Manager,即 Node 包管理工具

但是发展到后来,并不仅是适用于 node.js 的包。

所以现在看 node_modules 这个名字实在有点偏颇,现在 npm 自己都说自己是通用的包管理,并不局限于 node,然而这名字却不好改了。

npm 每周大约有 30 亿次的下载量,包含超过 600000 个包。

2、历史

npm 的发展是跟 Node.js 的发展相辅相成的。

Node.js 是由一个在德国工作的美国程序员 Ryan Dahl 写的。他写完了 Node.js,但是觉得缺少一个包管理器,于是他和 npm 的作者一拍即合、抱团取暖,最终 Node.js 内置了 npm。

3、包含什么

  • website
  • registry
  • 命令行工具 (CLI)

二、安装


1、安装

直接安装 Node.js 即可。

安装 Node.js 时,将自动安装 npm。

但是,npm的更新频率比Node.js的更新频率高,

2、更新

npm install npm@latest -g.

3、查看版本

npm -v

写本文时为 v6.13.0

4、Node.js 与 npm 的版本对应关系

可查阅:https://nodejs.org/zh-cn/download/releases/

其中一行如下:

Version LTS Date V8 npm
Node.js 13.1.0 2019-11-05 7.8.279.17 6.12.1

三、包


1、如何找包

  • 可以去官网直接搜索包名:https://www.npmjs.com/

  • google 一下

  • 找网上公开的精选清单

  • 看别的知名开源库用的是什么

2、全局包

如果你想将包作为一个命令行工具,(比如 grunt CLI),那么你应该选择全局安装

(1)安装

npm install -g <package_name>

(2)更新

查找需要更新的:

npm outdated -g

红色表示小版本升级,可以无脑升级。

黄色表示大版本升级,升级可能会遇到兼容性问题。


直接更新:

npm update -g.

只会更新到 wanted (红色),而不是 latest (黄色)。如果要指定其他版本的更新,请用 npm install 代替。

(3)查看已安装

npm list

这里很详细,但是嵌套太深,如果觉得冗余,可以用上面介绍的 npm outdated -g 查看。

(4)卸载

npm uninstall -g <package_name>

3、本地包

如果你自己的项目依赖于某个包,并通过 Node.js 的 require 加载,那么你应该选择本地安装


涉及概念:

  • /package.json文件

  • /package-lock.json文件

  • /node_modules文件夹

(1)package.json

npm init :在当前目录新增 package.json 文件

npm init 是交互式的方式创建 package.json,

npm init -ynpm init --yes 可以静默创建 package.json(自动识别你的项目信息)。

基本的 package.json 文件 demo:

{
  "name": "npm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
(2)安装

npm install lodash = npm install --save lodash

npm install --save-dev lodash

  • --save 为生产中需要这个软件包,如 lodash。

  • --save-dev 为仅在开发和测试时需要,如 mocha。

注意:默认安装该包的 Lastest 版本


上面的命令执行后,具体实行的步骤如下:

1、当前目录的 package.json 中添加该包的版本描述 (注:用 ^ 版本 标记)

  • 如果是 --save ,则添加到 "dependencies" 字段

  • 如果是 --save-dev ,则添加到 "devDependencies" 字段

其实还有更多的场景分类:

  • peerDependencies
  • optionalDependencies
  • bundledDependencies / bundleDependencies

不多介绍了,待写。

2、当前目录的 package-lock.json 中添加该包的版本描述 (注:用 具体版本 标记)

3、安装的包会放在当前目录的 node_modules 文件夹里。


安装后如何使用:

var lodash = require('lodash');

var output = lodash.without([1, 2, 3], 1);
console.log(output);
(3)更新

查找需要更新的:

npm outdated


直接更新:

npm update

只会更新到 wanted ,而不是 latest。如果要指定其他版本的更新,请用 npm install 代替。

(4)卸载

npm uninstall lodash = npm uninstall --save lodash

npm uninstall --save-dev lodash

上面的命令执行后,具体实行的步骤如下:

1、/node_modules中该包的文件删除

2、/package.json中该包的版本描述删除

4、包(Packages)跟模块(Modules)的区别

模块:在 CommonJS 世界中,大多数情况下,一个文件就是一个模块,并且可以被 require() 。

:大多数情况下,Node.js 应用就是一个包(他可能包含很多模块),并且有一个 package.json 文件。

可以简单粗暴理解成:包 > 模块

注意1:大多数的包都可以当作模块去 require(),但少部分不行(如仅提供 cli 命令)。

注意2:为什么叫 node_modules 而不是叫 node_packages? 因为包安装到 node_modules 后的目的就是为了被 require() 使用,而模块才能被 require()。

四、镜像


上面介绍了全局/本地包的安装,如果嫌在国内安装速度慢,可以使用淘宝npm镜像http://npm.taobao.org/

# 临时
npm install express --registry https://registry.npm.taobao.org
# 永久
npm config set registry https://registry.npm.taobao.org

五、发布包


待写。

https://www.npmjs.cn/getting-started/publishing-npm-packages/

.npmignore 文件可以列出不想打包的文件,避免把一些无关的文件发布到 npmjs 上。但是,统一使用 .gitignore 可以满足绝大部分场景下的需求。而且,只存在 .gitignore 的情况下,npm publish 会尊重 .gitignore 的声明,而 .npmignore.gitignore 同时存在的情况下,npm publish 会忽略 .gitignore,而不是取两者的并集。

六、包版本


安装包就涉及到包的版本。有两种描述包版本的方式。

1、语义版本控制

(1)介绍

首先,semver是一个语义化版本号管理的模块。可以实现版本号的规范、解析、比较

而 semver 的版本号格式,形如[X,Y,Z] 或 [major, minor, patch],如 2.0.1,且项目应始于 1.0.0

在代码里 require('semver') 还可以实现更复杂的功能。

(2)规则

(3)写法

1、^: 不允许 [X, Y, Z] 中最左非零数字的更改。

应用:开发者可以通过 ^ 来锁定一个模块的大版本,这样在每次重新安装依赖或打包部署的时候,都能够享受到这个包所有的新增 features 和 bug 修复。

注:安装本地包,在 package.json 里生成的版本号,默认会加 ^ 。

^15.6.1:>=15.6.1 && <16.0.0
# 因 semver 版本号格式规定必须从 1.0.0 开始,所以下面的情况只用于理论比较,实际情况并不会发生。
^0.1.2:>=0.1.2 && <0.2.0
^0.0.2:>=0.0.2 && <0.0.3

2、~: 匹配大于等于 [X, Y, Z] 的更新的Z的版本号

应用:开发者可以通过 ~ 来锁定一个模块的小版本,这样在每次重新安装依赖或打包部署的时候,都能够享受到这个包所有的 bug 修复。

~1.2.3:>=1.2.3 && <1.3.0
# 因 semver 版本号格式规定必须从 1.0.0 开始,所以下面的情况只用于理论比较,实际情况并不会发生
~0.2.3:>=0.2.3 && <0.3.0
~0.0.3:>=0.0.3 && <0.1.0

3、x / * / 空:表示任意

1.2.x / 1.2.* / 1.2: >=1.2.0 && <1.3.0

4、- : 指定范围

1.3.0-1.4.2: >=1.3.0 && <=1.4.2

注意:这里是左闭右闭,而上面都是左闭右开。

(4)使用

npm install somepkg@1.3.4

2、分发标签

(1)介绍

分发标签(dist-tags)补充了上面介绍的语义版本控制。

好处:

  • 标注某个有特殊意义的版本

  • 比语义版本控制更易于阅读

(2)写法

例如:

  • X.Y.Z-Alpha: 内测版
  • X.Y.Z-Beta: 公测版
  • X.Y.Z-Stable: 稳定版
  • X.Y.Z-Latest: 最新版
(3)其他

添加标签 & 使用标签发布 的 章节待写

(4)使用

npm install somepkg@latest

其实默认就是:npm install <pkg> = npm install <pkg>@latest

七、安装项目依赖 与 lock 机制


当你在 github 上拉下一个新项目,或者持续集成自己的项目时,总会涉及到根据 package.json (或 package.json + package-lock.json )文件来安装依赖(即生成 node_modules )的情况。

那么 npm 提供了两种方法:

  • 1、不锁版本:项目包含 package.json ,通过 npm install 安装依赖

    因为 package.json 的版本号常用 ^ ~ 等,而 npm install 又以 package.json 的版本为主,所以无法保证两次安装的具体版本是完全相同的。

  • 2、锁版本:项目包含 package.json 和 package-lock.json ,通过 npm ci 安装依赖

    因为 package-lock.json 里是具体的版本,npm ci 以这里为主。即达到了锁版本的效果。

    注意1:既然使用了 npm ci ,那就别忘了把 package-lock.json 加入 git 仓库。

    注意2:如果 package.json 和 package-lock.json 版本号冲突,则会报错。

    注意3:npm ci 在安装前会自动清除现存的 node_modules,所以 npm ci 天然规避了增量安装可能带来的不一致性等问题

而关于这两者方法哪一种更好,网上其实争论不少,想了解的请看:

为什么我不使用 shrinkwrap(lock)

透过 js-beautify@1.7.0 的 Bug 来看,npm 默认的 lock 机制是否重要?


支持不锁版本:

  • 让项目保持在良性的环境中,正向传递 new feature 和 bug fix,前提是一定要选择靠谱的开源模块

  • 你可以限制你安装模块的版本号,但是你无法限制你安装模块依赖的模块的版本号

支持锁版本:

  • 保证整个团队都使用版本完全一致的依赖,开发环境统一。

  • 保证每次部署都使用版本完全一致的依赖,确保安全。(从 npm ci 这个名字就能看出来这个命令是为持续集成准备的)

我的观点是,还是要看具体场景。例如作为开源包发布,建议不锁版本,而对生产环境的项目,安全第一,还是锁版本比较放心(如果要 update 某个/些包 ,一定要做好回归测试再上线)。

八、竞品分析 —— yarn


yarn 是 Facebook 出的。

早期 yarn 有很多新的好用的功能和更快的安装速度,但是 npm 后来也随着新版本的升级陆续把借鉴了过来。

现在 npm 6 不管是功能还是性能都已经很接近 yarn 了,所以我还是习惯用 npm。

下面介绍 yarn 当初吸引眼球的 feature。

1、嵌套

之前 npm 的 node_modules 问题在于:

  • 目录嵌套层级过深
  • 模块实例无法共享
  • 安装速度很慢

简单来说,就是模块都是独立的,比如说位于 express 下面的 path-to-regexp 和位于connect 下面的 path-to-regexp 模块,虽然版本都是一致的,但还是被视为不同的包,被重复安装两次,并放置在不同的位置。

后来,有人为了解决这个问题,引入了软链接的方案,市面上有很多第三方的库。

再后来, npm3 并没有采用软链接的方案,而是使用了扁平化方案,即直接将所有模块都安装到 node_modules 下。第一次出现的包会提升到顶层,后面重复出现的包(版本不一致的)才会被放入再深一层的依赖包的 node_modules 中。

而这些,yarn 早就实现了。

2、lock

yarn 默认提供了 lock 功能(即上面提到的锁版本)。

而 npm 5 才引入 package-lock.json ,等价于 yarn 中的 yarn.lock

其实 npm 早期就提供了 lock 功能:shrinkwrap。不过他需要用户执行 npm shrinkwrap 创建 npm-shrinkwrap.json 文件来 手动锁定版本

3、离线安装

npm6 的出现加入了缓存,进一步提升了安装速度。

  • npm install --offline
    • 完全使用线下缓存
  • npm install --online
    • 完全使用线上数据
  • npm install --prefer-offline
    • 优先使用线下缓存(建议使用这个来提高 npm 的安装速度)
  • npm install --prefer-online 【默认】
    • 优先使用线上数据

4、交互式更新

yarn 有交互式更新功能。

而 npm 也可以安装第三方工具npm-check ,它提供了命令行下的图形界面,可以手动选择升级哪些模块。

5、npx

npm5 出来 npx 代替了yarn run 这个命令。


原理:

1、分别检查当前目录node_modules/.bin和环境变量$PATH

2、如果命令存在,便运行

3、如果命令不存在,便安装到临时目录,运行完后删除


用处:

1、方便调用本地包

比如,之前项目安装了 sequelize 包,每次想要执行他的迁移命令都要输入长长的一串:

# old
./node_modules/sequelize-cli/lib/sequelize db:migrate
# new
npx sequelize db:migrate

2、只想临时使用下某个包,而不想安装它(全局和本地都不想安装)

建议:最好对日常使用的工具时才使用全局安装

例如,之前项目一直使用 webpack 打包,但现在想临时试下换用 rollup 打包的效果。

九、其他命令


1、npm audit

有些项目处于维护阶段,不打算加新特性了,甚至可能不太严重的 bug 都不打算修复了,但是像安全漏洞这样的严重问题还是要管的。这时可以使用 npm audit ,列出项目依赖中有安全漏洞的版本。

但是因为 npm install 引入新依赖时会自动运行 npm audit,再加上会定期运行 npm outdated ,所以手动运行 npm audit 的机会不太多。

2、npm repo 可以打开项目的源代码仓库(大部分情况下是 GitHub)

3、npm home 可以打开项目的主页(官网)

4、npm xmas 命令行里打印圣诞快乐树(纯无聊)

十、待写


scoped packages(作用域包)

two-factor auth

security tokens

私有源 (如 cnpm,例如上面介绍的淘宝镜像就算一个)

十一、故事


1、2016年,一个开发者对 NPM 公司不满,unpublish了自己之前发布的所有模块。其中包括被广泛使用的 left-pad,导致 Babel、ReactNative、Ember 等大量工具构建失败。

2、GitHub 发布了全新的软件包管理服务,叫 GitHub Package Registry,完全免费。且还兼容 npm。