为什么需要包管理器
CommonJS 的出现,使 node 环境下的 JS 代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。
为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等等,一时间,在前端社区涌现了大量的第三方库。这些库使用 CommonJS 标准书写而成,非常容易使用。
然而,在下载使用这些第三方库的时候,遇到难以处理的问题:
-
下载过程繁琐
- 进入官网或 github 主页
- 找到并下载相应的版本
- 拷贝到工程的目录中
- 如果遇到有同名的库,需要更改名称
- 如果该库需要依赖其他库,还需要按照要求先下载其他库
- 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
- 更新一个库极度麻烦
- 自己开发的库,如何在下一次开发使用
以上问题,就是包管理工具要解决的问题
NPM
- npm 全称为 node package manager,即 node 包管理器,它运行在 node 环境中,让开发者可以用简单的方式完成包的查找、安装、更新、卸载、上传等操作
- npm 之所以要运行在 node 环境,而不是浏览器环境,根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能。而 node 属于服务器环境,没有浏览器的种种限制,理论上可以完全掌控运行 node 的计算机。
- npm 的出现,弥补了 node 没有包管理器的缺陷,于是很快,node 在安装文件中内置了 npm,当开发者安装好 node 之后,就自动安装了 npm,不仅如此,node 环境还专门为 npm 提供了良好的支持,使用 npm 下载的包更加方便了。
- npm 由三部分组成:
- registry:入口
- 可以把它想象成一个庞大的数据库
- 第三方库的开发者,将自己的库按照 npm 的规范,打包上传到数据库中
- 使用者通过统一的地址下载第三方包
- 官网:https://www.npmjs.com/
- 查询包
- 注册、登录、管理个人信息
- CLI:command-line interface 命令行接口
设置淘宝镜像
npm config set registry https://registry.npm.taobao.org
本地安装
- 使用命令
npm install 包名
或npm i 包名
即可完成本地安装 - 本地安装的包出现在当前目录下的
node_modules
目录中
- 随着开发的进展,
node_modules
目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用.gitignore
文件忽略该目录中的内容- 本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用
- 通常在项目的根目录中使用本地安装
- 安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到
node_modules
目录中- 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到
node_modules/.bin
下,使用命令npx 命令名
即可调用
全局安装
- 全局安装的包放置在一个特殊的全局目录,该目录可以通过命令
npm config get prefix
查看 - 使用命令
npm install --global 包名
或npm i -g 包名
- 重要:全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具
-大部分情况下,都不需要全局安装包,除非:
- 包的版本非常稳定,很少有大的更新
- 提供的 CLI 工具在各个工程中使用的非常频繁
- CLI 工具仅为开发环境提供支持,而非部署环境
配置文件(package.json)
- 可以手动创建该文件,而更多的时候,是通过命令
npm init
创建的 - 使用
npm init --yes
或npm init -y
可以在生成配置文件时自动填充默认配置 - package.json文件最重要的作用,是记录当前工程的依赖
- 这样一来,代码移植就不是问题了,只需要移植源代码和package.json文件,不用移植node_modules目录,然后在移植之后通过命令
npm install
即可重新恢复安装
包的使用及如何查找包
- 当使用 nodejs 导入模块时,如果模块路径不是以 ./ 或 ../ 开头,则 node 会认为导入的模块来自于 node_modules 目录,例如:
var _ = require("lodash");
它首先会从当前目录的以下位置寻找文件
node_modules/lodash.js
node_modules/lodash/入口文件
上面提到的入口文件按照以下规则确定
- 查看导入包的package.json文件,读取main字段作为入口文件
- 若不包含main字段,则使用index.js作为入口文件
- 若当前目录没有这样的文件,则会回溯到上级目录按照同样的方式查找
- 如果到顶级目录都无法找到文件,则抛出错误
语义版本
- 版本规范:主版本号.次版本号.补丁版本号
- 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的API、技术架构发生了重大变化
- 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的API
- 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
避免还原的差异
- npm 在安装包的时候,会自动生成一个 package-lock.json 文件,该文件记录了安装包时的确切依赖关系
- 当移植工程时,如果移植了 package-lock.json 文件,恢复安装时,会按照 package-lock.json 文件中的确切依赖进行安装,最大限度的避免了差异
npm的差异版本处理
如果两个包依赖同一个包的不同版本,在 node_modules 目录中,不会使用扁平的目录结构,而会形成嵌套的目录,如下图:
├── node_modules
│ ├── a
│ │ ├── node_modules
│ │ │ ├── c
│ │ │ | |—— c包的文件
│ │ │── a包的文件
│ ├── b
│ │ ├── node_modules
│ │ │ ├── c
│ │ │ | |—— c包的文件
│ │ │── b包的文件
# npm 脚本 (npm scripts)
在开发的过程中,我们可能会反复使用很多的 CLI 命令,例如:
- 启动工程命令(node 或 一些第三方包提供的CLI命令)
- 部署工程命令(一些第三方包提供的CLI命令)
- 测试工程命令(一些第三方包提供的CLI命令)
这些命令纷繁复杂,根据第三方包的不同命令也会不一样,非常难以记忆
于是,npm 非常贴心的支持了脚本,只需要在 package.json 中配置 scripts 字段,即可配置各种脚本名称
之后,我们就可以运行简单的指令来完成各种操作了
运行方式是npm run 脚本名称
不仅如此,npm 还对某些常用的脚本名称进行了简化,下面的脚本名称是不需要使用run的:
- start
- stop
- test
一些细节:
- 脚本中可以省略npx
- start脚本有默认值:node server.js
运行环境配置
- node中有一个全局变量 global (可以类比浏览器环境的window),该变量是一个对象,对象中的所有属性均可以直接使用
- global有一个属性是process,该属性是一个对象,包含了当前运行node程序的计算机的很多信息,其中有一个信息是env,是一个对象,包含了计算机中所有的系统变量
- 通常,我们通过系统变量 NODE_ENV 的值,来判定node程序处于何种环境
我们一般使用临时设置; 为了避免不同系统的设置方式的差异,可以使用第三方库 cross-env 对环境变量进行设置
"scripts": {
"start": "cross-env NODE_ENV=development node index.js",
"build": "cross-env NODE_ENV=production node index.js",
"test": "cross-env NODE_ENV=test node index.js"
},
其他npm命令
安装指定版本
npm install 包名@版本号
发布包
- 准备工作
- 移除淘宝镜像源
- 到npm官网注册一个账号,并完成邮箱认证
- 本地使用 npm cli 进行登录
- 使用命令
npm login
登录 - 使用命令
npm whoami
查看当前登录的账号 - 使用命令
npm logout
注销
- 使用命令
- 创建工程根目录
- 使用npm init进行初始化
- 发布
- 开发
- 确定版本
- 使用命令
npm publish
完成发布