npm 基本用法

发表于: 2016-12-01分类于:前端开发者笔记

每天都在使用 npm,但自己明白很多地方还是不清楚,为此决定仔细阅读 npm 的官方文档,这篇文章讲解了 npm 的基本使用方法,后续会讲 npm 的一些高级用法。

虽然前不久出现的 yarn 确实比 npm 快,但是我想 npm 至少还会使用很久,因此有必要详细了解它。

package.json 中的常见字段

package.json 相当于是一个项目的整体描述,其中记录了该项目的 git 地址,项目简介,依赖模块,bug 汇报地址等等信息,如今很多工具都利用 package.json 来存放配置文件,比如 eslint 可以读取 package.json 中的 eslintConfig 字段来获取配置,babel 可以读取 babel 字段来获取配置。

关于 package.json 中每个字段的定义,请参照官方文档 package.json,这里只提到一些非常常用的字段。

name

包名,这个名字决定了包在 node_modules 中的文件夹名,可以加上 scope,比如 @att/wxx,scope 为 att,用户安装的时候会被安装在 node_modules/@att/ 这个文件夹下。

main

该模块的入口文件,比如 "main": "build/index.js",别人在 require 这个包的时候,实际是 require 了项目中的 build/index.js 这个文件。

bin

用来指定可执行文件的名称和路径,比如 { "bin" : { "wxx-cli" : "src/cli.js" } },这样用户在安装该模块的时候,npm 会创建一个文件叫做 wxx-cli 放置在 node_modules/.bin 文件下,并将其链接至 src/cli.js 这个文件上。关于此,后面还会详细讲解。

scripts

用来指定一些脚本,也就是人们通常所说的 npm script,这个字段是一个对象,在这里可以自定义多个命令,这些命令可以使用 npm run 来执行,比如:

{
    "scripts":{
        "build": "webpack",
        "start": "webpack-dev-server"
    }
}

有了上面这样的配置,在项目中执行 npm run build 就会执行命令 webpack,npm script 涉及的东西较多,后面有专门的一章来讲它。

dependencies 和 devDependencies

package.json 中的 dependenciesdevDependencies 分别记录了该项目在生产环境下依赖的模块,和在开发环境下依赖的模块。

比如一个项目使用了 typescript 来开发,在发布的时候通常要将代码编译为 JavaScript,因此在开发的时候使用的 typescript 就应该放在 devDependencies,所以在安装的时候,可以使用 npm i typescript --save-dev 来安装。

另外项目代码中依赖了一个模块 qs 来解析 url,为了让你的代码跑起来,这个模块必须存在,因此,必须将该模块记录在 dependencies 中,使用 npm i qs --save 来进行安装。这样用户在运行你的代码的时候,你代码中的 require('qs') 才不至于找不到模块报错。

peerDependencies

peerDependencies 常常出现在一些插件的 package.json 中,比如一个 react 的第三方组件,它只能用在 react 版本高于 15.0.0 的时候,此时它就应该这样写:

{
    "peerDependencies": {
        "react": "15.x"
    }
}

这表示,要想使用我,你必须使用 react 15.0.0 以后的版本,请确保你安装了 react 15.0.0 以后的版本。

为什么要存在 peerDependencies 呢?

使用 peerDependencies 表明的是,我与另外一个模块协调工作。这有别于依赖于某个模块,仔细体会,他们之间的差异很微妙。一个插件,可能不会去引用摸个模块,比如一个 jQuery 插件,它只是在 jQuery.propotype 上添加了一些方法,因此在安装插件的时候,插件应该是默认你已经安装了它的宿主模块。npm3 以后,在执行 npm install 的时候不会安装 peerDependencies 中的模块,这是符合 peerDependencies 的设计理念的。

如何使用 peerDependencies?

如果你的模块与另外一些模块协同工作,该模块要想工作,需要用户已经安装另外一个模块,比如一个 webpack 的 loader,一个 gulp 的插件,一个 express 的中间件等等。这个时候你需要考虑使用 peerDependencies

另外,设置 peerDependencies 的时候 版本范围要大,试想一下,你的项目中使用了 10 个 react 的第三方组件,这些第三方组件的 peerDependencies 分别是这样写的 "react": "15.0.0""react": "15.1.0""react": "15.2.0" …,这时候会出现什么情况?情况是你没办法同时使用这些第三方组件,因为它们限定了要使用某个特定版本的 react,你没有办法兼顾所有组件的要求。

当相反,如果版本的限制要求的比较宽泛,比如 "react": ">15.0.0" 表示大于 15.0.0 版本就好了,这样以来所有的组件都可以融洽相处了。

Ok,上面这些 package.json 中的字段算是最为常见的了,其他有些看到单词就知道是什么意思了,还有一些不常见,如需了解,请参见官方文档。

创建 package.json

使用 npm init 来创建一个 package.json,在创建的过程中要求输入一些基本信息,如果希望使用默认内容,可以使用 npm init --yes 这样就不需要一步步按回车了。

可以通过 npm set 来设置默认项,比如 npm set init.author.email "i@example.com" 设置默认邮件。

安装模块时候的选项

在安装模块的时候通常使用 npm install <package name> 比如 npm install q,也可以使用缩写 npm i q,但更多时候当我们安装了一个模块后,通常希望将它记录下来,将其写入到 package.json 中,在安装模块的时候,可以通过 --save--save-dev 选项来将一个模块添加至 dependenciesdevDependencies 中。

关于模块的版本

一个模块的版本号应该形如 1.0.0,在版本升级的时候,每一位代表着不同的变化:

  • 修改 bug,打补丁,应该修改最后一位,比如 1.0.1
  • 新的特性,且不会影响现存的特性,比较小的修改,增加中间一位,比如 1.1.0
  • 大的修改,对现有功能有影响,增加第一位,比如 2.0.0

设定使用的版本的模块

安装第三方模块的时候需要指定其版本号,如果你打开一个既有项目的 package.json,会看到 ^, ~ 这样的符号,因为依赖的模块是在不断升级的,这些符号表示接受怎样的升级:

用户在安装某个模块的时候,也会一并安装该模块依赖的模块(存在于 dependencies 中的模块),这个时候 npm 会安装最新的满足你版本要求的模块。看下面详细说明:

  • 接受微小的升级,bug 修复:1.0 或者 1.0.x 或者 ~1.0.4,这表示在安装的时候接受最后一位的变化。比如在初次安装的时候,模块 A 是 1.0.0 版本,用户下次安装的时候 A 模块已经升级到了 1.0.9,这个时候就会安装 1.0.9 版本,如果 A 项目的维护者,下次发布了 1.1.0 那么,再次安装的时候依然会安装 1.0.9。
  • 接受较小的升级,新增特性:1 或者 1.x 或者 ^1.0.4,这表示接受中间位置的变化
  • 接受重大升级:* 或者 x

懂得这个很重要,很多时候这个符号写的不对,导致依赖模块升级后,整个项目就跑不起来了。当然了有时候会看到更加复杂的写法,请参照这里 semver 了解更高级的用法。

升级模块

依赖的模块才你开发的过程中也可能在升级,为了获得符合你的版本描述的最新版本,可以在 package.json 所在目录下执行 npm update 来升级模块。也可以通过执行 npm outdated 来获取当前项目中可升级的模块的信息。

卸载模块

因为项目变动,不需要使用某个模块了,这个时候不应该让他继续存在于 package.json 中的,没有使用到,但还是要被安装,这显然是浪费资源,污染环境的做法。使用 npm uninstall loadsh 来卸载一个模块,不会从 package.json 中删除该模块,需要添加 --save--save-dev 来卸载他们。

安装全局模块

有两种模式来安装模块 global 和 local,安装在全局的模块通常是一些命令行工具,比如 eslint,可以将其安装在全局,在任何目录下都能使用。但是全局的模块只能存在一个版本,这也是为什么很多时候一些模块建议安装在本地,这样不同的项目可以使用不同的版本,避免不同项目之间的冲突。将一个模块安装在全局,只需要加上 -g 选项,比如 npm i eslint -g

安装全局模块,常常出现一些权限问题,通常是因为当前用户没有 npm 全局安装目录的写权限,为此可以使用 chown 来修权限,也可以直接使用 sudo 来执行 npm install 操作,或者将 npm 默认安装目录修改为一个自己具有权限的目录。这些操作请参考 Fixing npm permissions

升级全局模块

升级一个全局模块,只需要执行 npm update -g <package> 即可,比如 npm update -g gulp

卸载全局模块

很简单 npm uninstall -g <package> 比如 npm uninstall -g eslint

发布一个 npm 模块

创建了一个项目,填写了 package.json,为了让自己的代码让更多人使用,需要将其发布到 npm 上面,需要仔细填写 package.json 中的 descriptionkeywords 这些字段的信息将有助于其他人在 npm 上搜索到该模块。

为了将模块发布到 npm 上,首先需要创建一个 npm 的账号,这可以在 https://npmjs.com/ 上创建账号,也可以使用 npm adduser 在命令行中创建账号。如果是在网站上注册的账号,需要使用 npm login 来登录。在登录的时候需要输入用户名和密码,以及你的 E-mail。

登录完成后执行 npm publish 就可以发布模块了,在发布模块之前要确保你的模块名在当前的 npm 仓库中是不存在的。另外本地的一些测试文件,通常不需要发布在 npm 上,这样用户在安装的时候只会下载那些必要的文件,npm 在发布的时候会忽略掉所有 .gitignore.npmignore 中忽略的文件,通常你应该在 .npmignore 中忽略掉测试文件、文档等内容,另外要提供一个 README.md

升级模块

对自己的模块进行了升级以后,需要将更新发布到 npm 上去,这个时候要同样是使用 npm publish,但是在此之前要注意更新版本号,你可以手动更改,也可以使用 npm version <update_type> 来更改,这儿的 update_type 可以是 patch, minor, major 使用这三个值,npm 会对应地将版本号的第 3 位、第 2 位、第 1 位加 1,分别表示此次升级有微小改动、有较小改动以及有大幅改动。

带有命名空间的模块

有些时候需要创建一个公司内部使用的模块,或者某个模块已经有了同名的模块存在,这个时候可以用到带有命名空间的模块,其名称形如 @scope/project,在 npm 上面带有命名空间的模块默认是私有的,而且每个用户有有一个属于自己的命名空间也就是 @username,在 npm 上私有的模块是要付费的,因此为了免费地发布一个带有命名空间的模块,需要将该模块设置为公开的,只需要在执行 publish 的时候加上 --access=public 选项即可。

在安装这些带有命名空间的模块的时候需要这样安装 npm install @scope/project --save,在项目中引用的时候也要带上 scope,require('@scope/project')

使用 tag

npm 也允许开发着给某个版本打 tag,比如当版本进行到 1.0.9 的时候可以给他打个 tag 叫做 beta,这个时候用户可以使用 npm i project-name@beta 来安装这个版本,这等价于 npm i project@1.0.9

使用 npm dist-tag add <package>@<version> tag 来给某个版本打 tag,默认情况下载 npm publish 的时候 npm 会给当前版本打一个 tag 叫做 latest,表示这是最新的,可以使用 npm publish --tag <tag-name> 来改变默认的 tag。