使用 npm

内容列表

npm 是 Node.js 的一个包管理器,Web 前端工程师也经常利用它来简化开发流程,看看如何愉快的使用 npm ,并且发布自己的包,让 npm 成为我们的开发利器。

npm

**Node.js:**https://www.npmjs.com/

**npm:**https://www.npmjs.com/

npm 通常是伴随着 Node.js 一起安装的,只要安装了 Node.js,那么 npm 也就已经安装好了,可以在命令行运行以下命令查看版本:

node -v
npm -v

换源

这是作为墙内的开发者必须掌握的一项技能,将 npm 的官方源替换为国内源,这样下载 npm 包速度也会更快,更不容易出错。

运行以下命令查看 npm 配置,其中有仓库源 registry 一项:

npm config list
npm config get registry     // 只查看 registry 配置

更换仓库源:

npm config set registry <source>

npm config set registry https://registry.npmjs.org/         // 换回官方源
npm config set registry https://registry.npm.taobao.org/    // 换到国内淘宝源

更换掉仓库源之后,再查看下是否更换成功。

请勿使用 cnpm 等其它类似工具,用官方 npm 换到国内源即可。

常用源切换工具 nrm

常用命令

通常来说,我们只会利用 npm 来安装、卸载依赖包,或者在项目启动时进行初始化。

npm init [-y]   // 在当前文件夹初始化,生成一个 package.json 文件,-y 选项为全部默认

# https://docs.npmjs.com/cli/v6/commands/npm-install
npm install --global|-g <package_name>     // 在全局安装指定包
npm uninstall --global|-g <package_name>   // 卸载安装在全局的指定包

npm install [-P|--save] <package_name>     // 在当前项目本地安装生产环境依赖包,会列在 dependencies 中
npm uninstall [-S|--save] <package_name>   // 卸载安装在项目本地的 dependencies 中指定包

npm install -D|--save-dev <package_name>   // 在当前项目本地安装开发环境依赖包,会列在 devDependencies 中
npm uninstall -D|--save-dev <package_name> // 卸载安装在项目本地的 devDependencies 中指定包

这里值得一提的是,在 npm 5.2+ 之后,附带了一个 npx 命令,作用是执行包的二进制文件

# https://www.npmjs.com/package/npx
npx [options] [-p|--package <pkg>]... <command> [command-arg]...

npx create-react-app my-app     // 执行 create-react-app 包的主命令

通过 npx 命令执行包的二进制文件有一个优点:不需要安装包,即可执行包的命令,对本地环境无污染。

而且,在 npm 6.0+ 之后,init 命令可以接收一个新的选项:

# https://docs.npmjs.com/cli/v6/commands/npm-init
npm init <initializer>

npm init react-app my-app       // same as : npx create-react-app my-app

其中 <initializer> 是一个以 create- 开头命名的包,算是对这种特殊命名的包的 npx 命令的简写方式。

Node 版本管理

作为一名 Node.js 开发,如何在自己的设备上管理多个 Node 版本是一个相当重要的技能,而 npm 库中的 n 模块就为我们提供了最佳解决方案,使用它可以在同一台设备上安装多个不同版本的 Node,并随时进行切换,同时也可以方便的升级、降级。

作为一个管理 Node 的工具,建议将其安装在全局:

npm install -g n

安装完成后,执行以下命令安装相应版本的 Node:

n <version> // eg: n 10.16.0
n latest    // 安装最新版 node
n lts       // 安装最新的 LTS 版本 node

查看已安装的所有版本的 Node:

n   // 貌似只能查看通过 n 模块安装的 node

切换 node 版本来执行命令:

n use <version> [args ...]    // 切换到已安装的另一个版本的 node 并执行命令

卸载 node:

n rm <version>      // 卸载指定版本 node
n prune             // 卸载所有已安装 node 版本,但当前正在使用的 node 版本不会被卸载

Windows 平台用户可以使用 nvm-windows

npm 升级

虽然 npm 是随 Node 一起安装的,但在之后通过 n 模块升级 Node 的过程中,npm 不会也跟着升级,需要我们手动升级:

npm install -g npm[@latest|<version>]

发布 npm 包

通常,我们只是下载安装 npm 库中的包来使用,辅助我们进行开发,但去了解如何利用 npm 发布包也是有必要的,这样我们也可以写一些自己的模块并进行发布供自己和他人使用。

注册

首先需要去 npm 官网注册一个账号(无需翻墙)。如果想更换头像的话,还要去注册一个 Gravatar 并上传一张照片,才可以将这张照片作为头像。

**npm:**https://www.npmjs.com/

初始化

在本地新建一个文件夹,并初始化:

npm init [-y]

然后,修改生成的 package.json 中的必要字段,例如 nameauthorhomepage 等等,具体的字段以及含义可以去查 npm 的官方文档。

这里需要注意的是,如果你将要发布的包,是别人通过 require('package_name') 来使用的话,请指定 package.json 中的 main 字段为该包的入口文件。或者,也可能你将要发布的包只是一个命令行工具,那么删除掉 main 字段,指定 bin 字段即可。当然 mainbin 是可以共存的。

编码

初始化完成后,就主要是我们编码了,建议将入口文件放在项目根目录下,其余代码文件都放在相应文件夹下:

Package/
  - build/   // 编译后用于生产环境的文件
  - config/  // 项目开发环境配置文件
  - bin/     // 项目命令行脚本文件
  - scripts/ // 项目 npm 脚本文件
  - src/     // 项目源码文件
  - index.js // 项目入口文件

这里需要注意,bin/ 中的命令行脚本文件,必须在每个文件的第一行指定 #!/usr/bin/env node,表明这是一个 node 脚本,以及执行该脚本的二进制文件系统路径。

发布前本地测试

在编码完成并完善 package.json 文件后,我们可能需要测试才能确保最终发布后能被自己或者他人通过 npm 安装正常使用。

我们不需要反复进行发布–测试–修复–撤销发布–重新发布这个过程,npm 官方为我们提供了便捷的本地测试工具,也就是 link 命令。

# https://docs.npmjs.com/cli/v6/commands/npm-link
npm link    // 在你将要发布的包根目录下执行该命令,如同将其安装到全局一样,更改文件及时生效,不需要重新 link

npm link <package_name>     // 在另外一个测试目录中执行该命令,如同 install

npm unlink  // 测试完成后,在你将要发布的包根目录下执行该命令,unlink 会将其从全局卸载

本地测试还是相当简单和方便的,也是无污染的。但是, npm link 并不是最好的方案,查看以下文章:

4 reasons to avoid using npm link

####### 小技巧

这里有个小技巧可以不使用 npm link 命令就能在本地测试,而且是真的无污染:

"dependencies": {
  "my-dev-module": "file:../my-dev-module/index.min.js"
}

登录

发布前需要在命令行登录 npm 官方仓库

# https://docs.npmjs.com/cli/v6/commands/npm-adduser
npm login [--registry=url] [--scope=@orgname]

注意:如果替换了官方源,一定要指定 --registry=https://registry.npmjs.org/,这样才能登录到官方仓库进行发布。--scope则是命名空间,例如 @babel

登录成功后,可以查看已登录用户:

# https://docs.npmjs.com/cli/v6/commands/npm-whoami
npm whoami [--registry <registry>]

发布

登录后,即可通过 publish 命令发布包:

# https://docs.npmjs.com/cli/v6/commands/npm-publish
npm publish [--access public]   // 在将要发布的包根目录执行

需要注意,如果发布的包带有命名空间,例如 @babel/core,需要指定发布限制范围 --access,默认为 restricted(受限制),如你的 npm 帐户不是付费帐户,必须指定为 public

撤销发布

通常,是在本地测试无误后进行发布,如果真的在发布后发现问题,导致不能正常使用,可以撤销发布:

# https://docs.npmjs.com/cli/v6/commands/npm-unpublish
npm unpublish [<@scope>/]<package_name>[@<version>]

**事实上,npm 官方不建议开发者使用 unpublish 命令来撤销发布,因为如果其它用户已经安装了该包作为依赖,并能正常使用的情况下该包被撤销,会导致其它用户无法再次安装该包。**所以,尽可能用 deprecate 命令来表明该包已被弃用,即便用户安装成功,也会有醒目的提示告知用户已被弃用,用户则会及时寻找替代包。

# https://docs.npmjs.com/cli/v6/commands/npm-deprecate
npm deprecate <package_name>[@<version>] <message>

退出登录

如果不是在自己的机器上工作,建议完成发布后退出登录,保证数据安全。退出登录与登录一样简单,同样需要指定 --registry--scope 参数。

# https://docs.npmjs.com/cli/v6/commands/npm-logout
npm logout [--registry=url] [--scope=@orgname]

最佳实践

以上,是使用 npm 工具本身的过程,但 npm 工具本质上是为维护和发布 Node 模块/包服务的,开发 Node 模块/包有一些很好的社区实践,这里大致记录一下开发 Node 模块/包过程中一些注意的关键点。

模块/包的类型

根据用途,模块/包的类型大致可以分为以下几种,不同的类型需要做对应的处理。

  • Node 包
  • Web 包

Node 包一般来说发布时是不需要压缩的,JavaScript 代码也不需要编译,但需要标记一些特殊字段(如 engines)表明包所依赖的 Node 版本限制。

// https://docs.npmjs.com/cli/v6/configuring-npm/package-json#engines
{
  "engines": { "node": ">=0.10.3 <0.12" }
}

而用于 Web 的包在发布前通常需要进行编译和压缩,目的是解决兼容性问题和资源加载优化,而这些工作一般借助 BabelRollup 等工具配合使用即可。另一方面,用于 Web 的包在开发过程中为了便于调试,所以引入的应该是未经编译和压缩的源码版本,而打包时再引入经过编译和压缩的版本,实现这个目的社区有一个比较通用的做法就是在入口文件使用 NODE_ENV 进行判断并导出相应版本文件,以下是 React 的入口文件示例:

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

通过下面这篇文章即可了解实现细节。

https://overreacted.io/how-does-the-development-mode-work/

支持多个环境

纵观 Node.js 的发展历史,其生态中出现过多种模式,例如 AMDCommonJS、ESM(ECMAScript modules),以及 UMD,这种情况给包的开发带来一定困难,不过经过多年的发展社区已经形成一个约定(共识),可以很方便的解决该问题从而同时支持所有环境。

主要是通过对应字段导出不同的入口文件来实现,下面是一个示例:

{
  "main": "index.js",
  "module": "index.esm.js",
  "browser": "index.umd.js"
}

具体实现可以通过下面这篇文章进行了解。

https://2ality.com/2017/04/setting-up-multi-platform-packages.html

####### 新的方案

事实上,mainbrowser 字段在 npm 文档中有定义,查看文档:

https://docs.npmjs.com/cli/v6/configuring-npm/package-json#main

module 字段后来并没有被 Node 社区采用,而是推出了新的模块入口点定义字段 exports,配合条件导出我们就可以实现支持多个环境。查看文档:

https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#package-entry-points

示例:

{
  "exports": {
    "import": "./index.esm.js",
    "require": "./index.cjs.js",
    "browser": "./index.umd.js"
  },
  "main": "./index.cjs.js"
}

类型定义

JavaScript 并不是一个强类型语言,所以 IDE 要做类型推断和代码智能提示是比较困难的,尤其是编译、压缩、混淆后的代码对于用户使用有诸多不便,要不断的查询文档。然而,TypeScript 的出现使这一状况得到了改善,如果源代码直接使用 TypeScript 编写,最终编译时生成类型定义文件,在发布 npm 模块/包时指定一个 types 字段即可,查看文档:

https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html

对于使用 JavaScript 编写的源代码,事实上也可以利用 TypeScript 工具生成相应的类型定义文件,并随之一起发布,查看文档:

https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html

在使用第三方库时,如果作者发布时没有附带类型定义文件,我们则可以去社区维护的 DefinitelyTyped 仓库中看看,使用 npm 命令即可查询:

npm info @types/react

参考资料

相关

IDE:VS Code 配置同步

利用一款插件来同步 VS Code 的配置到 GitHub 的 gist 上,实现多个设备间共享一套配置。

了解更多

编辑器:Sublime Text - Vim 插件

Sumblime Text 是一个具有漂亮的界面和强大功能的文本编辑器,而且也支持许多丰富的插件。它是一个收费软件,但是允许开发人员无限期的免费试用。

了解更多

一些互联网名词

互联网时代是个新概念频出的时代,每年有相当多的新名词诞生,而诸多与商业活动紧密相关的词汇了解一下有助于我们熟悉日常工作中接触的东西。

了解更多