SakuraSnow's blog SakuraSnow's blog
首页
  • JavaScript
  • TypeScript
  • Vue
  • React
  • Git
  • Node
  • Linux
  • 技术文档
  • 博客搭建
  • 数据结构
  • leetcode
  • 关于
  • 友链
  • 收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

SakuraSnow

一只前端咸鱼
首页
  • JavaScript
  • TypeScript
  • Vue
  • React
  • Git
  • Node
  • Linux
  • 技术文档
  • 博客搭建
  • 数据结构
  • leetcode
  • 关于
  • 友链
  • 收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node

    • package-manager
      • 前言
      • 为什么要有包管理器
        • 一些基本概念
        • 包管理器的背景
        • 包管理器
      • npm
        • 包的安装
        • 包的配置文件
        • 包的语义版本
        • npm脚本
        • 其他常用的npm命令
      • yarn
        • 为什么要有yarn
        • yarn核心命令
        • yarn的其他命令
      • FAQ
  • 后端
  • Node
SakuraSnow
2021-01-28

package-manager

# 前言

这是一篇包管理器的基本知识的汇总笔记

# 为什么要有包管理器

# 一些基本概念

  • 模块(module)

    通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块或主模块

  • 库(library,简称lib) 以一个或多个模块组成的完整功能块,为开发中某一方面的问题提供完整的解决方案

  • 包(package) 包含元数据的库,这些元数据包括:名称、描述、git主页、许可证协议、作者、依赖等等

# 包管理器的背景

随着CommonJS 的出现,node环境下的JS代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。

为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等等,一时间,在前端社区涌现了大量的第三方库。这些库使用 CommonJS 标准书写而成,非常容易使用。

然而,在下载使用这些第三方库的时候,遇到了非常多的问题

  • 下载过程繁琐
    • 进入官网或 github 主页
    • 找到并下载相应的版本
    • 拷贝到工程的目录中
    • 如果遇到有同名的库,需要更改名称
  • 如果该库需要依赖其他库,还需要按照要求先下载其他库
  • 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
  • 更新一个库的过程也非常繁琐
  • 自己开发的库,要是想在下个项目中使用,也比较繁琐

包管理器的出现就是为了解决这个问题

# 包管理器

npm官网:https://www.npmjs.com/

最常见的包管理器有下面几个

  • npm
  • yarn
  • cnpm

但是,几乎前端所有的包管理器都是基于 npm 的,目前,npm 不仅是一个包管理器,也是其他包管理的基石

npm 全称为 node package manager,即 node 包管理器,它运行在 node 环境中,让开发者可以用简单的方式完成包的查找、安装、更新、卸载、上传等操作

npm 的出现,很快弥补了 node 没有包管理器的缺陷。于是不久后,node 在安装文件中内置了 npm,当开发者安装好 node 之后,就自动安装了 npm,不仅如此,node 环境还专门为 npm 提供了良好的支持,使用 npm 下载的包更加方便了。

# npm

# 包的安装

# 本地安装

使用命令npm install 包名或npm i 包名即可完成本地安装,本地安装的包出现在当前目录下的node_modules目录中

本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用

另外,安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到node_modules目录中 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到node_modules/.bin下,使用命令npx 命令名即可调用

对比一下

# 安装一个包,不保存到package.json里
npm install axios
# 会把依赖包名称添加到 package.json文件dependencies键下
npm install axios --save
# 添加到 package.json文件 devDependencies键下
npm install axios --save-dev
# 安装package.json里的全部包
npm install
1
2
3
4
5
6
7
8

# 全局安装

使用命令npm install --global 包名 或 npm i -g 包名就可以进行全局安装了

全局安装的包放置在一个特殊的全局目录,该目录可以通过命令npm config get prefix查看

全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具

大部分情况下,都不需要全局安装包,除非:

  1. 包的版本非常稳定,很少有大的更新
  2. 提供的 CLI 工具在各个工程中使用的非常频繁
  3. CLI 工具仅为开发环境提供支持,而非部署环境

# 包的配置文件

包的配置文件解决了下面几个问题

  1. 拷贝工程后如何还原?
  2. 如何区分开发依赖和生产依赖?
  3. 如果自身的项目也是一个包,如何描述包的信息

npm 将每个使用 npm 的工程本身都看作是一个包,包的信息需要通过一个名称固定的配置文件来描述,配置文件的固定名称为:package.json

你可以手动创建该文件,但是更多时候,我们使用下面的命令创建

npm init
1

配置文件中可以描述大量的信息,包括:

  • name:包的名称,该名称必须是英文单词字符,支持连接符
  • version:版本
    • 版本规范:主版本号.次版本号.补丁版本号(例如12.4.6)
    • 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的API、技术架构发生了重大变化
    • 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的API
    • 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
  • description:包的描述
  • homepage:官网地址
  • author:包的作者,必须是有效的 npm 账户名,书写规范是 account <mail>,例如:zhangsan <zhangsan@gmail.com>,不正确的账号和邮箱可能导致发布包时失败
  • repository:包的仓储地址,通常指 git 或 svn 的地址,它是一个对象
    • type:仓储类型,git 或 svn
    • url:地址
  • main:包的入口文件,使用包的人默认从该入口文件导入包的内容
  • keywords: 搜索关键字,发布包后,可以通过该数组中的关键字搜索到包

使用npm init --yes或npm init -y可以在生成配置文件时自动填充默认配置

但是实际上,package.json文件最重要的作用,是记录当前工程的依赖,下面两个属性记录了当前工程的依赖

  • dependencies:生产环境的依赖包
  • devDependencies:仅开发环境的依赖包

相关命令如下

## 安装依赖到生产环境(保存到dependencies)
npm i 包名
npm i --save 包名
npm i -S 包名

## 安装依赖到开发环境(保存到devDependencies)
npm i --save-dev 包名
npm i -D 包名
1
2
3
4
5
6
7
8

配置好依赖后,使用下面的命令即可安装依赖

## 本地安装所有依赖 dependencies + devDependencies
npm install
npm i

## 仅安装生产环境的依赖 dependencies
npm install --production
1
2
3
4
5
6

这样一来,在代码移植时,只需要移植源代码和package.json文件,然后重新安装,就可以在其他地方运行了(所以把代码发给别人时,不要把node_module也发过去了orz)

# 包的语义版本

有这样的场景:如果你编写了一个包A,依赖另外一个包B,你在编写代码时,包B的版本是3.4.1,你是希望使用你包的人一定要安装包B,并且是3.4.1版本,还是希望他可以安装更高的版本,如果你希望它安装更高的版本,高的什么程度呢?

  1. 有的时候,我们希望:安装我的依赖包的时候,次版本号和补丁版本号是可以有提升的,但是主版本号不能变化
  2. 有的时候,我们又希望:安装我的依赖包的时候,只有补丁版本号可以提升,其他都不能提升
  3. 有时我们甚至希望依赖包保持固定的版本,尽管这比较少见

这样一来,就需要在配置文件中描述清楚具体的依赖规则,而不是直接写上版本号那么简单。

我们可以用语义版本来描述包

符号 描述 示例 示例描述
> 大于某个版本 >1.2.1 大于1.2.1版本
>= 大于等于某个版本 >=1.2.1 大于等于1.2.1版本
< 小于某个版本 <1.2.1 小于1.2.1版本
<= 小于等于某个版本 <=1.2.1 小于等于1.2.1版本
- 介于两个版本之间 1.2.1 - 1.4.5 介于1.2.1和1.4.5之间
x 不固定的版本号 1.3.x 只要保证主版本号是1,次版本号是3即可
~ 补丁版本号可增 ~1.3.4 保证主版本号是1,次版本号是3,补丁版本号大于等于4
^ 次版本和补丁版本可增 ^1.3.4 保证主版本号是1,次版本号可以大于等于3,补丁版本号可以大于等于4
* 最新版本 * 始终安装最新版本

版本依赖控制始终是一个两难的问题

如果允许版本增加,可以让依赖包的bug得以修复,甚至可以带来一些性能提升,但同样可能带来不确定的风险(新的bug)

如果不允许版本增加,可以获得最好的稳定性,但失去了依赖包自我优化的能力

而有的时候情况更加复杂,如果依赖包升级后,依赖也发生了变化,会有更多不确定的情况出现

基于此,npm 在安装包的时候,会自动生成一个 package-lock.json 文件,该文件记录了安装包时的确切依赖关系

当移植工程时,如果移植了 package-lock.json 文件,恢复安装时,会按照 package-lock.json 文件中的确切依赖进行安装,最大限度的避免了差异

# npm脚本

在开发的过程中,我们可能会反复使用很多的 CLI 命令

这些命令可能很长,难以记忆,所以npm设置了脚本,只需要在 package.json 中配置 scripts 字段,即可配置各种脚本名称

我们用react脚手架创建的项目的package.json来演示

{
  "name": "react-ts-test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    // ...
  },
  // 脚本
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这些脚本的运行方式是

npm run 脚本名称
1

另外,npm 还对某些常用的脚本名称进行了简化,下面的脚本名称是不需要使用run的:

  • start
  • stop
  • test

所以你可以使用下面的命令,不用加run

npm start
1

最后,脚本中可以省略npx

比如有下面的脚本

{
  "name": "webpacktest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
  }
}
1
2
3
4
5
6
7
8
9

如果不写在脚本里,需要在命令行里运行npx webpack才能使用这个脚本

# 其他常用的npm命令

# 安装

安装指定版本的包

# 比如 npm install less@4.1.0
npm install 包名@版本号
1
2

# 查询

  1. 查询包安装路径
npm root [-g]
1
  1. 查看包信息
## 你可以使用下面的单词来代替view:v info show
npm view 包名 [子信息]
1
2
  1. 查询安装包
## 你可以使用下面的单词来代替list: ls  la  ll
npm list [-g] [--depth=依赖深度]
1
2

# 更新

  1. 检查有哪些包需要更新
npm outdated
1
  1. 更新包
## 你可以使用下面的单词来代替update :up、upgrade
npm update [-g] [包名]
1
2

# 卸载

## 你可以使用下面的单词来代替uninstall: remove, rm, r, un, unlink
npm uninstall [-g] 包名
1
2

# yarn

# 为什么要有yarn

yarn 是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具,它仍然使用 npm 的registry,不过提供了新的CLI 来对包进行管理

之所以会出现这种情况,是因为在过去,npm 存在下面的问题:

  • 依赖目录嵌套层次深:过去,npm 的依赖是嵌套的,这在 windows 系统上是一个极大的问题,因为windows 系统无法支持太深的目录(虽然npm后面也把依赖改成了扁平化的,但是当时没有改)
  • 下载速度慢
    • 因为npm对包的下载是串行的,即前一个包下载完后才会下载下一个包,导致带宽资源没有完全利用
    • 因为下载没有缓存,多个相同版本的包会被重复的下载
  • 无法定位错误位置:过去,npm 安装包的时候,每安装一个依赖,就会输出依赖的详细信息,导致一次安装有大量的信息输出到控制台。如果中途某个时候,一个包抛出了一个错误,但是npm会继续下载和安装包。因为npm会把所有的日志输出到终端,有关错误包的错误信息就会在一大堆npm打印的警告中丢失掉,并且你甚至永远不会注意到实际发生的错误。
  • 工程移植问题:由于 npm 的版本依赖可以是模糊的(因为npm使用的是语义版本),可能会导致工程移植后,依赖的确切版本不一致(npm5后才有package-lock.json)。

相对的,yarn就有下面的优势

  • node_module目录扁平化

  • 下载速度快

    • 并行下载
    • 本地缓存
  • 控制台仅输出关键信息

  • 使用了yanr-lock文件来记录依赖的确切版本

另外,yarn还优化了下面的内容

  • 增加了一些功能强大的命令
  • 让现有的命令更加语义化(比如把npm install react --save改为yarn add react)
  • 更加方便的yarn run 命令,它不仅仅会自动查看 package.json 中 scripts 下面的内容,还是查找 node_modules/.bin 下的可执行文件。也就是说,本地安装的CLI工具可以使用 yarn 直接启动

yarn 的出现给 npm 带来了巨大的压力,很快,npm 学习了 yarn 先进的理念,不断的对自身进行优化,到了目前的npm6版本,几乎完全解决了上面的问题:

在npm6版本,npm已经做了下面的优化

  • 目录扁平化
  • 并行下载
  • 本地缓存
  • 使用package-lock记录确切依赖
  • 增加了大量的命令别名
  • 内置了npx,可以启动本地的CLI工具
  • 极大的简化了控制台输出

npm6 之后,可以说npm已经和yarn非常接近,甚至没有差距了。

# yarn核心命令

# 初始化

yarn init [--yes/-y]
1

# 安装

添加新的包

yarn [global] add package-name [--dev/-D] [--exact/-E]
1

安装package.json中的所有依赖

yarn install [--production/--prod]
1

# 运行脚本和本地cli工具

使用下面的命令

# 运行脚本
yarn run 脚本名
# 运行本地安装的cli
yarn run cli名
1
2
3
4

cli名指的是node_module/.bin目录下的脚本名

image-20210128202659228

# 查询

查看bin目录

yarn [global] bin
1

查询包信息

yarn info 包名 [子字段]
1

列举已安装的依赖

yarn [global] list [--depth=依赖深度]
1

yarn的list命令和npm的list不同,yarn输出的信息更加丰富,包括顶级目录结构、每个包的依赖版本号

# 更新

列举需要更新的包

yarn outdated
1

更新包

yarn [global] upgrade [包名]
1

# 卸载

yarn remove 包名
1

# yarn的其他命令

在终端命令上,yarn不仅仅是对npm的命令做了一个改名,还增加了一些原本没有的命令,这些命令在某些时候使用起来非常方便

# yarn check

使用yarn check命令,可以验证package.json文件的依赖记录和lock文件是否一致

这对于防止篡改非常有用

# yarn audit

使用yarn audit命令,可以检查本地安装的包有哪些已知漏洞,以表格的形式列出,漏洞级别分为以下几种:

  • INFO:信息级别

  • LOW: 低级别

  • MODERATE:中级别

  • HIGH:高级别

  • CRITICAL:关键级别

# yarn why

使用yarn why 包名命令,可以在控制台打印出为什么安装了这个包,哪些包会用到它

# yarn create

这个命令在使用一些脚手架时特别方便

过去,我们都是使用如下的做法:

  1. 全局安装脚手架工具
  2. 使用全局命令搭建脚手架

由于大部分脚手架工具都是以create-xxx的方式命名的,比如react的官方脚手架名称为create-react-app

因此,可以使用yarn create命令来一步完成安装和搭建

例如:

yarn create react-app my-app
# 等同于下面的两条命令
yarn global add create-react-app
create-react-app my-app
1
2
3
4

# FAQ

npm中的依赖包分类 (opens new window)

npm 依赖管理中被忽略的那些细节 (opens new window)

依赖共享和冲突 (opens new window)

#包管理器
上次更新: 2022/03/05, 15:57:30
最近更新
01
009-Palindrome Number[回文数]
03-10
02
008-String to Integer (atoi)[字符串转整数]
03-10
03
004-Reverse-integer[整数反转]
03-09
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×