npm(Node Package Manager)入门指南

本文是一篇英文blog翻译,原文"A Beginener's Guide to npm - the Node Package Manager", 作者 Michael Wanyoike Peter Dierx
Node.js使得在服务器端使用JavaScript编写后台应用成为了可能。它是基于google的V8 JavaScript运行时环境构建,使用C++编写的,这也意味着它运行很快。最开始,它是为应用的服务器环境准备的,但是后来开发者开始用它创建工具来帮他们实现本地任务自动化。此后,基于Node的工具生态系统(如Grunt, GulpWebpack)开始出现并改变了前端开发的姿态。

本文更新与2017年8月6日,主要反映当前的npm版本状态,以及在version 5 中引入的一些改变。

为了使用Node.js中的工具(或者说是packages),我们首先需要能够以一种有效的方式安装并且管理它们。这就是npm-Node Package Manager发挥作用的地方。npm能安装你想使用的包并且提供了一组很友好的使用接口。
我将在本文中讲解一些使用npm的基础知识,并展示怎样在局部和全局模式下安装,删除,更新以及安装特定版本的包,同时我也会说明怎样使用package.json文件管理项目的依赖。
在开始使用npm之前,我们首先需要在系统中安装它。

安装Node.js

进入Node.js 下载页面并下载你需要的版本。Windows和Mac有安装器可以下载,Linux则有预编译号的二进制文件和源代码下载,也可以通过包管理器下载Node.js.
笔者使用Ubuntu的包管理器安装nodejs的指令如下:

$ sudo apt install nodejs

安装好node后,通过以下指令查看node的安装位置和安装版本:

$ which node
/usr/bin/node
$node --version
v6.10.3

为了验证Nodejs确实安装成功,让我们来运行Node的交互式解释器:

$ node
> console.log('Node is running');
Node is running
> .help
.break Sometimes you get stuck, this gets you out
.clear Alias for .break
.exit  Exit the repl
.help  Show repl options
.load  Load JS from a file into the REPL session
.save  Save all evaluated commands in this REPL session to a file
> .exit

以上代码可以看出Node.js已经成功安装,现在我们可以将注意力集中在npm上。npm被包含在了Nodejs的安装中,可以通过以下命令验证:

$ which npm
/usr/bin/npm
$ npm --verison
3.10.10

Node Packaged Modules

npm能在局部或全局模式下安装包。在局部模式下,npm将包安装在上一级工作目录的node_modules文件夹中;在全局模式下,npm将包安装在{prefix}/lib/node_modules文件夹下,这个文件夹是root用户所有的,{prefix}通常是/usr/usr/local。这意味着你必须有root权限才能在全局模式下安装包,当解析第三方依赖时可能会引发权限问题,这也是一个安全关注点。可以通过配置来改变全局模式下包的安装位置。

改变全局包安装的位置

通过npm config指令查看当前npm的配置:

$ npm config list
; cli configs
user-agent = "npm/3.10.10 node/v6.10.3 linux x64"

; userconfig /home/sitepoint/.npmrc
prefix = "/home/sitepoint/.node_modules_global"

; node bin location = /usr/bin/nodejs
; cwd = /home/sitepoint
; HOME = /home/sitepoint
; "npm config ls -l" to show all defaults.

以上指令给了我们相关的安装信息,现在最重要的是获取当前的全局包的安装位置:

$ npm config get prefix
/usr

这个前缀是我们需要修改的,我们将它改成home目录。首先在home创建一个新的文件夹:

$ cd ~ && mkdir .node_modules_global
$ npm confi set prefix=$HOME/.node_modules_global

通过这样简单地修改配置,我们已经修改了全局包的安装位置,同时在home目录下创建了一个.npmrc文件。

$ npm config get prefix
/home/sitepoint/.node_modules_global
$ cat .npmrc
prefix=/home/sitepoint/.node_modules_global

此时,在系统的某个地方,我们仍然有一个root用户所有的npm安装。但是因为我们修改了全局包的安装位置,我们可以利用这一点。我们需要重新安装npm,但是这一次将它安装在当前登录用户所有的目录下。重新安装会安装最新版本的npm。

$ npm install npm --global
└─┬ npm@5.0.2
  ├── abbrev@1.1.0
  ├── ansi-regex@2.1.1
....
├── wrappy@1.0.2
└── write-file-atomic@2.1.0

最后,我们需要将.node_modules_global/bin添加在$PATH环境变量,这样我们就能直接从命令行运行全局包。通过在.profile, .bash_profile.bashrc中添加以下语句

export PATH="$HOME/.node_modules_global/bin:$PATH"

重新启动terminal即可。
现在我们的.node_modules_global/bin目录下的npm会首先被找到并使用:

$ which npm
/home/sitepoint/.node_modules_global/bin/npm
$ npm --version
5.0.2

在全局模式下安装包

现在我们只在全局模式下安装了包-npm本身这个包。现在让我们安装 UglifyJS
(一个JavaScript最小化工具)。我们使用--global标记,为了简化,也可以使用-g

$ npm install uglify-js --global
/home/sitepoint/.node_modules_global/bin/uglifyjs -> /home/sitepoint/.node_modules_global/lib/node_modules/uglify-js/bin/uglifyjs
+ uglify-js@3.0.15
added 4 packages in 5.836s

从输出中可以看到,有额外的包被安装了-这些包是UglifyJS的依赖。

列出全局包

我们可以通过npm list指令列出已经安装的全局包

$ npm list --global
home/sitepoint/.node_modules_global/lib
├─┬ npm@5.0.2
│ ├── abbrev@1.1.0
│ ├── ansi-regex@2.1.1
│ ├── ansicolors@0.3.2
│ ├── ansistyles@0.1.3
....................
└─┬ uglify-js@3.0.15
  ├─┬ commander@2.9.0
  │ └── graceful-readlink@1.0.1
  └── source-map@0.5.6

可以看到输出非常多,通过--depth=0选项值看主要的包:

$ npm list -g --depth=0
/home/sitepoint/.node_modules_global/lib
├── npm@5.0.2
└── uglify-js@3.0.15

这样好多了,只显示我们通过命令安装的包和它们的版本号。
任何全局安装的包都能通过命令行调用。例如,下面的指令展示了我们怎样使用Uglify来将example.js压缩成example.min.js

$ uglifyjs example.js -o example.min.js

在局部模式下安装包

当在局部模式下安装包时,通常会用到一个package.json文件。下面让我们开始创建一个。

$ npm init
package name: (project)
version: (1.0.0)
description: Demo of package.json
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)

一路回车使用默认值,然后输入yes进行确认。这样就会在项目的根路径下生成一个package.json文件。系统生成的package.json格式如下:

{
  "name": "project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

小提示:如果想快速生成一个package.json文件,使用npm init --y指令就行。

除了mainscripts这两个字段,其他的都不需要再作解释。main字段是整个程序的入口点,scripts字段指明了在包的生命周期的不同时刻能执行的脚本命令。目前我们不需要管这些,但是如果你想了解的更多,参考 package.json documentation on npmusing npm as a build tool这两篇文章.
现在让我们尝试安装Underscore.

$ npm install underscore
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN project@1.0.0 No description
npm WARN project@1.0.0 No repository field.

+ underscore@1.8.3
added 1 package in 0.344s

注意到创建了一个lockfile。我们在后面再讨论这个

现在如果我们再看一下package.json文件,我们会发现dependencies字段下添加了新的条目:

{
  ...
  "dependencies": {
    "underscore": "^1.8.3"
  }
}

通过package.json文件管理依赖

正如你前面所看到的那样,UnderScore v1.8.3被安装到了我们的项目中。最前面的^符号表示npm在安装这个包时会安装它们找到的与主版本匹配(除非有package-lock.json)的最高的版本。在我们的例子中,这表示任何低于v2.0.0的包。这种版本依赖(major.minor.patch)的方法被称为语义版本(semantic versioning)。这里是更多详细信息: Semantic Versioning: Why You Should Be Using it.
同时也要注意到Underscore被作为dependencies字段的一个属性所保存,这是最新的npm版本的默认行为,这种行为被应用于使应用程序跑起来必须的包中。可以通过--save-dev标记将包保存为devDependencydevDependencies是开发阶段需要的包,例如运行测试或转译代码相关的包。
可以通过在package.json中添加private: true来组织私包意外发布,这样也能抑制npm install中生成的警告信息。
到目前为止,使用package.json最大的原因是它能很方便地指明项目的依赖。例如,当你克隆别人的代码时,你所需要做的就是在项目的跟路径下执行npm i指令,npm会解析并抓取那些使项目运行起来所有必须的包。我们会在后面详细讨论这点。
在结束本节之前,让我们快速检查一下Underscore使用在工作。在项目根目录下创建一个test.js文件,并添加一下语句:

const _ = require('underscore');
console.log(_.range(5));

通过node test.js运行这个文件,应该能在屏幕上看到[0, 1, 2, 3, 4]的输出。

卸载本地包

npm是一个包管理器,因此它必须具有删除包的功能。我们假设当前的Unserscore包引起了一些兼容性问题,需要把这个包删除然后安装一个老版本,如下:

$ npm uninstall underscore
removed 2 packages in 0.107s
$ npm list
project@1.0.0 /home/sitepoint/project
└── (empty)

安装指定版本的包

现在可以安装我们所需要的版本的Underscore包了,通过@符号添加版本号:
$ npm install underscore@1.8.2

  • underscore@1.8.2
    added 1 package in 1.574s

$ npm list
project@1.0.0 /home/sitepoint/project
└── underscore@1.8.2

更新包

让我们检查Underscore包是否有更新:

$ npm outdated
Package     Current  Wanted  Latest  Location
underscore    1.8.2   1.8.3   1.8.3  project

Current列展示的是本地安装的版本,Latest列展示的是最新的版本,Wanted列展示的是在不破坏已有代码前提下我们能更新到的最新的版本
还记得前面提到的package-lock.json文件吗?package-lock.json在npm v5中引入,是为了保证安装项目的不同机器上的依赖相同。这个文件在执行任何修改node_modulespackage.json的操作中自动生成。
可以直接试一下。先删除node_modules文件夹,然后执行npm i指令。最新版本的npm会安装Underscorev1.8.2(这个版本是在package-lock.json文件中指定的)。由于语义版本的原因,更早版本的npm会安装v1.8.3版本,也可以通过手动创建npm-shrinkwrap.json文件解决这个问题。
现在我们假设最新的Underscore已经修复了我们之前假设的问题,这时,我们就想把包进行更新。npm update package-name用于更新包:

$ npm update underscore
+ underscore@1.8.3
updated 1 package in 0.236s

$ npm list
project@1.0.0 /home/sitepoint/project
└── underscore@1.8.3

小提示: 要想上面的指令工作,Underscore必须在package.json中列出来。我们也可以执行npm update更新多个包。

搜索包

在本教程中,我们已经使用了几次mkdir指令。那么是否有node包也能执行相同的功能呢?让我们通过npm search来找以下:

$ npm search mkdir
NAME      | DESCRIPTION          | AUTHOR          | DATE       | VERSION
mkdir     | Directory crea…      | =joehewitt      | 2012-04-17 | 0.0.2
fs-extra  | fs-extra conta…      | =jprichardson…  | 2017-05-04 | 3.0.1
mkdirp    | Recursively mkdir,…  | =substack       | 2015-05-14 | 0.5.1
...

可以看到有一个mkdirp指令,先进行安装:

$ npm install mkdirp
+ mkdirp@0.5.1
added 2 packages in 3.357s

现在创建一个mkdir.js文件,并且复制-粘贴以下代码:

const mkdirp = require('mkdirp');
mkdirp('foo', function (err) {
  if (err) console.error(err)
  else console.log('Directory created!')
});

然后在terminal下运行它:

$ node mkdir.js
Directory created!

重新安装项目依赖

先再安装一个包:

$ npm install request
+ request@2.81.0
added 54 packages in 15.92s

检查package.json文件

"dependencies": {
  "mkdirp": "^0.5.1",
  "request": "^2.81.0",
  "underscore": "^1.8.2"
},

注意到依赖列表自动更新了。在以前的npm版本中,必须执行npm install request --save才会将依赖保存到package.json文件中。如果你执行安装包但是不将它保存进package.json文件中,使用--no-save即可。
假设你将项目源码克隆到另外一台机器上了,此时需要安装依赖。首先删除node_modules文件夹,然后执行npm install

$ rm -R node_modules
$ npm list
project@1.0.0 /home/sitepoint/project
├── UNMET DEPENDENCY mkdirp@^0.5.1
├── UNMET DEPENDENCY request@^2.81.0
└── UNMET DEPENDENCY underscore@^1.8.2

npm ERR! missing: mkdirp@^0.5.1, required by project@1.0.0
npm ERR! missing: request@^2.81.0, required by project@1.0.0
npm ERR! missing: underscore@^1.8.2, required by project@1.0.0

$ npm install
added 57 packages in 1.595s

再次看一下node_modules目录,发现它被重新建立了。这样你就能很简单地将代码分享给别人,而不用把项目依赖的源代码也打包分享。

管理缓存

当npm安装一个包,它就会保留一份副本。下次想再次安装的时候就不需要联网了。附件被魂村在home目录下的.npm文件夹下。

$ ls ~/.npm
anonymous-cli-metrics.json  _cacache  _locks  npm  registry.npmjs.org

随着旧包的加入,这个目录会变得很乱,因此时不时清理一下是很有必要的

$npm cache clean

如果系统上有多个node项目想要清理,可以通过以下指令清理所有的node_modules目录

find . -name "node_modules"  -type  d -exec rm -rf '{}' +

别名

你可能已经注意到了,有多种方式来执行npm命令。以下是一个常用的npm命令别名清单:

  • npm i <package> – 安装本地包
  • npm i -g <package> – 安装全局包
  • npm un <package> – 卸载本地包
  • npm up – 更新包
  • npm t – 运行测试
  • npm ls – 列出已安装的包
  • npm ll 或 npm la – 列出包时显示详细信息
    可以一次安装多个包
    $ npm i express momemt lodash mongoose body-parser webpack
    如果想要学习所有常用的npm命令,只需要执行npm help即可。也可以通过我们的文章10 Tips and Tricks That Will Make You an npm Ninja来了解更多。

版本管理

可以使用一些工具在同一台机器上管理多个版本的node.js.其中一个是n,另一个是 nvm (Node Version Manager). 如果你对这些感兴趣,参考这篇文章Install Multiple Versions of Node.js using nvm.

结论

在本教程中,我讲解了一些使用npm的基础,展示了怎样从官网页面安装Node.js,怎样修改全局包的位置(避免使用sudo)以及怎样在局部和全局模式下安装包,最后还讲了关于删除、更新以及安装特定版本的包相关的东西。参考npm Github releases page了解更多新版本特性。
锁着v5版本的到来,npm在向前端开发的世界里迈了一大步。据它的COO所说,npm的用户正在改变,大部分使用它的人不再用它来写Node应用了。相反,它正成为前端开发中用于打包JavaScript的工具了(严格来说,可以使用它安装任何东西)以及编写现代JavaScript的一个集成部分了。那么,你是否正在在项目中使用npm呢?如果没有的话, 现在就是时候了。

推荐阅读更多精彩内容