目录
什么是 NPM
NPM (Node Package Manager) 是 Node.js 中的包管理器和分发工具。对于 Node 而言,NPM 帮助其完成了第三方模块的发布、安装、删除和依赖等。借助 NPM,Node 与第三方模块之间形成了很好的一个生态系统。这时候你需要什么包,都可以在 npm 中寻找。
这里有一点我们需要注意,我们必须要有一个 package.json 文件或 node_modules 目录安装模块到本地。
背景
共享代码
npm 出现之前,前端是怎么共享代码的呢?前端依赖项是保存到存储库中并手动下载的。比如你想使用 jQuery ,那么你点击 jQuery 网站上提供的链接就可以下载 jQuery ,放到自己的网站项目上使用 。
当一个项目依赖的代码越来越多,程序员发现这是一件很麻烦的事情:
1.去 jQuery 官网下载 jQuery
2.去 BootStrap 官网下载 BootStrap
3.去 Underscore 官网下载 Underscore
有些程序员就受不鸟了,一个拥有三大美德的程序员 Isaac Z. Schlueter (以下简称 Isaaz)给出了一个解决方案:用一个工具把这些代码集中到一起来管理吧!这个工具就是他用 JavaScript (运行在 Node.js 上) 写的 npm,全称是 Node Package Manager。
npm 和 Node.js 的关系
npm 和 Node.js 原本是没啥关系的。npm 开发出来后,它的作者 Isaaz 曾经联系过 jQuery、Bootstrap 的作者,希望他们提交自己的软件包给 npm 进行管理,但是 jQuery、Bootstrap 的作者不理睬。
于是 Isaaz 联系 Node.js 的作者,当时Node.js并不火,而且缺一个包管理器,二者一拍即合,并且 Node.js 愿意将 npm 集成进来,npm 成为 Node.js 的一个组件。
从此,Node.js 和 npm 相互扶持,让 Node.js 火遍全球,也让 npm 的用户不断增多,目前 npm 管理了 60 万个软件包,平均每天有上亿次下载,曾经对 npm 爱理不理的 jQuery、Bootstrap 也加入到了 npm。npm 在 Node.js 中提供,完全是市场因素。
NPM 的命令
NPM 模块中的 scope
scope 的作用:
- scope 相当于 npm 包的命名空间,如果以 @ 开头,那它就是一个 scope package。
- 这样分类之后就会使结构更加清晰,比如 @vue 下面的包都是 vue 用的,相当于给包做了一个分类。
- 一个 scope 中可以包含很多个模块; 例如:@babel 下有很多模块,方便管理。
命名规则:
- scope 在模块 name 中使用时,以 @ 开头,后边跟一个 /
- package.json 中,name 的写法如下:“name”: “@somescope/somepackagename”
查看 npm 版本
npm -v
版本号
npm 的版本号分三个部分:主版本号.次版本号.补丁版本号
主版本号:当API发生改变,并与之前的版本不兼容的时候
次版本号:当增加了功能,但是向后兼容的时候
补丁版本号:当做了向后兼容的缺陷修复的时候
该约定在所有编程语言中均被采用,每个 npm 软件包都必须遵守该约定,这一点非常重要,因为整个系统都依赖于此。
~ 、^ 和 *
npm 设置了一些规则,可用于在 package.json 文件中选择要将软件包更新到的版本(当运行 npm update 时)。规则如下:
^:兼容模块新发布的次版本、补丁版本,只会执行不更改最左边非零数字的更新。 如果写入的是 ^0.13.0,则当运行 npm update 时,可以更新到 0.13.1、0.13.2 等,但不能更新到 0.14.0 或更高版本。 如果写入的是 ^1.13.0,则当运行 npm update 时,可以更新到 1.13.1、1.14.0 等,但不能更新到 2.0.0 或更高版本。
~:兼容模块新发布的补丁版本,比如 ~1.2.3 会匹配到所有 1.2.x 补丁版本,但是不包括1.3.0
*:安装最新版本的依赖包。可能会造成版本不兼容,慎用。
什么前缀也没有,比如1.2.3,指定特定的版本,不做任何更新。
初始化项目
npm init
npm init 用来初始化生成一个新的 package.json 文件。这个文件主要是用来记录这个项目的详细信息的,它会将我们在项目开发中所要用到的包,以及项目的详细信息等记录在这个项目中。
使用 npm init 初始化项目还有一个好处就是在进行项目传递的时候不需要将项目依赖包一起发送给对方,对方在接受到你的项目之后再执行 npm install 就可以将项目依赖全部下载到项目里。
它会向用户提问一系列问题,如果觉得不用修改默认配置,一路回车就可以了。
package name: 你的项目名字叫啥
version: 版本号
description: 对项目的描述
entry point: 项目的入口文件(一般你要用那个js文件作为node服务,就填写那个文件)
test command: 项目启动的时候要用什么命令来执行脚本文件(默认为 node app.js)
git repository: 如果你要将项目上传到git中的话,那么就需要填写git的仓库地址(
keywirds:项目关键字
author: 作者的名字
license: 发行项目需要的证书
尾缀带 -f(代表force)、-y(代表yes),则跳过提问阶段,直接生成一个新的 package.json 文件,不带尾缀的话,默认有提问阶段。
安装模块
基本用法
# 读取package.json里面的配置单安装
npm install //可简写成 npm i
# 默认安装指定模块的最新(@latest)版本
npm install [<@scope>/]<name> //npm install gulp
# 安装指定模块的指定版本
npm install [<@scope>/]<name>@<version> //npm install gulp@3.9.1
# 安装指定指定版本范围内的模块
npm install [<@scope>/]<name>@<version range> //npm install vue@">=1.0.28 < 2.0.0"
# 安装指定模块的指定标签 默认值为(@latest)
npm install [<@scope>/]<name>@<tag> //npm install sax@0.1.1
# 通过Github代码库地址安装
npm install <tarball url> //npm install git://github.com/package/path.git
# 通过压缩文件安装
npm install <tarball file> //npm install ./package.tgz
# 通过文件夹安装
npm install <folder> //npm install ./sub-package
常用参数,默认情况下,npm 安装会将任何指定的包保存到依赖项中。此外,您还可以使用一些附加标志来控制它们的保存位置和保存方式:
-S, --save dependencies 依赖项安装 (npm v5-)
-P, --save-prod dependencies 依赖项安装,不指定-D或-O时,默认使用此项 (npm v5+)
-D, --save-dev devDependencies 开发依赖项安装
-O, --save-optional optionalDependencies 可选依赖项安装--no-save 防止保存到依赖项
-g, --global 全局安装,不会将模块依赖记录在 dependencies 或 devDependencies 对象中
-B, --save-bundle bundleDependencies 依赖项安装
-E, --save-exact 明确版本号安装,依赖项上版本号不会添加限定符号 ^ 。
-w, --workspace install 命令也是支持多工作区安装的
-ws, --workspaces 设置为false时,禁用workspaces-f, --force 模块不管是否安装过,npm 都要强制重新安装
--dry-run 报告安装状况而不是真的安装
npm install 与 npm i 的区别
- 用 npm i 安装的模块无法用 npm uninstall 删除,必须用 npm uninstall i 才卸载掉
- npm i 会帮助检测与当前 node 版本最匹配的 npm 包版本号,并匹配出来相互依赖的npm 包应该提升的版本号
- 部分 npm 包在当前 node 版本下无法使用,必须使用建议版本
- 安装报错时intall肯定会出现 npm-debug.log 文件,npm i 不一定
因此,建议使用 npm install 安装,而非 npm i 安装。
本地安装与全局安装
npm 的包安装分为本地安装 (local)、全局安装 (global) 两种,从敲的命令行来看,差别只是有没有 -g 或 --global 而已。
npm install # 本地安装
npm install -g # 全局安装
全局安装:
1. 模块将被下载安装到【全局目录】中。
2. 可以直接在命令行里使用,这也是全局安装的目的。一般都是一些工具:不用于项目,而是作用于计算机的。
3. 不能直接通过 require() 的方式去引用模块,需要手动解决包路径的配置问题。
4. 公用的,对于包的更新不好管理,不能支持全局多版本,更新模块版本后,所有使用该模块的项目的模块版本都会随之更改。不利于每个项目分别维护自己的依赖。
5. 不会将模块依赖记录在 dependencies 或 devDependencies 对象中。
本地安装:
1. 将安装包放在 ./node_modules 下(运行npm时所在的目录)
2. 可以通过 require() 来引入本地安装的包,目的就是在项目中可以使用node_modules文件夹下 js 文件的代码。
3. 每个项目拥有独立的包,不受全局包的影响,方便项目的移动、复制、打包等,保证不同版本包之间的相互依赖。为各个项目量身定做。
依赖项的区别
npm 目前支持以下几类依赖包管理:
- dependencies
- devDependencies
- peerDependencies
- optionalDependencies
- bundledDependencies / bundleDependencies
dependencies 应用依赖,或者叫做业务依赖,表示我们要在生产环境下使用该依赖。比如 axios、vuex 线上代码运行需要。
devDependencies 开发环境依赖,则表示我们仅在开发环境使用该依赖。比如构建工具 glup、webpack,预处理器 sass 这些只在开发环境使用。
peerDependencies 同等依赖,或者叫同伴依赖,用于指定当前包(也就是你写的包)兼容的宿主版本。如何理解呢? 试想一下,我们编写一个gulp的插件,而gulp却有多个主版本,我们只想兼容最新的版本,此时就可以用同等依赖(peerDependencies)来指定。当别人使用我们的插件时,peerDependencies 就会告诉明确告诉使用方,你需要安装该插件哪个宿主版本。
optionalDependencies 可选依赖,如果有一些依赖包即使安装失败,项目仍然能够运行或者希望npm继续运行,就可以使用 optionalDependencies。另外 optionalDependencies 会覆盖dependencies 中的同名依赖包,所以不要在两个地方都写。
bundledDependencies 打包依赖,这个依赖项也可以记为 bundleDependencies,与其他几种依赖项不同,他不是一个键值对的对象,而是一个数组,数组里是包名的字符串,bundledDependencies 是一个包含依赖包名的数组对象。在发布时会将这个对象中的包打包到最终的发布包里。
卸载模块
npm uninstall [<@scope>/]<pkg>[@<version>] # 卸载模块,但不卸载模块留在package.json中的对应信息
npm uninstall [<@scope>/]<pkg>[@<version>] -g # 卸载全局模块
npm uninstall [<@scope>/]<pkg>[@<version>] --save # 卸载模块,同时卸载留在package.json中dependencies下的信息
npm uninstall [<@scope>/]<pkg>[@<version>] --save-dev # 卸载模块,同时卸载留在package.json中devDependencies下的信息
更新模块
#升级当前项目或全局的指定模块
npm update [<pkg>...] [-g]
引用模块
# 引用依赖 有些包是全局安装了,在项目里面只需要引用即可。
npm link [<@scope>/]<pkg>[@<version>]
# 解除引用 npm unlink gulp
npm unlink [<@scope>/]<pkg>[@<version>]
淘宝镜像
# 设置全局的npm淘宝镜像
npm config set registry https://registry.npm.taobao.org
# 也可以切换回默认全局镜像
npm config set registry https://registry.npmjs.org
# 查看当前使用的镜像地址
npm config get registry
查看命令
npm root # 查看项目中模块所在的目录
npm root -g # 查看全局安装的模块所在目录
npm list 或者 npm ls # 查看本地已安装模块的清单列表
npm view jquery dependencies # 查看某个包对于各种包的依赖关系
npm view jquery version # 查看jquery最新的版本号
npm view jquery versions # 查看所有jquery历史版本号(很实用)
npm view jquery # 查看最新的jquery版本的信息
npm info jquery # 查看jquery的详细信息,等同于上面的npm view jquery
npm list jquery 或 npm ls jquery # 查看本地已安装的jquery的详细信息
npm view jquery repository.url # 查看jquery包的来源地址
其他命令
npm cache clean # 清除npm的缓存
npm prune # 清除项目中没有被使用的包
npm outdated # 检查模块是否已经过时
npm repo jquery # 会打开默认浏览器跳转到github中jquery的页面
npm docs jquery # 会打开默认浏览器跳转到github中jquery的README.MD文件信息
npm home jquery # 会打开默认浏览器跳转到github中jquery的主页
运行脚本
将从包的对象运行任意命令。如果没有提供,它将列出可用的脚本。
npm run-script <command> [-- <args>]
aliases: run, rum, urn
run[-script] 由 test、start、restart 和 stop 命令使用,命令需要在 script 对象中配置运行的脚本:
# 启动模块
npm run start
# 停止模块
npm run stop
# 重新启动模块
npm run restart
# 测试模块
npm run test
但也可以直接调用:
# 启动模块
npm start
# 停止模块
npm stop
# 重新启动模块
npm restart
# 测试模块
npm test
可以在项目 package.json 里面自定义脚本命令,但是不能直接调用:npm run dev 来执行这段脚本。
{
"script":{
"dev": "webpack -w"
}
}
默认值
一般来说,npm 脚本由用户提供。但是,npm 对两个脚本提供了默认值。也就是说,这两个脚本不用定义,就可以直接使用。
"start": "node server.js",
"install": "node-gyp rebuild"
npm run start 的默认值是 node server.js,前提是项目根目录下有 server.js 这个脚本。
npm run install 的默认值是 node-gyp rebuild,前提是项目根目录下有 binding.gyp 文件。
执行原理
使用 npm run script 执行脚本的时候都会创建一个 shell,然后在 shell 中执行指定的脚本。
这个 shell 会将当前项目的可执行依赖目录(即node_modules/.bin)添加到环境变量 path 中,当执行之后之后再恢复原样。就是说脚本命令中的依赖名会直接找到 node_modules/.bin 下面的对应脚本,而不需要加上路径。
比如,当前项目的依赖里面有 Mocha,只要直接写 mocha test 就可以了:
"test": "mocha test"
执行顺序
一个 npm 脚本可以执行多个任务,这些任务之间可以指定不同的执行顺序。
& 符号,并行执行顺序 ,即同时的平行执行。
"dev":"node test.js & webpack"
&& 符号,继发执行,即只有前一个任务成功,才执行下一个任务。
"dev":"node test.js && webpack"
钩子
npm 脚本自带两个顺序钩子,pre 和 post :
"predev": "node test_one.js",
"dev": "node test_two.js",
"postdev": "node test_three.js"
当执行 npm run dev 的时候默认就会执行:
npm run predev && npm run dev && npm run postdev
获取当前正在运行的脚本名称
npm 提供一个 npm_lifecycle_event 变量,返回当前正在运行的脚本名称,可以配合顺序钩子使用:
const target = process.env.npm_lifecycle_event;
if(target === 'predev'){
console.log('the process is predev')
}
if(target === 'dev'){
console.log('the process is dev')
}
if(target === 'postdev'){
console.log('this process is postdev')
}
变量
npm 脚本有一个非常强大的功能,就是可以使用 npm 的内部变量。
首先,通过 npm_package_ 前缀,npm 脚本可以拿到 package.json 里面的字段。比如,下面是一个package.json
{
"name": "foo",
"version": "1.2.5",
"scripts": {
"view": "node view.js"
}
}
那么,变量 npm_package_name 返回 foo,变量 npm_package_version 返回 1.2.5。
// view.js
console.log(process.env.npm_package_name); // foo
console.log(process.env.npm_package_version); // 1.2.5
上面代码中,我们通过环境变量 process.env 对象,拿到 package.json 的字段值。如果是 Bash 脚本,可以用 $npm_package_name 和 $npm_package_version 取到这两个值。
npm_package_ 前缀也支持嵌套的 package.json 字段。
"repository": {
"type": "git",
"url": "xxx"
},
scripts: {
"view": "echo $npm_package_repository_type"
}
上面代码中,repository 字段的 type 属性,可以通过 npm_package_repository_type 取到。
环境变量
如果 env 环境变量中存在以 npm_config_ 为前缀的环境变量,则会被识别为 npm 的配置属性。比如,环境变量中的 npm_config_foo=bar 将会设置配置参数 foo 的值为 "bar"。
脚本还可以通过 npm_config_ 前缀,拿到 npm 的配置变量,即 npm config get xxx 命令返回的值。比如,当前模块的发行标签,可以通过 npm_config_tag 取到。
// view 脚本
"view": "echo $npm_config_tag",
注意,package.json 里面的 config 对象,可以被环境变量覆盖。
{
"name" : "foo",
"config" : { "port" : "8080" },
"scripts" : { "start" : "node server.js" }
}
上面代码中,npm_package_config_port 变量返回的是 8080。这个值可以用下面的方法覆盖。
npm config set foo:port 80
最后,env 命令可以列出所有环境变量
"env": "env"
package-lock.json
package.json 的不足之处
npm install 执行后,会生成一个 node_modules 树,在理想情况下, 希望对于同一个 package.json 总是生成完全相同 node_modules 树。在某些情况下,确实如此。但在多数情况下,npm 无法做到这一点。有以下两个原因:
1)某些依赖项自上次安装以来,可能已发布了新版本 。比如:A 包在团队中第一个人安装的时候是 1.0.5 版本,package.json 中的配置项为 A: '^1.0.5';团队中第二个人把代码拉下来的时候,A 包的版本已经升级成了 1.0.8,根据 package.json 中的 semver-range version 规范,此时第二个人 npm install 后 A 的版本为 1.0.8;可能会造成因为依赖版本不同而导致的 bug;
2)针对 1)中的问题,可能有的小伙伴会是把 A 的版本号固定为 A: '1.0.5' 不就可以了吗?但是这样的做法其实并没有解决问题, 比如 A 的某个依赖在第一个人下载的时候是 2.1.3 版本,但是第二个人下载的时候已经升级到了 2.2.5 版本,此时生成的 node_modules 树依旧不完全相同 ,固定版本只是固定来自身的版本,依赖的版本无法固定。
为了解决 npm install
的不确定性问题,在 npm 5.x
版本新增了 package-lock.json
文件,当包中有 package-lock.json 文件时,npm install 执行时,如果 package.json 和 package-lock.json 中的版本兼容,会根据 package-lock.json 中的版本下载;如果不兼容,将会根据 package.json 的版本,更新 package-lock.json 中的版本,已保证 package-lock.json 中的版本兼容 package.json。
package-lock.json
的作用是锁定依赖结构,即只要你目录下有 package-lock.json
文件,那么你每次执行 npm install
后生成的 node_modules
目录结构一定是完全相同的。
package-lock.json 文件的作用
- 在团队开发中,确保每个团队成员安装的依赖版本是一致的,确定一棵唯一的 node_modules 树;
- node_modules 目录本身是不会被提交到代码库的,但是 package-lock.json 可以提交到代码库,如果开发人员想要回溯到某一天的目录状态,只需要把 package.json 和 package-lock.json 这两个文件回退到那一天即可。
- 由于 package-lock.json 和 node_modules 中的依赖嵌套完全一致,可以更加清楚的了解树的结构及其变化。
- 在安装时,npm 会比较 node_modules 已有的包,和 package-lock.json 进行比较,如果重复的话,就跳过安装 ,从而优化了安装的过程。