概念
模块 module
通常以单个文件形式存在的功能片段,入口文件通常被称为入口模块。
库 library
以一个或多个模块组成的完整功能,为开发某一个方面问题提供完整的解决方案。
例如 jQuery、MockJS 等等。
包 package
包含元数据的库,这些元数据包括:名称、描述、git 主页、许可协议、作者等等。
背景
CommonJS 的出现,让 Node 环境的使用 JS 模块化开发更加的颗粒化,模块化的细分是开发大型应用的基础。
我们在开发的时候通常可能会使用一些第三方的工具库来解决一些问题,例如加密、模拟数据等等。
然后我们在下载工具库的时候通常会遇到这样的几个问题:
1、下载的过程比较繁琐,需要我们自己去官网或者 git 上下载
2、如果一个工具库有依赖别的库,我们还得下载它依赖的库
3、有些功工具库我们只是在开发的时候使用,生产环境则不需要如何进行区分呢?
4、更新一个工具库极度的麻烦
基于这些问题就是包管理器要解决的问题!
前端包管理器
前端中的包管理器其实有很多,例如 yarn、cnpm、pnpm 等,但是最核心的还是 npm,因为这些包管理器都是基于 npm 的。
npm 的全称是 node package manager,也就是 node 包管理器。
npm 的出现弥补了最初 Node 没有包管理器的缺陷,于是很快 Node 内置了 npm,当我们安装好 node 后,npm 也随着安装完成啦。
npm 由三个部分组成:
1、registry
可以把 registry 理解为一个巨大的数据库,开发者可以将自己写的包发布到 npm 的 registry 中。
2、官网
官网可以查询包,也可以登录注册、管理个人信息。
3、CLI 命令行
当我们安装好 npm 后,可以通过 CLI 来使用 npm。
包的安装
npm 的包管理器服务地址是在国外,国内安装某些包的时候可能会受到网络影响导致下载缓慢或致失败。
所以,我们最好更改一下 npm 的包管理地址,而淘宝提供了一个镜像地址,它会同步 npm 的包服务。
# 设置地址
$ npm config set registry https://registry.npm.taobao.org
# 查看地址
$ npm config get registry
这样,我们在国内也可以很快的下载包了。
npm 安装包分为「本地安装」和「全局安装」两种形式。
本地安装
使用如下命令进行安装一个包:
$ npm install packageName
$ npm i packageName # 简写
本地安装的包出现在当前目录下的 node_modules 目录中。
而我们安装的包可能又会依赖其他的包,这个时候 npm 会一并进行安装,所以某些时候,我们明明安装一个包却多出了一堆包,这就是因为 npm 分析了包的依赖关系然后全部安装了进来。
在实际的开发中,随着开发的进展 node_modules 的体积会变得非常的大,所以该目录通常不会传送到 git 仓库。
使用 .gitignore 来忽略 node_modules 目录的托管:
node_modules
如果我们安装的包存在 CLI 命令,那么 CLI 会安装到 node_modules/.bin 这个目录下,例如我安装了一个 Webpack:
然后,我们使用npx webpack
就可以运行 Webpack 这个工具了。
npx 是安装 npm 时自带的命令,运行 npx 会到当前目录下的 node_modules/.bin/ 执行你要运行的文件。
全局安装
全局安装的包放置在一个特殊的全局目录,该目录可以通过命令如下命令进行查看位置:
$ npm config get prefix
# 输出 /usr/local
每个人的目录不一样,我的是 MacOS 系统。
使用如下命令进行全局安装包:
# 全局安装一个包
$ npm install -g packageName
# 或者简写
$ npm install --global packageName
通常情况下,有很少的包需要你进行全局的安装,全局安装也是为了使用改包的 CLI 命令。
package.json 文件
前面我们说过,node_modules 的体积很大通常不会上传到 git 上,那么别人下载下来我们的代码后如何使用依赖的库呢?
npm 通过配置文件来解决这个问题,当然不止这一个功能。
npm 将每一个使用了 npm 的工程目录都看作是一个包,包的信息需要通过一个 package.json 文件来描述。
使用如下命令来创建 package.json 文件且输入一些元信息:
# 需要手动输入信息,然后回车确认
$ npm init
# 或者自动生成默认信息
$ npm init --yes
$ npm init -y # 简写
一个 package.json 文件包含这些基本的信息:
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
- name:包名,不能使用中文
- version:版本号,默认是 1.0.0
- 版本规范:主版本号.次版本号.补丁版本号
- 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的API、技术架构发生了重大变化
- 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的API
- 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
- description:包的描述
- keywords:关键字,方便搜索
- author:作者,必须是有效的 npm 账户名
- homepage:官网地址
- repository:仓库地址,通常是 git 地址
- main:入口文件,默认是 index.js
- license:许可证,默认是 MIT
大多数情况下,我们都是开发项目应用,并不会发布到 npm 上,所以有些信息我们并不需要特别的关注。
package.json 文件还可以帮助我们管理项目的库依赖,当我们的要安装一个生产的依赖库:
$ npm install packageName --save
# 简写
$ npm i packageName -S
这个时候我们的 package.json 文件会多出一个dependencies
对象,该对象包含了我们的库依赖及对应的版本。
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
}
}
如果要安装一个开发依赖需要使用如下命令:
$ npm install packageName --save-dev
# 简写
$ npm i packageName -D
这个时候我们的 package.json 文件会多出一个devDependencies
对象,该对象和dependencies
的作用是一样的,只不过它们管理的环境不同。
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"webpack": "^5.88.2"
}
}
所以,当别人下载下来我们代码的时候,他们的目录不会有 node_modules 目录,但是会有 package.json 文件,然后可以使用如下命令进行安装项目所需要的依赖:
# 根据 package.json 文件安装项目依赖
$ npm install
# 简写
$ npm i
或者可以只安装生产环境的依赖:
$ npm i --production
包的使用
Node 对 npm 的支持非常的好,当使用require()
的时候,如果不是相对路径(./ 或者 ../),那么 Node 会执行下面的规则:
1、包是不是 Node 内置的包?例如 fs 这样 Node 内置的包,不需要通过 npm 进行下载就可以直接导入使用。
2、如果不是 Node 的内置包,就会到当前目录下的 node_modules 目录根据下面的规则进行查找。
例如我们引入一个 chalk 库:
const _ = require("lodash");
1、先找当前目录的node_modules
文件夹
2、再找node_modules/chalk.js
文件
3、如果没有,再找node_modules/chalk
文件夹
4、再找node_modules/chalk/package.json
文件
5、在package.json
文件里找main
属性,为该模块包的入口文件
6、如果package.json
中没有main
属性,则默认找node_modules/chalk/index.js
文件为入口文件(按照index.js/.json/.node
进行补全)。
7、如果还是找不到就会从当前目录往上找
"/Users/xiechen/Documents/code-personal/JSPlusPlus/日常学习/NodeJS基础篇/node_modules"
"/Users/xiechen/Documents/code-personal/JSPlusPlus/日常学习/node_modules"
"/Users/xiechen/Documents/code-personal/JSPlusPlus/node_modules"
"/Users/xiechen/Documents/code-personal/node_modules"
"/Users/xiechen/Documents/node_modules"
"/Users/xiechen/node_modules"
"/Users/node_modules"
"/node_modules"
7、如果还是找不到就会抛出异常