npm模块管理器

简介

npm有两层含义。一层含义是Node.js的开放式模块登记和管理系统,网址为http://npmjs.org。另一层含义是Node.js默认的模块管理器,是一个命令行下的软件,用来安装和管理node模块。

npm不需要单独安装。在安装node的时候,会连带一起安装npm。但是,node附带的npm可能不是最新版本,最好用下面的命令,更新到最新版本。

$ npm install npm@latest -g

上面的命令之所以最后一个参数是npm,是因为npm本身也是Node.js的一个模块。

node安装完成后,可以用下面的命令,查看一下npm的帮助文件。

# npm命令列表
$ npm help

# 各个命令的简单用法
$ npm -l

下面的命令分别查看npm的版本和配置。

$ npm -v
$ npm config list -l

npm set

npm set用来设置环境变量。

$ npm set init-author-name 'Your name'
$ npm set init-author-email 'Your email'
$ npm set init-author-url 'http://yourdomain.com'
$ npm set init-license 'MIT'

上面命令等于为npm init设置了默认值,以后执行npm init的时候,package.json的作者姓名、邮件、主页、许可证字段就会自动写入预设的值。这些信息会存放在用户主目录的~/.npmrc文件,使得用户不用每个项目都输入。如果某个项目有不同的设置,可以针对该项目运行npm config

$ npm set save-exact true

上面命令设置加入模块时,package.json将记录模块的确切版本,而不是一个可选的版本范围。

npm info

npm info命令可以查看每个模块的具体信息。比如,查看underscore模块的信息。

$ npm info underscore
{ name: 'underscore',
  description: 'JavaScript\'s functional programming helper library.',
  'dist-tags': { latest: '1.5.2', stable: '1.5.2' },
  repository:
   { type: 'git',
     url: 'git://github.com/jashkenas/underscore.git' },
  homepage: 'http://underscorejs.org',
  main: 'underscore.js',
  version: '1.5.2',
  devDependencies: { phantomjs: '1.9.0-1' },
  licenses:
   { type: 'MIT',
     url: 'https://raw.github.com/jashkenas/underscore/master/LICENSE' },
  files:
   [ 'underscore.js',
     'underscore-min.js',
     'LICENSE' ],
  readmeFilename: 'README.md'}

上面命令返回一个JavaScript对象,包含了underscore模块的详细信息。这个对象的每个成员,都可以直接从info命令查询。

$ npm info underscore description
JavaScript's functional programming helper library.

$ npm info underscore homepage
http://underscorejs.org

$ npm info underscore version
1.5.2

npm search命令用于搜索npm仓库,它后面可以跟字符串,也可以跟正则表达式。

$ npm search <搜索词>

下面是一个例子。

$ npm search node-gyp
// NAME                  DESCRIPTION
// autogypi              Autogypi handles dependencies for node-gyp projects.
// grunt-node-gyp        Run node-gyp commands from Grunt.
// gyp-io                Temporary solution to let node-gyp run `rebuild` under…
// ...

npm list

npm list命令以树型结构列出当前项目安装的所有模块,以及它们依赖的模块。

$ npm list

加上global参数,会列出全局安装的模块。

$ npm list -global

npm list命令也可以列出单个模块。

$ npm list underscore

npm install

Node模块采用npm install命令安装。每个模块可以“全局安装”,也可以“本地安装”。两者的差异是模块的安装位置,以及调用方法。

“全局安装”指的是将一个模块直接下载到Node的安装目录中,各个项目都可以调用。“本地安装”指的是将一个模块下载到当前目录的node_modules子目录,然后只有在当前目录和它的子目录之中,才能调用这个模块。一般来说,全局安装只适用于工具模块,比如npm和grunt。

默认情况下,npm install命令是“本地安装”某个模块。

$ npm install <package name>

npm也支持直接输入github地址。

$ npm install git://github.com/package/path.git
$ npm install git://github.com/package/path.git#0.1.0

运行上面命令后,模块文件将下载到当前目录的node_modules子目录。

使用global参数,可以“全局安装”某个模块。global参数可以被简化成g参数。

$ sudo npm install -global [package name]
$ sudo npm install -g [package name]

install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号。

$ npm install sax@latest
$ npm install sax@0.1.1
$ npm install sax@">=0.1.0 <0.2.0"

如果使用--save-exact参数,会在package.json文件指定安装模块的确切版本。

$ npm install readable-stream --save --save-exact

install命令可以使用不同参数,指定所安装的模块属于哪一种性质的依赖关系,即出现在packages.json文件的哪一项中。

  • --save:模块名将被添加到dependencies,可以简化为参数-S
  • --save-dev: 模块名将被添加到devDependencies,可以简化为参数-D
$ npm install sax --save
$ npm install node-tap --save-dev
# 或者
$ npm install sax -S
$ npm install node-tap -D

如果要安装beta版本的模块,需要使用下面的命令。

# 安装最新的beta版
$ npm install <module-name>@beta (latest beta)

# 安装指定的beta版
$ npm install <module-name>@1.3.1-beta.3

npm install默认会安装dependencies字段和devDependencies字段中的所有模块,如果使用production参数,可以只安装dependencies字段的模块。

$ npm install --production
# 或者
$ NODE_ENV=production npm install

一旦安装了某个模块,就可以在代码中用require命令调用这个模块。

var backbone = require('backbone')

console.log(backbone.VERSION)

语义版本(SemVer)

npm采用”语义版本“管理软件包。所谓语义版本,就是指版本号为a.b.c的形式,其中a是大版本号,b是小版本号,c是补丁号。

一个软件发布的时候,默认就是 1.0.0 版。如果以后发布补丁,就增加最后一位数字,比如1.0.1;如果增加新功能,且不影响原有的功能,就增加中间的数字(即小版本号),比如1.1.0;如果引入的变化,破坏了向后兼容性,就增加第一位数字(即大版本号),比如2.0.0。

npm允许使用特殊符号,指定所要使用的版本范围,假定当前版本是1.0.4。

  • 只接受补丁包:1.0 或者 1.0.x 或者 ~1.0.4
  • 只接受小版本和补丁包:1 或者 1.x 或者 ^1.0.4
  • 接受所有更新:* or x

对于~和^,要注意区分。前者表示接受当前小版本(如果省略小版本号,则是当前大版本)的最新补丁包,后者表示接受当前大版本的最新小版本和最新补丁包。

~2.2.1 // 接受2.2.1,不接受2.3.0
^2.2.1 // 接受2.2.1和2.3.0

~2.2 // 接受2.2.0和2.2.1,不接受2.3.0
^2.2 // 接受2.2.0、2.2.1和2.3.0

~2 // 接受2.0.0、2.1.0、2.2.0、2.2.1和2.3.0
^2 // 接受2.0.0、2.1.0、2.2.0、2.2.1和2.3.0

还可以使用数学运算符(比如>, <, =, >= or <=等),指定版本范围。

>2.1
1.0.0 - 1.2.0
>1.0.0-alpha
>=1.0.0-rc.0 <1.0.1
^2 <2.2 || > 2.3

注意,如果使用连字号,它的两端必须有空格。如果不带空格,会被npm理解成预发布的tag,比如1.0.0-rc.1。

npm update,npm uninstall

npm update 命令可以升级本地安装的模块。

$ npm update [package name]

加上global参数,可以升级全局安装的模块。

$ npm update -global [package name]

npm uninstall 命令,删除本地安装的模块。

$ npm uninstall [package name]

加上global参数,可以删除全局安装的模块。

$ sudo npm uninstall [package name] -global

npm shrinkwrap

对于一个项目来说,通常不会写死依赖的npm模块的版本。比如,开发时使用某个模块的版本是1.0,那么等到用户安装时,如果该模块升级到1.1,往往就会安装1.1。

但是,对于开发者来说,有时最好锁定所有依赖的版本,防止模块升级带来意想不到的问题。但是,由于模块自己还依赖别的模块,这一点往往很难做到。举例来说,项目依赖A模块,A模块依赖B模块。即使写死A模块的版本,但是B模块升级依然可能导致不兼容。

npm shrinkwrap命令就是用来彻底锁定所有模块的版本。

$ npm shrinkwrap

运行上面这个命令以后,会在项目目录下生成一个npm-shrinkwrap.json文件,里面包含当前项目用到的所有依赖(包括依赖的依赖,以此类推),以及它们的准确版本,也就是当前正在使用的版本。

只要存在npm-shrinkwrap.json文件,下一次用户使用npm install命令安装依赖的时候,就会安装所有版本完全相同的模块。

如果执行npm shrinkwrap的时候,加上参数dev,还可以记录devDependencies字段中模块的准确版本。

$ npm shrinkwrap --dev

npm prune

npm prune命令与npm shrinkwrap配套使用。使用npm shrinkwrap的时候,有时可能存在某个已经安装的模块不在dependencies字段内的情况,这时npm shrinkwrap就会报错。

npm prune命令可以移除所有不在dependencies字段内的模块。如果指定模块名,则移除指定的模块。

$ npm prune
$ npm prune <package name>

npm run

npm不仅可以用于模块管理,还可以用于执行脚本。package.json文件有一个scripts字段,可以用于指定脚本命令,供npm直接调用。

{
  "name": "myproject",
  "devDependencies": {
    "jshint": "latest",
    "browserify": "latest",
    "mocha": "latest"
  },
  "scripts": {
    "lint": "jshint **.js",
    "test": "mocha test/"
  }
}

上面代码中,scripts字段指定了两项命令linttest。命令行输入npm run lint,就会执行jshint **.js,输入npm run test,就会执行mocha test/

npm run命令会自动在环境变量$PATH添加node_modules/.bin目录,所以scripts字段里面调用命令时不用加上路径,这就避免了全局安装NPM模块。

npm内置了两个命令简写,npm test等同于执行npm run testnpm start等同于执行npm run start

npm run会创建一个shell,执行指定的命令,并将node_modules/.bin加入PATH变量,这意味着本地模块可以直接运行。也就是说,npm run lint直接运行jshint **.js即可,而不用./node_modules/.bin/jshint **.js

如果直接运行npm run不给出任何参数,就会列出scripts属性下所有命令。

$ npm run
Available scripts in the user-service package:
  lint
     jshint **.js
  test
    mocha test/

下面是另一个package.json文件的例子。

"scripts": {
  "watch": "watchify client/main.js -o public/app.js -v",
  "build": "browserify client/main.js -o public/app.js",
  "start": "npm run watch & nodemon server.js",
  "test": "node test/all.js"
},

上面代码在scripts项,定义了四个别名,每个别名都有对应的脚本命令。

$ npm run watch
$ npm run build
$ npm run start
$ npm run test

其中,starttest属于特殊命令,可以省略run

$ npm start
$ npm test

如果希望一个操作的输出,是另一个操作的输入,可以借用Linux系统的管道命令,将两个操作连在一起。

"build-js": "browserify browser/main.js | uglifyjs -mc > static/bundle.js"

但是,更方便的写法是引用其他npm run命令。

"build": "npm run build-js && npm run build-css"

上面的写法是先运行npm run build-js,然后再运行npm run build-css,两个命令中间用&&连接。如果希望两个命令同时平行执行,它们中间可以用&连接。

下面是一个流操作的例子。

"devDependencies": {
  "autoprefixer": "latest",
  "cssmin": "latest"
},

"scripts": {
  "build:css": "autoprefixer -b 'last 2 versions' < assets/styles/main.css | cssmin > dist/main.css"
}

写在scripts属性中的命令,也可以在node_modules/.bin目录中直接写成bash脚本。下面是一个bash脚本。

#!/bin/bash

cd site/main
browserify browser/main.js | uglifyjs -mc > static/bundle.js

假定上面的脚本文件名为build.sh,并且权限为可执行,就可以在scripts属性中引用该文件。

"build-js": "bin/build.sh"

参数

npm run命令还可以添加参数。

"scripts": {
  "test": "mocha test/"
}

上面代码指定npm test,实际运行mocha test/。如果要通过npm test命令,将参数传到mocha,则参数之前要加上两个连词线。

$ npm run test -- anothertest.js
# 等同于
$ mocha test/ anothertest.js

上面命令表示,mocha要运行所有test子目录的测试脚本,以及另外一个测试脚本anothertest.js

scripts脚本命令最佳实践

scripts字段的脚本命令,有一些最佳实践,可以方便开发。首先,安装npm-run-all模块。

$ npm install npm-run-all --save-dev

这个模块用于运行多个scripts脚本命令。

# 继发执行
$ npm-run-all build:html build:js
# 等同于
$ npm run build:html && npm run build:js

# 并行执行
$ npm-run-all --parallel watch:html watch:js
# 等同于
$ npm run watch:html & npm run watch:js

# 混合执行
$ npm-run-all clean lint --parallel watch:html watch:js
# 等同于
$ npm-run-all clean lint
$ npm-run-all --parallel watch:html watch:js

# 通配符
$ npm-run-all --parallel watch:*

(1)start脚本命令

start脚本命令,用于启动应用程序。

"start": "npm-run-all --parallel dev serve"

上面命令并行执行dev脚本命令和serve脚本命令,等同于下面的形式。

$ npm run dev & npm run serve

(2)dev脚本命令

dev脚本命令,规定开发阶段所要做的处理,比如构建网页资源。

"dev": "npm-run-all dev:*"

上面命令用于继发执行所有dev的子命令。

"predev:sass": "node-sass --source-map src/css/hoodie.css.map --output-style nested src/sass/base.scss src/css/hoodie.css"

上面命令将sass文件编译为css文件,并生成source map文件。

"dev:sass": "node-sass --source-map src/css/hoodie.css.map --watch --output-style nested src/sass/base.scss src/css/hoodie.css"

上面命令会监视sass文件的变动,只要有变动,就自动将其编译为css文件。

"dev:autoprefix": "postcss --use autoprefixer --autoprefixer.browsers \"> 5%\" --output src/css/hoodie.css src/css/hoodie.css"

上面命令为css文件加上浏览器前缀,限制条件是只考虑市场份额大于5%的浏览器。

(3)serve脚本命令

serve脚本命令用于启动服务。

"serve": "live-server dist/ --port=9090"

上面命令启动服务,用的是live-server模块,将服务启动在9090端口,展示dist子目录。

live-server模块有三个功能。

  • 启动一个HTTP服务器,展示指定目录的index.html文件,通过该文件加载各种网络资源,这是file://协议做不到的。
  • 添加自动刷新功能。只要指定目录之中,文件有任何变化,它就会刷新页面。
  • npm run serve命令执行以后,自动打开浏览器。、

以前,上面三个功能需要三个模块来完成:http-serverlive-reloadopener,现在只要live-server一个模块就够了。

(4)test脚本命令

test脚本命令用于执行测试。

"test": "npm-run-all test:*",
"test:lint": "sass-lint --verbose --config .sass-lint.yml src/sass/*"

上面命令规定,执行测试时,运行lint脚本,检查脚本之中的语法错误。

(5)prod脚本命令

prod脚本命令,规定进入生产环境时需要做的处理。

"prod": "npm-run-all prod:*",
"prod:sass": "node-sass --output-style compressed src/sass/base.scss src/css/prod/hoodie.min.css",
"prod:autoprefix": "postcss --use autoprefixer --autoprefixer.browsers "> 5%" --output src/css/prod/hoodie.min.css src/css/prod/hoodie.min.css"

上面命令将sass文件转为css文件,并加上浏览器前缀。

(6)help脚本命令

help脚本命令用于展示帮助信息。

"help": "markdown-chalk --input DEVELOPMENT.md"

上面命令之中,markdown-chalk模块用于将指定的markdown文件,转为彩色文本显示在终端之中。

(7)docs脚本命令

docs脚本命令用于生成文档。

"docs": "kss-node --source src/sass --homepage ../../styleguide.md"

上面命令使用kss-node模块,提供源码的注释生成markdown格式的文档。

pre- 和 post- 脚本

npm run为每条命令提供了pre-post-两个钩子(hook)。以npm run lint为例,执行这条命令之前,npm会先查看有没有定义prelint和postlint两个钩子,如果有的话,就会先执行npm run prelint,然后执行npm run lint,最后执行npm run postlint

如果执行过程出错,就不会执行排在后面的脚本,即如果prelint脚本执行出错,就不会接着执行lint和postlint脚本。

下面是一些常见的pre-post-脚本。

  • prepublish:发布一个模块前执行。
  • postpublish:发布一个模块后执行。
  • preinstall:安装一个模块前执行。
  • postinstall:安装一个模块后执行。
  • preuninstall:卸载一个模块前执行。
  • postuninstall:卸载一个模块后执行。
  • preversion:更改模块版本前执行。
  • postversion:更改模块版本后执行。
  • pretest:运行npm test命令前执行。
  • posttest:运行npm test命令后执行。
  • prestop:运行npm stop命令前执行。
  • poststop:运行npm stop命令后执行。
  • prestart:运行npm start命令前执行。
  • poststart:运行npm start命令后执行。
  • prerestart:运行npm restart命令前执行。
  • postrestart:运行npm restart命令后执行。

对于最后一个npm restart命令,如果没有设置restart脚本,prerestart和postrestart会依次执行stop和start脚本。

如果start脚本没有配置,npm start命令默认执行下面的脚本,前提是模块的根目录存在一个server.js文件。

$ node server.js

另外,不能在pre脚本之前再加pre,即preprelint脚本不起作用。

下面是一个例子。

"scripts": {
  "lint": "standard",
  "test": "node test/my-tests.js",
  "posttest": "npm run lint",
  "predeploy": "npm test",
  "deploy": "surge ./path/to/dist"
}

以上都是npm相关操作的钩子,如果安装某些模块,还能支持Git相关的钩子。下面以husky模块为例。

$ npm install husky --save-dev

安装以后,就能在package.json添加precommitprepush等钩子。

{
    "scripts": {
        "lint": "eslint yourJsFiles.js",
        "precommit": "npm run test && npm run lint",
        "prepush": "npm run test && npm run lint",
        "...": "..."
    }
}

内部变量

scripts字段可以使用一些内部变量,主要是package.json的各种字段。

比如,package.json的内容是{"name":"foo", "version":"1.2.5"},那么变量npm_package_name的值是foo,变量npm_package_version的值是1.2.5。

{
  "scripts":{
    "bundle": "mkdir -p build/$npm_package_version/"
  }
}

运行npm run bundle以后,将会生成build/1.2.5/子目录。

config字段也可以用于设置内部字段。

"name": "fooproject",
  "config": {
    "reporter": "xunit"
  },
  "scripts": {
    "test": "mocha test/ --reporter $npm_package_config_reporter"
    "test:dev": "npm run test --fooproject:reporter=spec"
  }

上面代码中,变量npm_package_config_reporter对应的就是reporter。

一般来说,每个项目都会在项目目录内,安装所需的模块文件。也就是说,各个模块是局部安装。但是有时候,我们希望模块是一个符号链接,连到外部文件,这时候就需要用到npm link命令。

为了理解npm link,请设想这样一个场景。你开发了一个模块myModule,目录为src/myModule,你自己的项目myProject要用到这个模块,项目目录为src/myProject。每一次,你更新myModule,就要用npm publish命令发布,然后切换到项目目录,使用npm update更新模块。这样显然很不方便,如果我们可以从项目目录建立一个符号链接,直接连到模块目录,就省去了中间步骤,项目可以直接使用最新版的模块。

先在模块目录(src/myModule)下运行npm link命令。


src/myModule$ npm link

上面的命令会在npm的全局模块目录内(比如/usr/local/lib/node_modules/),生成一个符号链接文件,该文件的名字就是package.json文件中指定的文件名。


/usr/local/lib/node_modules/myModule -> src/myModule

然后,切换到你需要放置该模块的项目目录,再次运行npm link命令,并指定模块名。


src/myProject$ npm link myModule

上面命令等同于生成了本地模块的符号链接。


src/myProject/node_modules/myModule -> /usr/local/lib/node_modules/myModule

然后,就可以在你的项目中,加载该模块了。


var myModule = require('myModule');

这样一来,myModule的任何变化,都可以直接在myProject中调用。但是,同时也出现了风险,任何在myProject目录中对myModule的修改,都会反映到模块的源码中。

npm link命令有一个简写形式,显示连接模块的本地目录。

$ src/myProject$ npm link ../myModule

上面的命令等同于下面几条命令。

$ src/myProject$ cd ../myModule
$ src/myModule$ npm link
$ src/myModule$ cd ../myProject
$ src/myProject$ npm link myModule

如果你的项目不再需要该模块,可以在项目目录内使用npm unlink命令,删除符号链接。


src/myProject$ npm unlink myModule

一般来说,npm公共模块都安装在系统目录(比如/usr/local/lib/),普通用户没有写入权限,需要用到sudo命令。这不是很方便,我们可以在没有root的情况下,用好npm。

首先在主目录下新建配置文件.npmrc,然后在该文件中将prefix变量定义到主目录下面。

prefix = /home/yourUsername/npm

然后在主目录下新建npm子目录。

$ mkdir ~/npm

此后,全局安装的模块都会安装在这个子目录中,npm也会到~/npm/bin目录去寻找命令。因此,npm link就不再需要 root权限了。

最后,将这个路径在.bash_profile文件(或.bashrc文件)中加入PATH变量。

export PATH=~/npm/bin:$PATH

npm adduser

npm adduser用于在npmjs.com注册一个用户。

$ npm adduser
Username: YOUR_USER_NAME
Password: YOUR_PASSWORD
Email: [email protected]

npm publish

npm publish用于将当前模块发布到npmjs.com。执行之前,需要向npmjs.com申请用户名。

$ npm adduser

如果已经注册过,就使用下面的命令登录。

$ npm login

最后,使用npm publish命令发布。

$ npm publish

如果当前模块是一个beta版,比如1.3.1-beta.3,那么发布的时候需要使用tag参数。

$ npm publish --tag beta

npm version

npm version命令用来修改项目的版本号。当你完成代码修改,要发布新版本的时候,就用这个命令更新一下软件的版本。

$ npm version <update_type> -m "<message>"

npm version命令的update_type参数有三个选项:patch、minor、major。

  • npm version patch增加一位补丁号(比如 1.1.1 -> 1.1.2)
  • npm version minor增加一位小版本号(比如 1.1.1 -> 1.2.0)
  • npm version major增加一位大版本号(比如 1.1.1 -> 2.0.0)。

下面是一个例子。

$ npm version patch -m "Version %s - xxxxxx"

上面命令的%s表示新的版本号。

除了增加版本号,npm version命令还会为这次修改,新增一个git commit记录,以及一个新的git tag。

由于更新npm网站的唯一方法,就是发布一个新的版本。因此,除了第一次发布,这个命令与npm publish几乎是配套的,先使用它,再使用npm publish

npm deprecate

如果想废弃某个版本的模块,可以使用npm deprecate命令。

$ npm deprecate my-thing@"< 0.2.3" "critical bug fixed in v0.2.3"

运行上面的命令以后,小于0.2.3版本的模块的package.json都会写入一行警告,用户安装这些版本时,这行警告就会在命令行显示。

参考链接