Skip to content
大纲

npm

npm 是 Node.js 标准的包管理工具, 随 Node.js 一同安装, 通过它可本地或全局安装依赖库并管理其版本。

npm 安装依赖

当使用 npm 安装软件包时, 可以执行两种安装类型:

  • 本地安装
  • 全局安装

默认情况下, 当输入 npm install <packageName> 命令时, 执行的是本地安装, 例如:

bash
npm install lodash

软件包会被安装到当前文件夹树中的 node_modules 子文件夹下。

并且, npm 还会在 package.json 文件的 dependencies 属性中添加 lodash 条目。

使用 -g 标志可以全局安装依赖:

bash
npm install lodash -g

-g 标志也可放在 package 之前:

bash
npm install -g lodash

全局安装时, npm 不会将软件包安装到本地文件夹下, 软件包确切位置可使用 npm root -g 查看。

bash
npm root -g

在 Windows 上, 可能是 C:\Users\YOU\AppData\Roaming\npm\node_modules。 在 macOS 或 Linux 上, 此位置可能是 /usr/local/lib/node_modules

但是, 如果使用 nvm 来管理 Node.js 版本, 位置会有所不同。

例如, 软件包的位置可能为 /Users/joe/.nvm/versions/node/v8.9.0/lib/node_modules

适合全局安装的软件包

通常, 所有的软件包都应该本地安装。

这样可以确保计算机可以有数十个应用程序, 并且如果需要, 每个应用程序都可以运行不同的版本。

但是也并不包括所有, 下面是一些流行的全局软件包示例:

  • npm
  • vue-cli
  • create-react-app
  • nodemon
  • react-native-cli
  • grunt-cli
  • mocha
  • gatsby-cli
  • forever

可以通过在命令行上运行以下命令查看系统上已安装的全局安装包:

bash
npm list -g --depth 0

npm 依赖与开发依赖

当使用 npm install <package-name> 安装 npm 软件包时, 是将为其安装依赖项。

该软件包会被自动的列出在 package.json 文件中的 dependencies 列表下。

WARNING

在 npm 5 之前, 必须手动指定 --save

当添加了 -D--save-dev 标志时, 则会将其安装为开发依赖项, 添加至 devDependencies 列表。

开发依赖是仅用于开发的软件包, 在生产环境并不需要, 如测试的软件包、webpack、Babel 等。

DANGER

-S--save 的缩写, -D--save-dev 的缩写

bash
npm install cowsay --save
# 等同于
npm install cowsay -S

npm install webpack --save-dev
# 等同于
npm install webpack -D

安装指定版本软件包

npm install <package-name> 会默认安装最新版本的软件包, 但有时候并不想如此。

可以使用 @ 语法来安装指定版本的软件包

bash
npm install cowsay@1.3.0

如何使用或执行 npm 安装的软件包

当安装完软件包后, 如何使用它呢?

假设使用以下命令安装了流行的 JavaScript 实用工具库 lodash:

bash
npm install lodash

这会把软件包安装到本地的 node_modules 文件夹中。

WARNING

若要在代码中使用它, 则只需要使用 require 将其导入到程序中:

js
const _ = require('lodash')

如果软件包是可执行文件, 该怎么办 ❓ ❓ ❓

在这种情况下, 他会把可执行文件放到 node_modules/.bin/ 文件夹下。

可以利用 cowsay 来简单验证一下。

cowsay 软件包提供了一个命令行程序, 可以执行该程序以使母牛说些话 (以及其他动物也可以说话) 🐮 🐮 🐮 🐮 🐮 🐮

当使用 npm install cowsay 安装软件包时, 它会在 node_modules 文件夹中安装自身以及它的一些依赖包, 并有一个隐藏的 .bin 文件夹, 其中包含指向 cowsay 二进制文件的符号链接。

0002_node_modules-bin可执行文件

DANGER

那如何执行这些文件呢 ❓ ❓ ❓ 方式有三种:

  • 相对路径执行
  • npx 执行
  • npm scripts 执行

可以输入 ./node_modules/.bin/packageName 来运行它, 例如:

bash
./node_modules/.bin/cowsay

0003_相对路径执行cowsay

但是 npm v5.2 版本起包含的 npx 才是更好的选择, 只需运行:

bash
npx cowsay

0004_npx执行cowsay

除此之外, 还可以在 package.json 文件中配置 npm scripts, 再执行 npm run scriptName, 例如:

js
// package.json
{
  "name": "cowsay-test",
  "version": "1.0.0",
  "scripts": {
    "cowsay": "cowsay hello-cowsay"
  },
  "license": "ISC",
  "dependencies": {
    "cowsay": "^1.5.0"
  }
}
bash
npm run cowsay

0005_npm-scripts执行cowsay

package.json 指南

package.json 文件是项目的清单。它可以做很多完全互不相关的事情。例如, 它是用于工具的配置中心。 它也是 npmyarn 存储所有已安装软件包名称和版本的地方。

创建 package.json 文件的方式有两种:

  • 手动创建
  • 自动创建

手动创建即直接在项目根目录下新建一个 package.json 文件, 然后输入相关内容。

自动创建则是在根目录下执行 npm init 命令, 然后根据提示一步步输入相应内容后自动创建。

如使用默认配置, 可用 npm init -y 命令跳过配置自动创建。

json
{
  "name": "default",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
这是一个更复杂的示例, 从 Vue.js 提取
json
{
  "name": "test-project",
  "version": "1.0.0",
  "description": "A Vue.js project",
  "main": "src/main.js",
  "private": true,
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "test": "npm run unit",
    "lint": "eslint --ext .js,.vue src test/unit",
    "build": "node build/build.js"
  },
  "dependencies": {
    "vue": "^2.5.2"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.22.1",
    "babel-eslint": "^8.2.1",
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-jest": "^21.0.2",
    "babel-loader": "^7.1.1",
    "babel-plugin-dynamic-import-node": "^1.2.0",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-stage-2": "^6.22.0",
    "chalk": "^2.0.1",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.28.0",
    "eslint": "^4.15.0",
    "eslint-config-airbnb-base": "^11.3.0",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-import-resolver-webpack": "^0.8.3",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-vue": "^4.0.0",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.4",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "html-webpack-plugin": "^2.30.1",
    "jest": "^22.0.4",
    "jest-serializer-vue": "^0.3.0",
    "node-notifier": "^5.1.2",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "portfinder": "^1.0.13",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.8",
    "postcss-url": "^7.2.1",
    "rimraf": "^2.6.0",
    "semver": "^5.3.0",
    "shelljs": "^0.7.6",
    "uglifyjs-webpack-plugin": "^1.1.1",
    "url-loader": "^0.5.8",
    "vue-jest": "^1.0.2",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    "webpack-bundle-analyzer": "^2.9.0",
    "webpack-dev-server": "^2.9.1",
    "webpack-merge": "^4.1.0"
  },
  "engines": {
    "node": ">= 6.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"]
}

package.json 属性详解

下面详细介绍可以使用的属性, 其中 * 代表重要:

  • name* : 软件包名称, 名称必须少于 214 个字符, 且不能包含空格, 只能包含小写字母、连字符(-)和下划线(_)

    这是因为当软件包在 npm 上发布时, 它会基于此属性获得自己的 URL。

    如果在 Github 上公开的发布此软件包, 则 Github 仓库的名称是作为此属性的不错选择。

    在使用 npm init -y 自动创建 package.json 文件时, 文件夹名称映射为 name 属性, 所以文件夹名称须符合此规范。

  • version* : 软件包当前版本, 遵循语义版本控制记法, 这意味着始终以 3 个数字表示: x.y.z

    第一个数字是主版本号, 第二个数字是次版本号, 第三个数字是补丁版本号。

    仅修复缺陷的版本是补丁版本, 引入向后兼容的更改的版本是次版本, 具有重大更改的是主版本

  • author* : 软件包作者, 可设置作者名, 邮箱与链接地址, 如 authorName <email> (url)

    示例:

    json
    {
      "author": "NodeJS中文网 <mail@nodejs.cn> (http://nodejs.cn)"
    }
    

    也可使用以下格式

    json
    {
      "author": {
        "name": "NodeJS中文网",
        "email": "mail@nodejs.cn",
        "url": "http://nodejs.cn"
      }
    }
    
  • contributors : 软件包其他贡献者

    示例:

    json
    {
      "contributors": ["NodeJS中文网 <mail@nodejs.cn> (http://nodejs.cn))"]
    }
    

    也可使用以下格式:

    json
    {
      "contributors": [
        {
          "name": "NodeJS中文网",
          "email": "mail@nodejs.cn",
          "url": "http://nodejs.cn"
        }
      ]
    }
    
  • bugs : 链接到软件包的问题跟踪器, 最常用的是 Github 的 issue 页面

    示例:

    json
    {
      "bugs": "https://github.com/nodejscn/node-api-cn/issues"
    }
    
  • homepage : 设置软件包的主页

    示例:

    json
    {
      "homepage": "http://nodejs.cn"
    }
    
  • description : 软件包简短描述

    如果要将软件包发布到 npm, 则这个属性特别有用, 人们可以知道软件包是干啥用的

  • keywords : 软件包关键字数组

    有助于人们在浏览相似的软件包或浏览 npmjs 网站时找到软件包

  • repository : 软件包仓库所在位置

    示例

    json
    "repository": "github:nodejscn/node-api-cn",
    
  • main 软件包入口点

    当应用程序导入此软件包时, 应用程序会在该位置搜素模块的导出

  • private : 软件包是否私有

    如果设置为 true, 可防止软件包被意外地发布到 npm

  • scripts* : 定义一组可运行的 node 脚本

    WARNING

    这些脚本是命令行应用程序,

    可以通过 npm run xxxyarn xxx 来运行它们, 其中 xxx 是命令名称, 如 npm run dev

    可以为命令使用任何名称, 脚本也可以是任何操作

    json
    "scripts": {
      "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
      "start": "npm run dev",
      "unit": "jest --config test/unit/jest.conf.js --coverage",
      "test": "npm run unit",
      "lint": "eslint --ext .js,.vue src test/unit",
      "build": "node build/build.js"
    }
    
  • dependencies* : 生产依赖列表

    DANGER

    当使用 npm 或 yarn 安装软件包时:

    bash
    npm install <PACKAGENAME>
    npm install --save <PACKAGENAME>
    yarn add <PACKAGENAME>
    yarn add --save <PACKAGENAME>
    

    该软件包会自动地插入此列表中

  • devDependencies* : 开发依赖列表

    DANGER

    不同于 dependencies, 这些软件包只需要安装在开发机器上, 而无需在生产环境中运行代码

    当使用 npm 或 yarn 安装软件包时:

    bash
    npm install --save-dev <PACKAGENAME>
    yarn add --dev <PACKAGENAME>
    

    该软件包会自动地插入此列表

  • license 软件许可证, 让用户知道他们的使用权力和限制

    0006_license选择

  • engines : 软件包运行的 Node.js 或其他命令的版本

    示例

    json
    "engines": {
      "node": ">= 6.0.0",
      "npm": ">= 3.0.0",
      "yarn": "^0.13.0"
    }
    
  • browserslist 用于告知要支持哪些浏览器(及其版本)

    Babel、Autoprefixer 和其他工具会用到它, 以将所需的 polyfill 和 fallback 添加到目标浏览器

    示例:

    json
    "browserslist": [
      "> 1%",
      "last 2 versions",
      "not ie <= 8"
    ]
    

    此配置意味着需要支持使用率超过 1% (来自 CanIUse 的统计信息) 的所有浏览器的最新的 2 个主版本, 但不包含 IE8 及更低的版本

命令特有属性

package.json 文件还可以承载命令特有的配置, 例如 Babel、ESLint 等。

每个都有特有的属性, 例如 eslintConfigbabel 等。它们是命令特有的, 可以在相应的命令/项目文档中找到如何使用它们。

npm 语义版本控制

如果 Node.js 软件包有一件很棒的事情, 那就是它们都同意使用语义版本控制作为版本编号。

语义版本控制的概念很简单:所有的版本都有 3 个数字 x.y.z

  • 第一个数字是主版本
  • 第二个数字是次版本
  • 第三个数字是补丁版本

当发布新的版本时, 不仅仅是随心所欲地增加数字, 还要遵循以下规则:

  • 当进行不兼容的 API 更改时, 则升级主版本
  • 当以向后兼容的方式添加功能时, 则升级次版本
  • 当进行向后兼容的缺陷修复时, 则升级补丁版本

该约定在所有的编程语言中均被采用, 每个 npm 软件包都必须遵守该规定, 这一点非常重要, 因为整个系统都依赖于此。

为什么这么重要 ❓ ❓ ❓

因为 npm 设置了一些规则, 可用于在 package.json 文件中选择要将软件包更新到的版本 (当运行 npm update 时)。

规则使用了这些符号:

  • ^* : 表示接受『主版本不变更即可』的更新。
  • ~* : 只会更新补丁版本

    WARNING

    如果写入的是 ^0.13.0, 只能更新补丁版本, 即 0.13.1 可以, 但 0.14.0 就不行。

  • > : 接受高于指定版本的任何版本
  • >= : 接受等于或高于指定版本的任何版本
  • < : 接受低于指定版本的任何版本
  • = : 接受确切的版本
  • - : 接受一定范围的版本。例如: 2.1.0 - 2.6.2
  • || : 组合集合。例如 < 2.1 || > 2.6
  • 无符号* : 仅接受指定的特定版本 (如 1.2.1)
  • latest* : 使用可用的最新版本

package-lock.json

在版本 5 中, npm 引入了 package-lock.json 文件。

该文件旨在跟踪被安装的每个软件包的确切版本, 以便产品可以以相同的方式被 100% 复制 (即使依赖的软件包被更新)。

这解决了 package.json 一直尚未解决的特殊问题, 在 package.json 中, 可以使用 semver 表示法设置要升级到的版本 (补丁版本或次版本), 例如:

  • 如果写入的是 〜0.13.0,则只更新补丁版本:即 0.13.1 可以,但 0.14.0 不可以。
  • 如果写入的是 ^0.13.0,则要更新补丁版本和次版本:即 0.13.10.14.0、依此类推。
  • 如果写入的是 0.13.0,则始终使用确切的版本。

当 package.json 文件中所依赖的软件包指定了 ^~ 语法时, 当执行 npm install 操作时, 可能会引入新的次版本或补丁版本。

即使补丁版本或次版本不应该引入重大的更改, 但是还是可能引入缺陷。

DANGER

package-lock.json 会固化当前安装的每个软件包的版本, 当运行 npm install 时, npm 会使用这些确切的版本。

当运行 npm update 时, package-lock.json 文件中的依赖的版本会被更新。

示例:

json
// package.json 代码片段
{
  "dependencies": {
    "cowsay": "^1.5.0"
  }
}
json
// package-lock.json 代码片段
{
  "dependencies": {
    "cowsay": {
      "version": "1.5.0",
      "resolved": "https://registry.npmmirror.com/cowsay/-/cowsay-1.5.0.tgz",
      "integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==",
      "requires": {
        "get-stdin": "8.0.0",
        "string-width": "~2.1.1",
        "strip-final-newline": "2.0.0",
        "yargs": "15.4.1"
      }
    }
  }
}

可以看出, 在 package.json 文件中, cowsay 的版本是 ^1.5.0, 而在 package-loak.json 中则固化了 1.5.0

DANGER

当存在 package-lock.json 文件时, npm instal 会选择此文件安装依赖。

当不存在 package-lock.json 文件时, npm instal 会选择 package.json 文件安装依赖。

在 npm 版本不低于 5 时, package-lock.json 文件会随着安装依赖自动生成或更新。

查看 npm 包安装的版本

若要查看所有已安装的 npm 软件包 (包括它们的依赖包) 的版本, 则:

bash
npm list

例如:

bash
 npm list
/Users/joe/dev/node/cowsay
└─┬ cowsay@1.3.1
  ├── get-stdin@5.0.1
  ├─┬ optimist@0.6.1
   ├── minimist@0.0.10
   └── wordwrap@0.0.3
  ├─┬ string-width@2.1.1
   ├── is-fullwidth-code-point@2.0.0
   └─┬ strip-ansi@4.0.0
     └── ansi-regex@3.0.0
  └── strip-eof@1.0.0

也可以打开 package-lock.json 文件, 但这需要一些视觉扫描。

npm list -g 也一样, 但适用于全局安装的软件包。

若仅要获取顶层的软件包 (基本就是 package.json 中列出的软件包), 则运行 npm list --depth=0

bash
 npm list --depth=0
/Users/joe/dev/node/cowsay
└── cowsay@1.3.1

也可以提供指定名称来获取特定软件包的版本:

bash
 npm list cowsay
/Users/joe/dev/node/cowsay
└── cowsay@1.3.1

若要查看软件包在 npm 仓库上最新的可用版本, 则运行 npm view [package_name] version:

bash
 npm view cowsay version

1.5.0

若要查看 npm 包的所有版本, 则运行 npm view [package_name] versions:

bash
 npm view cowsay versions

[
  '1.0.0', '1.0.1', '1.0.2',
  '1.0.3', '1.1.0', '1.1.1',
  '1.1.2', '1.1.3', '1.1.4',
  '1.1.5', '1.1.6', '1.1.7',
  '1.1.8', '1.1.9', '1.2.0',
  '1.2.1', '1.3.0', '1.3.1',
  '1.4.0', '1.5.0'
]

npm 淘宝镜像

由于"墙"的存在, 咱们可能需要更换 npm 源才能正常下载和安装模块, 可以使用淘宝镜像。

临时使用:

bash
npm --registry https://registry.npm.taobao.org install <package-name>

永久使用:推荐

bash
npm config set registry https://registry.npm.taobao.org

验证是否成功:

bash
npm config get registry

恢复官方源:

bash
npm config set registry https://registry.npmjs.org

如果不想给 npm 设置代理, 也可以配置 cnpm, 这样一来 npm 依旧采用官方源。

bash
npm install -g cnpm --registry=https://registry.npm.taobao.org

WARNING

cnpm 安装软件包可能会多下载一些文件/文件夹, 建议还是给 npm 设置代理, 使用淘宝镜像。

Mochi's personal blog.