使用 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
中的必要字段,例如 name
、author
、homepage
等等,具体的字段以及含义可以去查 npm 的官方文档。
这里需要注意的是,如果你将要发布的包,是别人通过 require('package_name')
来使用的话,请指定 package.json
中的 main
字段为该包的入口文件。或者,也可能你将要发布的包只是一个命令行工具,那么删除掉 main
字段,指定 bin
字段即可。当然 main
与 bin
是可以共存的。
编码
初始化完成后,就主要是我们编码了,建议将入口文件放在项目根目录下,其余代码文件都放在相应文件夹下:
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
并不是最好的方案,查看以下文章:
####### 小技巧
这里有个小技巧可以不使用 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 的包在发布前通常需要进行编译和压缩,目的是解决兼容性问题和资源加载优化,而这些工作一般借助 Babel、Rollup 等工具配合使用即可。另一方面,用于 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');
}
通过下面这篇文章即可了解实现细节。
支持多个环境
纵观 Node.js 的发展历史,其生态中出现过多种模式,例如 AMD、CommonJS、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
####### 新的方案
事实上,main
和 browser
字段在 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