npm 是前端开发广泛使用的包管理工具,之前使用 Weex 时看了阮一峰前辈的文章了解了一些,这次结合官方文章总结一下,加深下理解吧!
读完本文你将了解:
什么是 npm
安装 npm
更新 npm
packagejson 文件
packagejson 如何创建
packagejson 的内容
指定依赖的包
Semantic versioning语义化版本规则
安装 package
全局安装的权限问题
更新全局的 package
卸载全局 package
安装参数 --save 和 --save -dev
使用下载好的包
更新本地 package
卸载本地 package
本地安装 package
全局安装 package
其他命令
npm run
npm install from github
npm info
npm adduser
npm homerepo
npm prune
npm publish
国内镜像
总结
Thanks
官方文档中对它的介绍是:
npm makes it easy for JavaScript developers to share and reuse code, and it makes it easy to update the code that you’re sharing.
即: npm 是一个包管理器,它让 JavaScript 开发者分享、复用代码更方便(有点 maven 的感觉哈)。
在程序开发中我们常常需要依赖别人提供的框架,写 JS 也不例外。这些可以重复的框架代码被称作包(package)或者模块(module),一个包可以是一个文件夹里放着几个文件,同时有一个叫做 package.json
的文件。
一个网站里通常有几十甚至上百个 package,分散在各处,通常会将这些包按照各自的功能进行划分(类似我们安卓开发中的划分子模块),但是如果重复造一些轮子,不如上传到一个公共平台,让更多的人一起使用、参与这个特定功能的模块。
而 npm 的作用就是让我们发布、下载一些 JS 轮子更加方便。
我们可以去官方网站 https://www.npmjs.com/ 浏览、搜索想要的轮子,也可以直接在命令行中 search 一下意中轮。
使用 npm 后我们可以非常方便地查看依赖的轮子是否有更新、是否需要下载新版本。
现在我们知道 npm 是干什么的了。当人们说起 “npm” 时,可能在说三个东西:
一个网站,就是前面提到用于搜索 JS 模块的网站:https://www.npmjs.com/
一个仓库,保存着人们分享的 JS 模块的大数据库
命令行里的客户端,开发者使用它来管理、安装、发布模块
只要开发者发布某个模块到仓库中,其他人就可以从 npm 网站或者命令行中下载、使用它了!
npm 是依附于 node.js 的,我们可以去它的官网 https://nodejs.org/en/download/ 下载安装 node.js。
下载好 node.js, npm 也就有了,使用 npm -v
查看安装的 npm 版本:
zhangshixin$ node -v v6.10.012
npm 更新地可比 node 勤快多了,因此你下载的 node 附带的 npm 版本可能不是最新的,你可以使用如下命令下载最新 npm:
npm install npm@latest -g1
其中 install 不用介绍了,就是安装,后面的 npm@latest
就是
的格式,我们在下载其他模块时也是这个格式。-g
代表全局安装。
package.json
文件非常重要,因此需要单独一小节介绍。
管理本地安装 npm 包的最好方式就是创建 package.json
文件。
一个 package.json
文件可以有以下几点作用:
作为一个描述文件,描述了你的项目依赖哪些包
允许我们使用 “语义化版本规则”(后面介绍)指明你项目依赖包的版本
让你的构建更好地与其他开发者分享,便于重复使用
使用 npm init
即可在当前目录创建一个 package.json
文件:
如图所示,输入 npm init
后会弹出一堆问题,我们可以输入对应内容,也可以使用默认值。在回答一堆问题后输入 yes
就会生成图中所示内容的 package.json
文件。
如果嫌回答这一大堆问题麻烦,可以直接输入 npm init --yes
跳过回答问题步骤,直接生成默认值的 package.json
文件:
package.json
文件至少要有两部分内容:
“name”
全部小写,没有空格,可以使用下划线或者横线
“version”
x.x.x 的格式
符合“语义化版本规则”
比如:
{ "name": "shixinzhang-demo-package", "version": "1.0.0"}1234
其他内容:
description
:描述信息,有助于搜索
main
: 入口文件,一般都是 index.js
scripts
:支持的脚本,默认是一个空的 test
keywords
:关键字,有助于在人们使用 npm search
搜索时发现你的项目
author
:作者信息
license
:默认是 MIT
bugs
:当前项目的一些错误信息,如果有的话
我们可以为 init
命令设置一些默认值,比如:
> npm set init.author.email "[email protected]"> npm set init.author.name "shixinzhang"> npm set init.license "MIT"123
注意:
如果 package.json 中没有description
信息,npm 使用项目中的 README.md 的第一行作为描述信息。这个描述信息有助于别人搜索你的项目,因此建议好好写description
信息。
我们需要在 package.json
文件中指定项目依赖的包,这样别人在拿到这个项目时才可以使用 npm install
下载。
包有两种依赖方式:
dependencies
:在生产环境中需要用到的依赖
devDependencies
:在开发、测试环境中用到的依赖
举个例子:
{ "name": "my-weex-demo", "version": "1.0.0", "description": "a weex project", "main": "index.js", "scripts": { "build": "weex-builder src dist", "build_plugin": "webpack --config ./tools/webpack.config.plugin.js --color", "dev": "weex-builder src dist -w", "serve": "serve -p 8080" }, "keywords": [ "weex" ], "author": "[email protected]", "license": "MIT", "devDependencies": { "babel-core": "^6.14.0", "babel-loader": "^6.2.5", "babel-preset-es2015": "^6.18.0", "vue-loader": "^10.0.2", "eslint": "^3.5.0", "serve": "^1.4.0", "webpack": "^1.13.2", "weex-loader": "^0.3.3", "weex-builder": "^0.2.6" }, "dependencies": { "weex-html5": "^0.3.2", "weex-components": "*" }}1234567891011121314151617181920212223242526272829303132
https://docs.npmjs.com/getting-started/semantic-versioning
dependencies
的内容,以 "weex-html5": "^0.3.2"
为例,我们知道 key 是依赖的包名称,value 是这个包的版本。那版本前面的 ^ 或者版本直接是一个 * 是什么意思呢?
这就是 npm 的 “Semantic versioning”,简称”Semver”,中文含义即“语义化版本规则”。
在安卓开发中我们有过这样的经验:有时候依赖的包升级后大改版,之前提供的接口不见了,这对使用者的项目可能造成极大的影响。
因此我们在声明对某个包的依赖时需要指明是否允许 update 到新版本,什么情况下允许更新。
这就需要先了解 npm 包提供者应该注意的版本号规范。
如果一个项目打算与别人分享,应该从 1.0.0 版本开始。以后要升级版本应该遵循以下标准:
补丁版本:解决了 Bug 或者一些较小的更改,增加最后一位数字,比如 1.0.1
小版本:增加了新特性,同时不会影响之前的版本,增加中间一位数字,比如 1.1.0
大版本:大改版,无法兼容之前的,增加第一位数字,比如 2.0.0
了解了提供者的版本规范后, npm 包使用者就可以针对自己的需要填写依赖包的版本规则。
作为使用者,我们可以在 package.json
文件中写明我们可以接受这个包的更新程度(假设当前依赖的是 1.0.4 版本):
如果只打算接受补丁版本的更新(也就是最后一位的改变),就可以这么写:
1.0
1.0.x
~1.0.4
如果接受小版本的更新(第二位的改变),就可以这么写:
1
1.x
^1.0.4
如果可以接受大版本的更新(自然接受小版本和补丁版本的改变),就可以这么写:
*
x
小结一下:总共三种版本变化类型,接受依赖包哪种类型的更新,就把版本号准确写到前一位。
使用 npm 安装 package 有两种方式:本地(当前项目路径)安装 或者 全局安装。
你选择哪种安装方式取决于你将如何使用这个包:
如果你只是想在当前项目里用 require()
加载使用,那你可以安装到本地
npm install
默认就是安装到本地的
如果你想要在命令行里直接使用,比如 grunt
CLI,就需要安装到全局了
如果在你的项目里有 package.json
文件,运行 npm install
后它会查找文件中列出的依赖包,然后下载符合语义化版本规则的版本。
npm install
默认会安装 package.json
中 dependencies
和 devDependencies
里的所有模块。
如果想只安装 dependencies
中的内容,可以使用 --production
字段:
npm install --production1
npm 使用下面的命令下载一个包:
npm install1
后面就是要安装包的名称。这个命令会在当前目录创建一个 node_modules
目录,然后下载我们指定的包到这个目录中。
举个例子:
zhangshixindeMacBook-Pro:publish-pkg zhangshixin$ npm install lodash zhangshixindeMacBook-Pro:publish-pkg zhangshixin$ ls index.js package-lock.json node_modules package.json zhangshixindeMacBook-Pro:publish-pkg zhangshixin$ ls node_modules/ lodash123456
下载后的项目文件夹:
安装指定版本:
npm install
默认安装最新版本,如果想要安装指定版本,可以在库名称后加 @版本号
:
$ npm install sax@latest$ npm install [email protected]$ npm install sax@">=0.1.0 <0.2.0"123
如果当前项目有 package.json
文件,下载包时会下载这个文件中指定的版本;
如果当前项目中没有 package.json
文件,就会下载指定包的最新版本。
有时下载会报错:
npm install error saveError ENOENT: no such file or directory, open '/Users/zhangshixin/package.json'
解决办法:
- 在目录下执行npm init
创建package.json
,输入初始化信息
- 然后再执行下载命令
--save
和 --save -dev
添加依赖时我们可以手动修改 package.json
文件,添加或者修改 dependencies
devDependencies
中的内容即可。
另一种更酷的方式是用命令行,在使用 npm install
时增加 --save
或者 --save -dev
后缀:
npm install
表示将这个包名及对应的版本添加到 package.json
的 dependencies
npm install
表示将这个包名及对应的版本添加到 package.json
的 devDependencies
下载后 node_modules
文件夹中有要使用的包,我们就可以使用其中的代码了。
比如在 Node.js 项目中,我们可以用 require(XXX)
引入它。
举个例子:
创建一个 index.js 文件,写入如下代码:
在使用 require('lodash')
后引入了 lodash 库,然后调用了它的 without()
方法,这个方法可以去除第一个数组参数中与第二个参数重复的数据。
保存这个文件后,使用 node index.js
运行这个文件,成功的话就可以得到运行结果;如果之前安装失败,可能就会遇到这个错误:
module.js:340 throw err; ^Error: Cannot find module 'lodash'1234
这时你需要在这个目录下重新运行 npm install lodash
安装。
有时候我们想知道依赖的包是否有新版本,可以使用 npm outdated
查看,如果发现有的包有新版本,就可以使用 npm update
更新它,或者直接 npm update
更新所有:
上图中,我们在输入 npm update
后发现本地的 lodash
模块还不是最新的,这是为什么呢?
原来,npm update 的工作过程是这样的:
先到远程仓库查询最新版本
然后对比本地版本,如果本地版本不存在,或者远程版本较新
查看 package.json
中对应的语义版本规则
如果当前新版本符合语义规则,就更新,否则不更新
一开始我本地的 package.json 的依赖是这样的:
"dependencies": { "lodash": "^3.1.0" }123
根据前面的介绍我们可以知道,这表示只接受小版本或者补丁版本的更新,因此在执行了一次 npm update
后它变成了这样:
"dependencies": { "lodash": "^3.10.1" }123
第二位升到了最高,但是无法更新第一位,因此无法更新到最新的 4.17.4。
所以我需要把它修改成:
"dependencies": { "lodash": "*" }123
这表示任何版本的更新都接受,然后再执行 npm update
,就发现更新成功了:
小结一下:
执行 npm outdated
后可以看到有三个版本号:
第一个是当前 node_modules 中该模块的版本,第二个是 package.json
文件中声明的版本,第三个是远程仓库最新的版本。
只有当前模块版本低于远程,package.json 中的版本语义规则满足情况,才能更新成功。
卸载一个本地 package 很简单,npm uninstall
即可:
官方文档说输入
npm uninstall --save lodash
才能在删除项目的同时移除package.json
中对它的依赖。但我没加--save
也达到了一样的效果,一脸懵逼。
卸载后再 ls node_modules/
查看目录下,发现没有东西,删除成功。
如果你想要直接在命令行中使用某个包,比如 jshint
,你可以全局安装它。
全局安装比本地安装多了个 -g
:
npm install -g1
以 jshint
为例,全局安装命令是:
npm install -g jshint1
安装后可以使用 npm ls -g --depth=0
查看安装在全局第一层的包。
在全局安装时可能会遇到 EACCES
权限问题,解决办法办法有如下 3 种:
1.sudo npm install -g jshint
,使用 sudo
简单粗暴,但是治标不治本
2.修改 npm 全局默认目录的权限
先获取 npm 全局目录:npm config get prefix
,一般都是 /usr/local
;
然后修改这个目录权限为当前用户:
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}1
3.使用其他包管理器帮你解决这个问题
实在懒得弄可以直接卸载 node,然后使用 Homebrew 重装 node:
brew install node1
Homebrew 会帮我们处理好权限的问题。
想知道哪些包需要更新,可以使用 npm outdated -g --depth=0
,然后使用 npm update -g
更新指定的包:
要更新所有全局包,可以使用 npm update -g
,可以发现对比本地的,只是多了个 -g
。
不过官方说在 2.6.1 以下的 npm ,直接使用 npm update -g
并不安全,因为它会递归地更新所有全局依赖。
这种情况下可以使用 npm-check
,贴一张它的截图:
Github 地址:https://github.com/dylang/npm-check
一句搞定:npm uninstall -g
部分摘自 阮一峰的 NPM 教程
npm 还可以直接运行 package.json
中 scripts
指定的脚本:
{ "name": "demo", "scripts": { "lint": "jshint **.js", "test": "mocha test/" }}1234567
npm run 是 npm run-script 的缩写。
命令行输入 npm run lint
或者 npm run-script lint
就会执行 jshint **.js
。
npm run
会创建一个Shell,执行指定的命令,并临时将node_modules/.bin加入PATH 变量,这意味着本地模块可以直接运行。
package.json
中的 scripts
执行的脚本是本地项目内 node_modules
-> .bin
内的脚本。
"scripts": { "build": "weex-builder src dist", "build_plugin": "webpack --config ./tools/webpack.config.plugin.js --color", "dev": "weex-builder src dist -w", "serve": "serve -p 8080" }123456
直接运行 npm run
会列出当前项目的 package.json
中 scripts
属性下的所有脚本命令。
npm install 也可以直接从 github 下载:
$ npm install git://github.com/package/path.git$ npm install git://github.com/package/path.git#0.1.012
npm info
可以查看指定包的信息:
npm adduser
用于在npmjs.com注册一个用户:
$ npm adduserUsername: YOUR_USER_NAMEPassword: YOUR_PASSWORDEmail: [email protected]
npm home
命令可以打开指定模块的主页; npm repo
命令则是打开指定模块的代码仓库。
prune 即“修剪”的意思。
npm prune
可以检查出当前项目的 node_modules
目录中,没有在 package.json
里提到的模块。
现在水平还不够,等写出可以复用的 JS 代码后,我们就可以将它发布到 npm 仓库上,类似 Github 的提交。
这部分主要摘自阮一峰的 NPM 教程
要想发布,首先需要使用 npm adduser
向 npmjs.com
申请用户名(当然去官网也可以)。
接着使用 npm login
在命令行中登录。
登录以后,就可以使用 npm publish
命令发布。
$ npm publish12
如果当前模块是一个beta版,比如1.3.1-beta.3,那么发布的时候需要使用tag参数,将其发布到指定标签,默认的发布标签是latest。
$ npm publish --tag beta12
如果发布私有模块,模块初始化的时候,需要加上scope参数。只有npm的付费用户才能发布私有模块。
$ npm init --scope=12
如果你的模块是用ES6写的,那么发布的时候,最好转成ES5。首先,需要安装Babel。
$ npm install --save-dev babel-cli@6 babel-preset-es2015@612
然后,在package.json
里面写入build
脚本。
"scripts": { "build": "babel source --presets babel-preset-es2015 --out-dir distribution", "prepublish": "npm run build"}12345
运行上面的脚本,会将source目录里面的ES6源码文件,转为distribution目录里面的ES5源码文件。
不翻的话有时候 npm 比较卡,可以使用国内的淘宝镜像 cnpm: https://npm.taobao.org。
cnpm
支持 npm
除了 publish
之外的所有命令。
经过这么一番总结,总算可以说 npm 入门了。
接触前端后发现这个圈子太复杂了,各种工具各种框架,眼花缭乱啊。一步一步来吧!