npm

介绍

npm 全名 node package manager

基本命令

1
2
3
4
5
6
7
8
9
10
11
# 查看 npm 命令列表
npm help

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

# 查看 npm 版本
npm -v

# 查看 npm 配置
npm config list -l

npm 使用

  1. npm init 一路回车,初始化 package.json 文件
  2. 如果使用 -f(代表 force)、-y(代表 yes)直接跳过询问阶段,直接生成 package.json 文件
1
2
3
4
npm init -y
npm init -f

# -y 和 -f 一样的效果

npm set 设置环境变量

  • mac 的配置文件在 vim ~/.npmrc *
1
2
3
4
npm set init-author-name 'your name'
npm set init-author-email 'your email'
npm set init-author-url 'your website'
npm set init-license '开源协议 MIT'

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

1
2
3
4
5
# 全局设置 下载的每个模块都确切版本,而不是一个可选的版本范围,版本前不带 ^ 或 ~
npm set save-exact true

# 如果需要安装依赖时明确版本 固定依赖包axios的版本
npm install axios --save-exact
1
2
# 将指定的 $dir 目录,设为模块的全局安装目录,如果当前有这个目录的写权限,那么运行 npm i的时候,就不需要 sudo 命令授权了
npm config set prefix $dir
1
2
3
npm config set save-prefix ~

# 上面的命令使得 `npm install --save` 和 `npm install --save-dev` 安装新模块时允许的版本范围从克拉符号 ^ 改成 ~,即从允许小版本升级,变成只允许补丁包的升级,详细的规则后面会有介绍

npm info

npm info 命令可以查看每个模块的具体信息

1
2
3
4
5
# 查看underscore模块的信息
npm info underscore
npm info underscore description
npm info underscore homepage
npm info underscore version

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

1
2
3
npm search <搜索>

npm search axios

npm list

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

1
2
3
4
5
6
7
npm list

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

# 列出单个模块
npm list vue

npm install

node 模块采用 npm install 命令安装

每个模块都可以全局安装或是本地安装,全局安装指的是将一个模块安装到系统目录中,各个项目都可以调用,一般来说,全局安装只适合于工具模块,比如 eslintgulp,本地安装指的是将一个模块下载到当前项目的 node_modules 子目录,在项目目录之中,才能调用这个模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 本地安装
npm install <package name>

# 全局安装
npm install -g <package name>
npm install -global <package name>

# 也可以直接输入github代码库地址
npm install git://github.com/package/path.git
npm install git://github.com/package/path.git#0.1.0

# 强制重新安装
npm install <packageName> --force

# 所有模块强制重新安装,直接删除node_modules
rm -rf node_modules
npm install


# 下载最新的npm @latest表示最新版本 npm可以自己安装自己,之所以可以这样是因为 npm 本身和 node 其他模块没有区别
npm install npm@latest -g

安装不同版本

npm i 总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上特定的版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
npm i axios@latest # 安装最新版本
npm i axios@0.1.1 # 安装指定版本
npm i axios@">=0.1.0 <0.2.0" # 安装大于0.1.0小于0.2.0版本


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

npm i readable-stream --save --save-exact

npm i sax --save
npm i node-tap --save-dev
# 或者
npm i sax -S
npm i node-tap -D

# --save: 模块将被添加 dependencies 可以简化为 -S
# --save-dev: 模块将被添加到 devDependencies 可以简化为 -D

# 如果安装beta版本的模块,使用如下命令

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

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

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

npm i --production

# 或者
NODE_ENV=production npm install

避免系统权限

默认情况下,npm 全局模块会安装在系统目录(/usr/local/lib),普通用户没有写入权限,需要使用 sudo,使用如下方式可以在没有 root 权限下,安装全局模块

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

1
prefix = /home/qingxin/npm

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

1
mkdir ~/npm

此后,全局安装的模块都会安装到这个子目录中,npm 也会到 ~/npm/bin 目录去寻找命令

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

1
export PATH=~/npm/bin:$PATH

npm update

npm update 命令用来更新本地安装的模块

1
2
3
4
5
6
7
# 升级当前项目的指定模块

npm update [module-name]

# 升级全局安装的模块

npm update -global [module-name]

它会先到原创仓库查询最新版本,然后查询本地版本,如果本地版本不存在,或者远程版本较新,就会安装

使用 -S--save 参数,可以在安装的时候更新 package.json 里面模块的版本号

注意,从 npm v2.6.1 开始,npm update 只更新顶层模块,而不更新依赖的依赖,以前版本是递归更新的,如果想取到老版本的效果,使用如下命令

1
npm --depth 9999 update

npm uninstall

npm uninstall 命令,卸载已安装的模块

1
2
3
4
npm uninstall [package name]

# 卸载全局模块
npm uninstall [package name] -global

npm run

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

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

npm run 如果不加任何参数,直接运行,会列出 package.json 里面所有可以执行的脚本命令,npm run 实际上是 npm run lint 的缩写

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

1
npm run serve

npm run 会创建一个 shell 执行指定的命令,并临时将 node_modules/.bin 加入 PATH 变量,这意味着本地模块可以直接运行

1
npm i eslint -S

例如运行了上面的命令,它产生了两个结果,eslint 被安装在当前目录的 node_modules 目录下,其次,node_modules/.bin 目录会生成一个符号链接 node_modules/.bin/eslint,指向 eslint 模块的可执行脚本,然后就可以在 package.jsonscript 属性里面,不带路径的引用 eslint 这个脚本

1
2
3
4
5
6
7
8
9
{
"name": "Test Project",
"devDependencies": {
"eslint": "^1.10.3"
},
"scripts": {
"lint": "eslint ."
}
}

运行 npm run lint的时候,它会自动执行 ./node_modules/.bin/eslint .

如果希望一个操作的输出,是另一个操作的输入,可以借用 linux 系统的管道命令,酱两个操作链接在一起

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

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

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

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

1
2
3
4
5
6
7
8
9
// 一个例子
"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 脚本

1
2
3
4
#!/bin/bash

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

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

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

npm run 参数

npm run 命令添加参数

1
2
3
4
5
{
"scripts": {
"test": "mocha test/"
}
}

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


1
2
3
4
5
npm run test -- anothertest.js

# 等于

mocha test/ anothertest.js

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

npm run本身有一个参数 -s,表示关闭 npm 本身的输出,只输出脚本产生的结果

npm-run-all

  1. npm-run-all 这个模块可以运行多个 scripts 脚本命令
1
npm i npm-run-all -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 继发执行
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 脚本用于启动应用程序

      1
      2
      3
      4
      "start": "npm-run-all --parallel dev serve"
      # npm run start 并行执行 dev 脚本 和 serve 脚本命令
      # 相当于
      npm run dev & npm run serve
    • 如果没有 start 脚本配置,npm start 命令默认执行下面的脚本,前提是模块的根目录存在一个 server.js 文件

      1
      node server.js
  2. dev 脚本命令
  • dev 脚本命令,规定开发阶段所要做的处理,比如构建网页资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "dev": "npm-run-all dev:*"
    // 这个命令用于继发执行所有 dev 的子命令 例如:dev:sass dev:autoprefix


    "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 文件 这个一个hook 后面会讲到


    "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%的浏览器
  1. serve 脚本命令

    • serve 脚本用于启动服务
    1
    2
    "serve": "live-server dist/ --port=9090"
    // 上面命令启动服务,用的是live-server模块,将服务启动在9090端口,展示dist子目录
    • live-server 模块有三个功能
      • 启动一个 HTTP 服务器,展示指定目录的 index.html 文件,通过该文件加载各种网络资源,这是 file:// 协议做不到的
      • 添加自动刷新功能。只要指定目录之中,文件有任何变化,它就会刷新页面
      • npm run serve 命令执行以后,自动打开浏览器
      • 以前,上面三个功能需要三个模块来完成:http-serverlive-reloadopener,现在只要 live-server 一个模块就够了
  2. test 脚本命令

    • test 命令用于执行测试
    1
    2
    3
    4
    "test": "npm-run-all test:*",
    // 执行 npm run test 它会帮我们执行test的所有子命令 例如 test:lint
    "test:lint": "sass-lint --verbose --config .sass-lint.yml src/sass/*"
    // 上面命令规定,执行测试时,运行lint 脚本,检查脚本之中的语法错误
  3. prod 脚本命令

    • prod 脚本命令,规定进入生产环境时需要做的处理
    • 例如下面的命令,将 sass 文件转为 css 文件,并加上浏览器前缀
    1
    2
    3
    "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"
  4. help 脚本命令

    • help 脚本命令用于展示帮助信息
    • 下面的命令,markdown-chalk 模块用于将指定的 markdown 文件,转为彩色文本显示在终端
    1
    "help": "markdown-chalk --input DEVLOPMENT.md"
  5. docs 脚本命令

    • docs 脚本用于生成文档
    • 下面的命令使用 kss-node 模块,提供源码的注释生成 markdown 格式的文档。
    1
    "docs": "kss-node --source src/sass --homepage ../../styleguide.md"

pre-post- 脚本

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "myproject",
"devDependencies": {
"eslint": "latest"
"karma": "latest"
},
"scripts": {
"lint": "eslint --cache --ext .js --ext .jsx src",
"test": "karma start --log-leve=error karma.config.js --single-run=true",
"pretest": "npm run lint",
// 在执行 npm run test 之前执行
"posttest": "echo 'Finished running tests'"
// 在执行 npm run test 之后执行
}
}
  1. 注意:如果执行过程出错,就不会执行排在后面的脚本,即如果 prelint 脚本执行出错,就不会接着执行 lintpostlint 脚本。

  2. 一些常见的 pre-post- 脚本

    • prepublish:发布一个模块前执行
    • postpublish:发布一个模块后执行
    • preinstall:用户执行npm install命令时,先执行该脚本
    • postinstall:用户执行npm install命令时,安装结束后执行该脚本,通常用于将下载的源码编译成用户需要的格式,比如有些模块需要在用户机器上跟本地的C++模块一起编译
    • 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命令后执行
  3. 对于最后一个npm restart命令,如果没有设置restart脚本,prerestartpostrestart会依次执行stopstart脚本

  4. 另外,不能在pre脚本之前再加pre,即prepretest脚本不起作用

  5. 注意,即使 Npm 可以自动运行prepost脚本,也可以手动执行它们

  6. 一些例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "postinstall": "node lib/post_install.js"

    // 用于处理从Git仓库拉下来的源码。比如,有些源码是用TypeScript写的,可能需要转换一下 安装之后执行 lib/post_install.js
    }



    {
    "dist:modules": "babel ./src --out-dir ./dist-modules",
    "gh-pages": "webpack",
    "gh-pages:deploy": "gh-pages -d gh-pages",
    "prepublish": "npm run dist:modules",
    // npm run publish时,会先执行Babel编译,然后调用Webpack构建,最后发到Github Pages上面
    "postpublish": "npm run gh-pages && npm run gh-pages:deploy"
    // && 继发 先执行 npm run gh-page 然后执行 npm run gh-pages:deploy
    }
  7. 以上都是npm相关操作的钩子,如果安装某些模块,还能支持Git相关的钩子。下面以husky模块为例

    1
    npm install husky --save-dev
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "scripts": {
    "lint": "eslint yourJsFiles.js",
    "precommit": "npm run test && npm run lint",
    // commit 之前
    "prepush": "npm run test && npm run lint",
    "...": "..."
    // push之前
    }
    }

    类似作用的模块还有pre-commitprecommit-hook

  8. 内部变量

    • scripts 字段可以使用一些内部变量,主要是 package.json 的各种字段
    • 比如 package.json 的内容是 {"name":"foo", "version":"1.2.5"} 那么变量 npm_package_name 的值就是 foo,变量 npm_package_version 就是 1.2.5
    1
    2
    3
    4
    5
    6
    {
    "scripts":{
    "bundle": "mkdir -p build/$npm_package_version/"
    }
    }
    // 运行npm run bundle以后,将会生成build/1.2.5/子目录
    • config 字段也可以用来设置内部字段
    1
    2
    3
    4
    5
    6
    7
    8
    "name": "fooproject",
    "config": {
    "reporter": "xunit"
    },
    "scripts": {
    "test": "mocha test/ --reporter $npm_package_config_reporter"
    }
    // 变量npm_package_config_reporter对应的就是reporter
  9. 通配符

  10. npm 的通配符的规则如下

    • * 匹配0个或多个字符
    • ? 匹配1个字符
    • [...] 匹配某个范围的字符。如果该范围的第一个字符是!^,则匹配不在该范围的字符
    • !(pattern|pattern|pattern) 匹配任何不符合给定的模式
    • ?(pattern|pattern|pattern) 匹配0个或1个给定的模式
    • +(pattern|pattern|pattern) 匹配1个或多个给定的模式
    • *(a|b|c) 匹配0个或多个给定的模式
    • @(pattern|pat*|pat?erN) 只匹配给定模式之一
    • ** 如果出现在路径部分,表示0个或多个子目录

全局模块

  1. 全局模块可以直接在任何地方使用

就是将当前的目录临时放到全局下

开发 npm 模块的时候,有时我们会希望,边开发边试用,比如本地调试的时候 require('module'),会自动加载本机开发中的模块,node 规定,使用一个模块时,需要将其安装到全局或项目的 node_modules 目录中,对于开发中的模块,解决方法就是在全局的 node_modules 目录中,生成一个符号链接,指向模块的本地目录

npm link 就是起到这个作用,会自动创建这个符号链接

举个🌰

在项目根目录执行

1
2
3
4
5
6
# 该命令会在 npm 全局模块目录中,生成一个符号链接文件,该文件的名字就是 package.json文件中指定的模块名
src/myModule$ npm link


# 模块名
/path/to/global/node_modules/myModule -> src/myModule

这个时候,已经可以全局调用模块了,但是如果我们要让这个模块安装在项目内,还要进行下面的步骤

切换到项目目录,再次运行 npm link 命令,并指定模块名

1
2
3
4
5
src/myProject$ npm link myModule

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

src/myProject/node_modules/myModule -> /path/to/global/node_modules/myModule

然后就可以加载模块了

1
var moduleO = require("myModule")

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

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

1
src/myProject$ npm unlink myModule

npm bin

1
2
3
4
npm bin

# 显示相对于当前目录的,Node模块的可执行脚本所在的目录(即.bin目录)
/User/qx/node_modules/.bin

npm adduser

1
2
3
4
5
npm adduser

Username: Username
Password: Password
Email: Email

npm publish

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 申请用户名
npm adduser

# 登录
npm login

# 发布
npm publish


# 如果当前模块是一个 beta 版本,比如 1.3.1-beta.3 那么发布的时候 需要使用 tag 参数,将其发布到指定标签,默认的发布标签是 @latest

npm publish --tag beta

# 如果发布私有模块,模块初始化的时候,需要加上 scope 参数,只有 npm 的付费用户才能发布私有模块
npm init --scope=<yourscope>

# 如果模块是 es6 语法,那么发布的时候,最好转成 es5 首先 需要安装Babel
npm i babel-cli@6 babel-preset-es2015@6 -D

然后在 package.json 里写入 build 脚本

1
2
3
4
"scripts": {
"build": "babel source --presets babel-preset-es2015 --out-dir distribution",
"prepublish": "npm run build" // publish 之前打包
}

运行上面的脚本,会将 source 目录中的 es6 源码文件,转为 distribution 目录里面的 es5 文件,然后创建 .npmignore.gitignore ,分别写入以下内容

1
2
3
4
5
6
7
# .npmignore

source

# .gitignore
node_modules
distribution

npm deprecate

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

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

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

npm owner

  1. 模块的维护者可以发布新版本,npm owner 命令用于管理模块的维护者
1
2
3
4
5
6
7
8
# 列出指定模块的维护者
npm owner ls <package name>

# 新增维护者
npm owner add <user> <package name>

# 删除维护者
npm owner rm <user> <package name>

其他命令

  1. npm home 打开一个模块的主页

  2. npm repo 打开一个模块的代码仓库

    1
    2
    3
    4
    npm home $package
    npm repo $package

    # 这两个命令不需要提前安装模块
  3. npm outdated 检查当前项目所依赖的模块,是否已经有新版本

    • 它会输出当前版本 current version 应当安装的版本 wanted version 和最新发布的版本 latest version
  4. npm prune 检查当前项目的 node_modules 目录中,是否有 package.json 里面没有提到的模块,然后将所有这些模块输出在命令行

  5. npm shrinkwrap 锁定当前项目的依赖模块的版本

    • 运行该命令后,会在当前项目的根目录下生成一个 npm-shrinkwrap.json 文件,内容是node_modules 目录下所有已经安装的模块,以及它们的精确版本
    • 下次运行 npm install 命令时,npm 发现当前目录下有 npm-shrinkwrap.json 文件,就会只安装里面提到的模块,且版本也会保持一致

git / npm

npm 版本号管理的问题

  1. semver 规范,规定了版本号,由 MAJOR MINOR PATCH
  2. MAJOR 可能不在兼容老版本
  3. MINOR 新增了一些兼容旧版本的 api
  4. PATCH 修复 bug

gittag 对应着 npm 的版本

1
2
# 会自动和git进行关联
npm version major minor patch

版本号的含义

  1. 2.2.0 必须是 2.2.0
  2. ^2.2.0 限定大版本,后面更新只要不超过 2 就可以
  3. ~2.2.0 限定前两个版本,后面的版本只要比 0 大就可以
  4. =2.0 大于这个版本
  5. <=2.0 小于等于这个版本

预发版本

  1. alpha 预览版本,内部测试版
  2. beta 测试版,公开测试版
  3. rc 最终测试版本

scripts

  1. 可以配置脚本的命令 快捷键(可以把很长的命令放到 scripts 中)
  2. 执行命令 会将当前的 node_modules 目录下的 .bin 文件夹放到全局中(所以可以直接使用)
  3. npm run start 可以简写为 npm start

npx

  1. npxscript 一样可以帮我们直接运行 .bin 目录下的内容
  2. 如果 .bin 目录下存在 会执行对应脚本,如果不存在会下载运行

npx 只是一个临时的使用方案。 npm5.2 之后产生的

源的切换

1
2
3
4
5
npm i nrm -g

nrm ls

nrm use