如今每个语言体系中都有一个包管理工具,PHP的Composer,Ruby的gem,Python的pip,Java的Maven……当然还有Node.js的npm。有的人会奇怪为何要引入又一个新东西来让我们已经够辛苦的编程工作雪上加霜呢?其实不然,例如我们在做Java开发的时候,有的项目依赖数百个jar,开发人员在build之前总会碰到各种包版本,包找不到的问题,但包管理工具却让我们从这种依赖噩梦中解救出来。我们开发过程中的任何依赖,都可以通过这种工具自动从远程的包仓库中下载回来,并且保证是我们需要的版本,然我们可以专注于开发生产中。
唯一一个缺点,可能就是我东方大国和那些老外的主机距离有够远,经常无法通过官方镜像下载,这种时候通常有三种办法:一个是对包管理工具本身设置代理,第二个是直接全局VPN,第三个就是使用国内同步镜像,当然前提是要有。
好了废话不多说了,下面这篇文章要是出现在几年前就好了,我当时头一次接触npm的时候也是有够晕,希望这次的这篇文章能够给初学者指明一个直路。
正文
Node.js让开发者可以用JavaScript编写服务端程序,它基于用C++编写的JavaScript的V8引擎构建,所以它很快(你是否想说C更快?但要知道比起那些用Java编写的中间件来说,C++已经很快了——译者注)。起初,NodeJS主要是设计为做服务端程序开发的,但慢慢的,开发者们把它应用于构建本地自动化工具链。自此开放出了一片新的NodeJS生态环境(例如Grunt和Gulp),让新一代的前端开发者们逐渐形成了工程化的思想。
为了利用好这些工具(或者包),我们需要以一种良好的方式去安装和管理它们(就像Windows的添加删除程序一样)。于是npm就诞生了,npm以你希望的方式安装这些工具,并且提供了一个易用的接口组织这些工具。在我介绍之前,首先要在你的额系统中安装Node.js。
安装Node.js
去官网的下载页可以获取你需要的版本。Windows和Mac都有现成的安装包。而对于Linux平台来说也准备了预编译好的二进制文件和源代码。另外,你也可以用对应Linux发行版的包管理工具来安装。
在本文撰写之时(2016年4月3日)官方最新版是v.5.10.0 stable。
下面让我们看看如何查看Node的安装路径以及版本:
$ which node
/usr/local/bin/node
$ node --version
v5.7.0
我们通过来尝试Node的REPL(交互式解释器)来验证一下是否安装成功。
$ 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已经随Node一起装好了,无需额外安装。
$ which npm
/usr/local/bin/npm
$ npm --version
3.6.0
用Node.js安装模块
npm可以在你的某个项目中单独安装模块,也可以在你的电脑中全局安装。在某个项目中单独安装的时候,npm会下载所有的文件到你项目中的一个叫做node_modules的文件夹内。这个文件夹的所有者是当前用户(在Linux中,对文件夹的所有者权限敏感,其他用户如果没有授权是无法查看另一个用户拥有的文件的,Windows中虽然也有这个概念,但通常我们个人电脑用户都很单一,所以并不是很重视这个概念——译者注)。全局模块会被安装到{prefix}/lib/node_modules/
这个目录下,目录的拥有者是root用户({prefix}
通常是/usr/
或者/usr/local/
)。这意味着如果你要在某个Linux发行版下全局安装模块,需要用sudo命令,否则会引发权限错误。
更改全局模块安装路径
首先让我们看一下npm config
这个命令会输出什么
$ npm config list
; cli configs
user-agent = "npm/3.6.0 node/v5.7.0 linux x64"
; node bin location = /usr/local/bin/node
; cwd = /home/sitepoint
; HOME = /home/sitepoint
; 'npm config ls -l' to show all defaults.
这里列出了npm的基本配置信息,重点是这里
$ npm config get prefix
/usr/local
列出了全局安装模块的路径。我们通过下面的命令可以在我们的用户主目录下新建一个文件夹,然后把全局模块安装路径更改到这个目录下。
$ cd && mkdir .node_modules_global
$ npm config set prefix=$HOME/.node_modules_global
这样,我们就更改了全局模块的安装位置了。做了责怪操作以后,我们同时也会得到一个自动新建的.npmrc文件。
$ npm config get prefix
/home/sitepoint/.node_modules_global
$ cat .npmrc
prefix=/home/sitepoint/.node_modules_global
但对于有强迫症的开发者来说,这样还不够,我们的npm还停留在root目录下呢。不要紧,我们可以用下面的命令重新安装npm到我们的本地目录中。
$ npm install npm --global
/home/sitepoint/.node_modules_global/bin/npm -> /home/sitepoint/.node_modules_global/lib/node_modules/npm/bin/npm-cli.js
/home/sitepoint/.node_modules_global/lib
└── npm@3.7.5
好了,最后一步,我们把.node_modules_global/bin
添加到环境变量$PATH
中,我们输入npm命令的时候,就不会去root目录下寻找,而是在我们自己的主目录下寻找了。
export PATH="$HOME/.node_modules_global/bin:$PATH"
现在我们用which命令可以确认我们现在用的npm是来自哪个目录的了。
$ which npm
/home/sitepoint/.node_modules_global/bin/npm
$ npm --version
3.7.5
ok,这样依赖,我们就不需要再用sudo来进行全局安装了。
在全局安装模块
第一个例子,我们来装一个包,叫做UglifyJS。这是一个JS压缩工具。我们用--global这个参数来告诉npm我们要进行全局安装,这个参数也可以简写为-g
$ npm list --global
├─┬ npm@3.7.5
│ ├── abbrev@1.0.7
│ ├── ansi-regex@2.0.0
│ ├── ansicolors@0.3.2
│ ├── ansistyles@0.1.3
....................
└─┬ uglify-js@2.6.2
├── async@0.2.10
├── source-map@0.5.3
├── uglify-to-browserify@1.0.2
大家可以看到,这个输出相当复杂,我们在安装其他包的时候,有可能会更惨不忍睹。我们可以用--depth=0来告诉控制台只输出一个层级的信息。
$ npm list -g --depth=0
├── npm@3.7.5
└── uglify-js@2.6.2
好了,接下来你就可以直接用uglifyjs这个命令在shell中压缩文件了。
$ uglifyjs example.js -o example.min.js
在自己的项目中安装模块
不带任何参数,直接用npm install
命令即可在单独的项目目录中安装模块了。新的包会安装到项目文件夹下的一个叫做node_modules
的目录中。例如,下面我们在本地安装一个叫做underscore模块(Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能):
$ mkdir ~/project && cd ~/project
$ npm install underscore
/home/sitepoint/project
└── underscore@1.8.3
$ ls
node_modules
$ ls node_modules
underscore
在自己项目中列出安装过的模块
我们可以通过npm list
来列出自己项目已经安装过的模块
$ npm list
/home/sitepoint/project
└── underscore@1.8.3
这就表示,我们可以在另一个项目目录中,安装另一个版本的underscore模块。
卸载模块
还是以underscore为例,让我们卸载后并检查这个模块是否还存在:
$ npm uninstall underscore
- underscore@1.8.3 node_modules/underscore
$ npm list
/home/sitepoint/project
└── (empty)
安装特定版本的模块
默认情况下,npm总会寻找最新版本的包来安装,但我们仍然可以通过参数指定安装特定版本的模块:
$ npm install underscore@1.8.2
/home/sitepoint/project
└── underscore@1.8.2
$ npm list
/home/sitepoint/project
└── underscore@1.8.2
更新模块
如果我们以前安装的某个包,有了新版本,例如修复bug,那我们需要一条命令能直接对这个包进行升级。
$ npm update underscore
underscore@1.8.3 node_modules/underscore
$ npm list
/home/sitepoint/project
└── underscore@1.8.3
注:我们需要在package.json
中标记underscore为依赖才能让上面的命令生效。(参见下面的“管理依赖”一节)
搜索模块
我们在Linux通常会使用mkdir
这个命令,那么npm中是否有这样一种包能达到一样的效果呢。在这个场景下,我们就可能会需要搜索某个具有特定功能的模块,那么我们可以用如下命令去搜索:
$ npm search mkdir
npm WARN Building the local index for the first time, please be patient
我们发现,还真有一个,叫做mkdirp。那我们试试直接安装它
$ npm install mkdirp
/home/sitepoint/project
└─┬ mkdirp@0.5.1
└── minimist@0.0.8
然后我们试试看是否真的是我们想要的包
var mkdirp = require('mkdirp');
mkdirp('foo', function (err) {
if (err) console.error(err)
else console.log('Directory created!')
});
运行结果如下
$ node. mkdir.js
Directory created!
ok,还真成功了~
管理缓存
通常,我们在用npm安装了一个模块以后,npm会保留一份安装包到本地的.npm目录中,以加速下次安装同样的包的速度。例如在Linux发行版,这个目录是~
$ ls ~/.npm
_locks minimist mkdirp registry.npmjs.org underscore
但日积月累,这个目录下一定会积攒很多文件,所以我们需要一个命令清理它。
$ npm cache clean
管理依赖
现在,我们已经装过2个包了,但是这以后肯定会需要更多包,所以我们每次都手动安装并非明智之举,这种时候,我们经常在github项目中看到的package.json
就派上用场了。我们可以通过这个文件来管理我们的包安装。而这个文件可以用npm init
命令创建。
$ npm init
This utility will walk you through creating a package.json file.
Press ^C at any time to quit.
name: (project) demo
version: (1.0.0)
description: Demo of package.json
entry point: (index.js)
test command:
git repository:
keywords:
author: Sitepoint
license: (ISC)
然后在项目根目录会生成这样一个package.json
文件。我们对这个文件添加我们的依赖项,并设置一些参数:
{
"name": "demo",
"version": "1.0.0",
"description": "Demo package.json",
"main": "main.js",
"dependencies": {
"mkdirp": "^0.5.1",
"underscore": "^1.8.3"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Sitepoint",
"license": "ISC"
}
下面这个命令可以省略大段的输入参数,直接创建一个配置文件:
$ npm init --yes
package.json
中的name参数将会用你项目的文件夹名字来自动设定。
我们可以通过添加private:true
这个配置项来阻止不小心发布了私有的仓库,而且可以阻止在执行npm install
的时候会产生的警告。让我们创建一个目录,然后试试用package.json
来下载模块。
$ mkdir ~/demo && cd ~/demo
$ cp ~/project/package.json ~/demo
$ npm install
$ npm list
demo@1.0.0 /home/sitepoint/demo
├─┬ mkdirp@0.5.1
│ └── minimist@0.0.8
└── underscore@1.8.3
又有新问题了,如果我们要手动安装新包的时候(不试用package.json
,直接在shell用npm install
命令),如何保持这个文件也能同步修改呢?我们可以用--save参数。例如下面,我们用--save参数安装一个叫做request的新包
$ npm install request --save
$ npm list --depth=0
demo@1.0.0 /home/sitepoint/demo
├── mkdirp@0.5.1
├── request@2.53.0
└── underscore@1.8.3
然后,我们的package.json
文件就会被自动更新为
"dependencies": {
"mkdirp": "^0.5.1",
"request": "^2.53.0",
"underscore": "^1.8.3"
}
可以看到对应的包已经被加入了package.json
中了,这全部过程不需要我们修改文件。
##版本管理器##
有几个工具可以让我们在同一台机器上使用不同版本的Node.js。其中一个叫做n。另一个叫做nvm(Node Version Manager)。有一篇教程是专门讲这个问题的,这里就不赘述了。