面试官又问我为什么要出现pnpm?

学习这个知识,就不不得不完整的学一学前端包管理器的发展史。

概述

包的概念:包含一些元数据:名称,描述,给git主页,许可证协议,作者,依赖等等。

背景

commonjs的出现,node环境有了模块化的概念,可以将代码拆分的特别细。这种很细的划分,是开发大型应用的基石。

开发中一些的常见的问题,就可以使用第三方库封装。 使用CommomJS标准书写,很方便对这些工具进行使用。

但是下载这些第三方库的时候,遇到很多问题:

  • 下载过程繁琐:找到地址,下载对应版本,粘贴到工程中,并且遇到同名的库,还得需要该名称

  • 如果该库依赖其他库,还要先下载其他的库。也可能下载的不同的库中,有相同的包。下载之后的库还得放到准确的位置。 还可能不同的库中依赖相同的库版本还不同

  • 依开发环境中的库如何再生产环境中还原(因为不可能将第三方库的全部上传,文件多,速度慢,还可能丢包)。 还有很多工具只会在开发环境中使用,如何区分这个库是否需要上传服务器。

  • 更新库麻烦

  • 自己开发的库,如何让别人使用,如何迭代。

前端包管理器

npm:node package manager。 node包管理器。运行在node环境中,解决上述的开发者使用包的各种问题。

前端所有的包管理器几乎都是基于npm。

为什么要在node环境?

浏览器无法进行操作本地文件,无法下载文件功能。 node属于服务器环境,没有浏览器的限制。

npm出现之后,node就内置了npm。

npm可以理解为很庞大的数据库,开发者将自己的库按照npm的规范,打包上传。使用者通过统一的地址下载第三方包。

npm提供了CLI命令。通过这些命令对npm的各种包操作。

包的安装

npm的服务器位于国外,网速会受影响。 所以需要淘宝镜像源。同步了海外的npm服务器。

npm安装一个包,分为两种安装方式: 本地安装,全局安装。

本地安装

在当前目录下,npm i

可以安装多个包 npm i react vue

安装完成之后,会放在node_modules文件夹下

node_modules文件越来越大,是不需要上传到git的, .gitignore中添加文件名后,再上传的时候会忽略其中的文件。

安装一个包,npm会分析依赖树,自动安装该包所依赖的其他包。

如果安装的包,带有自己的CLI命令。npm会将这些命令放在node_modules/.bin文件中。

使用npx可以执行这些命令。

全局安装

查看全局安装的目录:npm cpnfig get prefix

全局安装: npm install --global "包名" 或者 npm i -g '包名'。

全局安装 提供全局的CLI工具

什么时候全局安装?

频繁使用的命令工具。 并且这个工具只是在开发环境中支持。

包配置

现在遇到了几个问题:

node_module 是不能上传到服务器的, 那如何还原?

如何区分生产依赖和开发依赖?

如果自己也是一个包,该如何描述?

配置文件

包的描述需要固定名称的package.json文件

可以手动创建,但是一般情况下是使用 npm init 创建。

配置文件中有大量的信息:

  • name: 必须使用英文字符
  • version: 版本

版本规范: 主版本号.次版本号.补丁版本号

主版本号:当程序发生了巨大变化,新增了重大功能,新增了大量api或者技术架构发生了重大 变 化

次版本号:小范围更改,进行了局部的优化。

补丁版本号:仅解决了一些bug

  • description:描述

  • main:包的入口文件

  • repositort:包的仓库地址

  • keywords:搜索关键字

  • author:作者 必须是有效的npm的账户名 不正确会影响到发包

npm init -y 所有配置都是默认值,快速生成。

保存依赖关系

绝大多数的时候,我们并不是要发包,package.json最重要的作用是记录当前工程的依赖。

  • dependencice 生产环境中的依赖
  • devdependencice 开发依赖,仅开发环境中使用的依赖

安装依赖

css 复制代码
// 安装到生产环境的依赖
npm i 包名
npm i --save
npm i -v

// 安装到开发环境的依赖
npm i --save-dev 包名
npm i -D

配置好依赖,使用命令安装

less 复制代码
//如果后面不加报名,会下载package.json文件中所有依赖
npm install
npm i

// 仅安装生产环境中的依赖
npm i --production

这样就解决了两个问题 1还原node_modules的文件,2 上传代码之后, 服务器如何查找生产环境中所需要的依赖的问题。

npm会将安装的包进行扁平化(并不是一开始就这样,更新迭代出现的), 如果下载A包和B包,他们都依赖了C包。 ABC的目录结构时相同的。 如果C版本不同的话, 会在各自的包里嵌套。

包的使用

当使用nodejs导入模块时,如果模块路径不是以 ./ 或者../开头, node会认为该文件是来自node_modules目录中。

javascript 复制代码
var _ = require("lodash")

// 这个文件node是如何进行查找的呢?
首先去查找 node_modules/lodash.js
没找到就会找 node_nodules/lodash/入口文件

如果在子目录中导入,首先在当前文件中找。未找到就会向上级查找,直到最顶层(不是当前工程的顶层,而是电脑文件夹的顶层)未找到就会报错

什么是入口文件?

  • node_modules 中 package.json的main字段的值,作为入口文件

  • 如果没找到main字段,回会使用index.js

css 复制代码
var a = requie(./a)
// node如何查找的呢
// 查找a.js文件, 如果没有会把a当作文件夹,并且将它看作是一个包,查看是否有package.json的main路径。 如果没有就继续查找 a.js

语义化版本

版本规范: 主版本号.次版本号.补丁版本号

  • 主版本号:当程序发生了巨大变化,新增了重大功能,新增了大量api或者技术架构发生了重大变化
  • 次版本号:小范围更改,进行了局部的优化。
  • 补丁版本号:仅解决了一些bug

如果我自己编写了一个包, 依赖了其他包, 我们希望依赖的包保持固定版本。 也希望次版本号和补丁版本号可以提升。有时候也希望只有补丁版本号能够提升。

这时候我们就需要配置文件将这些依赖规则描述清楚。 这种规则的描述就是语义化版本

列几个常见的

  • 大于某版本

    • 介于两个版本之间 1.2.1 - 1.3.4
  • ~补丁版本号可以增加 ~1.3.4 保证了主版本号和次版本号, 补丁版本号大于4
  • ^主版本不可变。次版本号和补丁版本号可以增加

package-lock.json

如果依赖的某个包(a)使用了某个语义化版本,当再次安装的时候,可能会因为这个包(a)的小版本升级,导致这个包(a)所依赖的包(b)进行了大版本更新。 这就很可能出现问题。

这个文件记录了安装包时的确切依赖关系,最大限度的避免了版本差异。

(最早是yarn解决的,后来npm也解决了)

npm脚本script

在开发的过程中,我们可能会反复使用很多CLI命令。这些命令繁琐复杂,第三方包不同命令也会跟着不同。很难记忆和管理,于是npm通过package.json配置script字段后,就可以很轻松的运行各种指令。

npm run 脚本名称

为了简化, start test stop 这三个时可以省略 run。

脚本命令中可以省略npx。

start脚本如果不配置,有默认配置 node server.js

运行环境配置

我们一般遇到的环境有 生产环境, 开发环境, 测试环境 等等。

根据代码在什么环境中运行, 要进行不同的操作,那该如何使用呢?

node中有一个全局变量global(类似于浏览器的window)

global中有一个属性时process。 这个属性包含了当前运行node程序的计算机的很多信息,有一个属性时env,这个属性包含了所有的环境信息。

我们通常通过系统变量 NODE_ENV(约定成俗)配置具体的值,这样就能结局上述中判断环境的问题。

这个系统变量可以手动添加(永久设置);

也可以使用脚本在项目启动的时候临时添加变量,当项目结束的时候再删除(临时设置);

window临时设置变量 : set NODE_ENV= development

但是不同的环境有不同的命令,为了避免差异有第三方库:cross-env

命令行"cross-env NODE_ENV= development"

yarn

基于npm,仍然使用了npm的数据库,但是提供了全新的CLI对包进行管理。

yarn为什么这么火爆,因为yarn当时解决了npm的很多存在的问题

npm过去的问题是什么?

  • 依赖目录嵌套层次很深。 如果a和b同时依赖c包, 则会a包的node_module中安装c。b包的node_module中也安装c。 win系统无法支持很深的目录结构(256字符)
  • 下载速度慢。 前一个包下载完才会下载下一个包。并且多个重复的版本重复下载。
  • 过去控制台输出繁杂:过去npm安装依赖,输出依赖的详情信息,遇到报错极难排查。
  • 工程移植问题:npm版本依赖模糊,确切版本不能一致。 (锁文件)

yarn解决的问题

  • 使用了扁平结构,避免层次过深,避免了重复下载。
  • 并行下载,多个线程
  • 使用本地缓存。下载过的包会直接从本地拷贝
  • 控制台仅输出关键信息
  • yarn-lock文件记录确切依赖

优化

  • 语义化命令

  • 本身自带CLI命令的 可以直接使用yarn命令。

很快npm学习了yarn先进的理念,不断对自身进行优化,npm6几乎解决了上面的问题。

  • 扁平化

  • 并行下载

  • 本地缓存

  • package-lock记录依赖

  • 增加了命令别名

  • npx命令,启动本地的CLI工具

  • 简化控制台输出

npm6之后,和yarn非常接近,甚至没有差距。

nvm

nvm不是包管理器,是用于管理多个node版本的工具

nvm就是用来切换node版本的工具

pnpm

16年出现的包管理器。后起之秀。

npm和yarn缓存的问题

  • 是通过复制本地缓存的包到自己的工程。 如果多个工程,把缓存就复制了多个。
  • 拷贝本身也需要时间

使用

npm i -g pnpm

命令只需要将之前的npm 换成pnpm就可以。

npx --- pnpx 功能也是相同的,唯一的区别是

npx在执行一个不存在的命令时,会在本地临时安装一个命令,用完之后会删除。

pnpx也是相同,只是安装这个命令的时候会使用pnpm

优势:

  • 安装效率高()

  • node_modules目录简洁(只展示了所依赖的)

  • 避免开发使用间接依赖的问题

  • 极大降低磁盘的占用

原理

  1. 同样使用了缓存,缓存已经安装过的包,缓存放在当前磁盘的 pnpm-store中
  2. 和npm和yarn不同的点,pnpm使用软链接和硬链接的做法放置依赖,规避了拷贝的时间。使得安装和卸载的速度都变快。
  3. 如何做到node_modules只展示当前依赖呢? npm和yarn为了解决重复包和路径过长的问题,做了扁平结构的处理,而pnpm由于使用了符号链接和硬连接,解决了上面两个的问题。树结构本身就可以使依赖更清晰,也就可以恢复使用了。这样也解决了使用间接依赖的问题。

硬链接(指向同一个磁盘中的地址)

文件A -->内存地址

产生一个A的硬链接 B, B的文件也指向了A的内存地址。硬链接没有限制,同个文件可以产生多个硬链接.

如果A文件删除了, B还是可以存在的.

通过硬链接的方式不会产生额外的磁盘占用。

创建一个硬链接(只能给文件创建,不能给文件夹创建)

mklink /h 链接文件名称 目标文件

软连接

A-->内存地址, 创建A的软件链接B 此时B指向的不是A的内存地址,而是指向了A.

如果A文件删除了,B也就无效了.

创建一个软链接

为目录创建软连接 加上/d 如果时为文件创建软链接 不需要加

mklink /d 链接文件名称 目标文件

整理下这两者区别:

  • 硬链接只能用于文件不能用于目录,软链接可以.
  • 硬链接完成之后只和内容有关系,和链接的文件没有关系. 但是软连接只和连接到文件有关系,和文件内容没有直接关系
  • node中运行时,硬链接是无法识别的,. 运行软连接文件,是按照链接文件的路径执行.

pnpm安装依赖的时候分析步骤:

  • 要安装依赖A,通过查询依赖关系,.得到要安装的包是A和B
  • 查看A和B是否有缓存,如果没有就下载,如果有下一步
  • 创建node_nodules文件夹.开始结构化目录
  • 创建.pnpm,管理包的依赖关系, node是读不到的(这里指的是,不干扰node寻找文件的规则)
  • .pnpm中创建文件夹node_modules文件夹和.refisty.npm.taobao.org (镜像源命名) 真正保存包的文件夹. A包->版本名文件夹->node_modules->A包:创建缓存中A包的硬链接 ; 创建B包的软连接,(只会创建直接依赖)

为什么要创建这个node_modules文件, 因为A中文件,引入了B文件,为了保证A文件能找到B文件,按照node啊查找文件的方式,所以创建了这个文件夹.

  • node_modules为什么创建? 简单来说 向下兼容. 总有人创建包的时候 不规范,使用了间接依赖, 使用pnpm之后因为找不到就会报错. 所以间接依赖都存放再这个文件夹下.(使用符号链接)
  • .pnpm构建完成之后. 再创建我们项目中的依赖包的软连接.(这样就避免了使用简洁依赖)

pmpm会不会导致目录过长呢?

不会的, pnpm的层级是可以控制的.

因为到版本文件夹下, 就已经结束了..

bower

结论:已经被淘汰

浏览器模块化出现之前, 想解决浏览器端的包管理器

在浏览器端解决了包的问题.

Monorepo

monorepo是一种项目管理的策略模式, "单一代码仓库".

它解决了什么问题呢?

  • 你有没有多个项目再不同的仓库, 每个仓库的eslint ts 都是相同的配置,能不能放在一个仓库里一同配置呢?

  • 多个项目的存在相同的工具包,如何复用呢? 可能想过发布npm包.开发调试都很麻烦

  • 每个项目依赖版本都要各自维护,

解决了这些问题,也就是monorepo的优势了

  • 模块独立管理,代码复用方便,耦合度降低

  • 统一项目的构建部署流程,统一规范'

缺点:

  • 仓库庞大,clone慢
  • 权限不能够好的控制

如何搭建

使用pnpm ,为什么是pnpm ,?

  • 了解pnpm的都知道, 优点太多了.

  • pnpm提出了workspace, 使得包之间可以互相依赖, 不需要第三方支持, 天然支持monorepo

安装依赖

  • 安装全局依赖 pnpm i 包名 -w
  • 指定某个包安装依赖 pnpm add axios --filter
  • 模块间的依赖 pnpm --filter i -S 给name1的包添加依赖 name2(进入name保重添加) 如果是根目录pnpm install -r --filter
  • 给一个模块下添加公共模块: pnpm -F main-project add common
相关推荐
QTX1873011 分钟前
使用 Axios 进行 API 请求与接口封装
javascript·vue.js·node.js
9ilk21 分钟前
【前端基础】--- HTML
前端·html
Lafar23 分钟前
Dart单线程怎么保证UI运行流畅
前端·面试
不和乔治玩的佩奇29 分钟前
【 设计模式】常见前端设计模式
前端
bloxed35 分钟前
vue+vite 减缓首屏加载压力和性能优化
前端·vue.js·性能优化
打野赵怀真1 小时前
React Hooks 的优势和使用场景
前端·javascript
HaushoLin1 小时前
ERR_PNPM_DLX_NO_BIN No binaries found in tailwindcss
前端·vue.js·css3·html5
Lafar1 小时前
Widget 树和 Element 树和RenderObject树是一一 对应的吗
前端
似水流年QC1 小时前
什么是Lodash
javascript·lodash
小桥风满袖1 小时前
炸裂,前端神级动效库合集
前端·css