深入理解包管理工具

目录

引入

  • 随着前端技术的发展,项目依赖的第三方库和工具越来越多(例如:React、Vue、Lodash、Axios等),手动管理这些库的版本、兼容性和更新变得非常困难

  • 包管理工具不仅可以来安装、升级、删除我们的工具代码,也能让开发者能够更方便地分享代码,开发者可以将自己的模块发布到公共仓库(如 npm),其他开发者可以通过包管理工具简单地引入和使用这些模块

npm

Node Package Manager,也就是Node包管理器 ,目前已经不仅仅是Node包管理器了,在前端项目中我们也在使用它来管理依赖的包

  • npm属于node的一个管理工具,安装Node的过程会自动安装npm工具node管理工具:https://nodejs.org/en/

  • npm管理的包可以在 https://www.npmjs.org/ npm官网查看、搜索,只要能搜到就可以通过npm安装

  • npm管理的包都是发布到到registry上面的,安装一个包时也是是从registry上面下载的包

配置文件

那么对于一个项目来说,我们如何使用npm来管理这么多包呢?

  • 每一个项目都会有一个对应的配置文件package.json ,无论是前端项目(Vue、React)还是后端Node项目

  • 这个配置文件会记录着你项目的名称、版本号、项目描述

  • 也会记录项目所依赖的其他库的信息和依赖库的版本号

配置文件如何得到呢?

  • 手动从零创建项目,使用 npm init 在创建时填写信息

    js 复制代码
    // package.json
    {
      "name": "npm",
      "version": "1.0.0",
      "description": "npm包管理工具练习",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC"
    }
  • 手动从零创建项目,使用 npm init -y 在创建时所有信息使用默认的都是yes

    js 复制代码
    // package.json
    {
      "name": "npm",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC"
    }
  • 通过脚手架创建项目,脚手架会帮助我们生成package.json,并且里面有相关的配置

常见属性

  • name:是项目的名称,必须填写

  • version:是当前项目的版本号,必须填写

  • description:是描述信息,很多时候是作为项目的基本描述

  • author:是作者相关信息(发布时用到)

  • license:是开源协议(发布时用到)

  • private:记录当前的项目是否是私有的,当值为true时,npm是不能发布它的,这是防止私有项目或模块发布出去的方式

  • main:设置程序的入口文件,比如使用axios模块 const axios = require('axios'),如果有main属性,实际上是找到对应的main属性查找文件的

  • scripts:用于配置一些脚本命令,以键值对的形式存在

    • 配置后可以通过 npm run 命令的key 来执行这个命令

    • npm startnpm run start是等价的

    • 对于常用的 start、test、stop、restart 可以省略掉 run 直接通过 npm start 等方式运行

  • dependencies:是指定无论开发环境还是生成环境都需要依赖的包

    • 当你运行 npm install 时,dependencies 中的依赖会被安装

    • npm install --production,则只会安装 dependencies 中的依赖,而跳过 devDependencies

  • devDependencies:用于声明开发环境所需的依赖,这些依赖通常在生产环境中不需要

    • 运行 npm install 时,devDependencies 中的依赖会被安装

    • 通过 npm install webpack --save-dev,它会安装到 devDependencies 属性中

    • 常见的 devDependencies 包包括:

      1. 测试框架(如 Jest、Mocha

      2. 构建工具(如 Webpack、Rollup

      3. 代码检查工具(如 ESLint、Prettier

      4. 类型定义文件(如 @types/node

  • peerDependencies:用于定义某个包所需的外部依赖的版本

    • 告诉用户这个包需要与某个特定版本的依赖一起使用

    • 当一个包声明了 peerDependencies,npm 不会自动安装这些依赖,而是会在安装时发出警告,提醒用户需要手动安装合适的版本

    • peerDependencies 常用于插件、组件库和框架,比如element-plus是依赖于vue3的,ant design是依赖于react、react-dom

  • engines:用于指定NodeNPM的版本号

    • 在安装的过程中,会先检查对应的引擎版本,如果不符合就会报错
    • 也可以指定所在的操作系统 "os" : [ "darwin", "linux" ],只是很少用到
  • browserslist:用于配置打包后的JavaScript浏览器的兼容情况

    • webpack等打包工具服务的一个属性

    • 可以放在 package.json 中,或者在单独的 .browserslistrc 文件中定义

    • 可以使用多种方式定义支持的浏览器,例如:

      1. 指定具体版本:last 2 versions

      2. 指定市场份额:> 1%

      3. 指定浏览器类型:not dead(排除不再维护的浏览器)

版本理解

我们会发现安装的依赖版本出现:^2.0.3~2.0.3,这是什么意思呢?

  • npm的包通常需要遵从semver版本规范:

  • semver版本规范是X.Y.Z

    • X主版本号(major :当你做了不兼容的 API 修改(可能不兼容之前的版本)

    • Y次版本号(minor:当你做了向下兼容的功能性新增(新功能增加,但是兼容之前的版本)

    • Z修订号(patch :当你做了向下兼容的问题修正(没有新功能,修复了之前版本的bug

  • ^~ 的区别:

    • x.y.z:表示一个明确的版本号

    • ^x.y.z:表示x是保持不变的,y和z永远安装最新的版本

    • ~x.y.z:表示x和y保持不变的,z永远安装最新的版本

package-lock.json

package-lock.jsonnpm 在安装依赖时自动生成的文件,主要用于锁定项目的依赖版本

  • 记录了每个依赖的确切版本,包括直接依赖和间接依赖。这样可以确保在不同的环境中安装依赖时,版本一致

  • 当安装依赖时,npm 会优先使用 package-lock.json 中记录的信息,从而加快安装速度

package-lock.json 的示例结构如下:

js 复制代码
{
  "name": "your-project",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "dependencies": {
    "express": {
      "version": "4.17.1",
      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
      "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
      "dev": false,
      "requires": {
        "body-parser": "1.20.0"
      },
      "engines": {
        "node": ">= 0.10.0"
      },
      "dependencies": {
        "body-parser": {
          "version": "1.20.2",
          ...
        }
      }
    }
  }
}

相关属性:

  • name:项目的名称

  • version:项目的版本

  • lockfileVersionlock文件的版本

  • requires:使用requires来跟踪模块的依赖关系

  • dependencies:项目的依赖

    • 上面代码显示项目依赖 express,但 express 又依赖 body-parser

    • express中的属性如下:

      version:表示实际安装的express的版本

      resolved:用来记录下载的地址,registry仓库中的位置

      requires/dependencies:记录当前模块的依赖

      integrity:用来从缓存中获取索引,再通过索引去获取压缩包文件

npm install

安装npm包分两种情况:

  • 全局安装(global install): npm install XXX -g/global

    • 在某些系统(如 Linux 或 macOS)中,可能需要使用 sudo 来获取安装全局包的权限。例如:sudo npm install XX -g
    • 全局安装的包通常会自动配置环境变量,允许你在命令行中直接使用它们,可运行 npm config get prefix 来查看全局安装路径
  • 项目(局部)安装(local install): npm install XXX

    • 会在当前目录下生成一个 node_modules 文件夹

    • npm install/idependenciesdevDependencies 中的依赖会被安装

    • npm install/i --production ,则只会安装 dependencies 中的依赖,而跳过 devDependencies

    • npm install/i XXX :会把依赖安装到 dependencies 属性中

    • npm install/i XXX --save-dev || npm install/i XXX -D ,会把依赖安装到 devDependencies 属性中

那么 npm install/i 是什么原理呐?npm install会检测是否有package-lock.json文件

  • 没有lock文件:

    • 分析依赖关系,因为可能有的包会依赖其他包,并且多个包之间会产生相同依赖的情况

    • registry仓库中下载压缩包(如果设置了镜像,会从镜像服务器下载压缩包)

    • 获取到压缩包后会对压缩包进行缓存 (从npm5开始有的)

    • 压缩包解压到项目node_modules文件夹中

  • lock文件:

    • 检测lock中包的版本是否和package.json中一致 (会按照semver版本规范检测)

    • 不一致,那么会重新构建依赖关系,直接会走顶层的流程

    • 一致的情况下,会去优先查找缓存

    • 没有找到会从registry仓库下载,直接走顶层流程

    • 查找到会获取缓存中的压缩文件 ,并且将压缩文件解压到node_modules文件夹中

其他命令

  • npm uninstall XXX || npm uninstall XXX --save-dev || npm uninstall XXX -D:卸载某个依赖包

  • npm rebuild :强制重新build

  • npm config list :查看基本配置,后面增加参数-l能查看所有配置

  • npm config set registry https://registry.npm.taobao.org:设置下载地址,比如这里的淘宝镜像地址

  • npm --registry https://registry.npm.taobao.org install XXX:临时使用指定的下载地址

  • npm config set cache "D:\xxx\xxx\node_global":设置安装路径

  • npm config set prefix "D:\xxx\xxx\npm_cache":设置缓存路径

  • npm config get xxx:查看某个属性

  • npm cache clean --force:清除npm缓存

  • 更多的命令可以根据需要查阅官方文档:https://docs.npmjs.com/cli-documentation/cli

发布自己的包

  • 注册npm账号:在官网 https://www.npmjs.com/ 选择sign up

  • npm login:在命令行登录

  • 修改package.json

  • npm publish :发布到npm registry

  • 更新仓库:修改版本号(最好符合semver规范),重新npm publish

  • npm unpublish:删除发布的包

  • npm deprecate:让发布的包过期

yarn

yarn是由Facebook、Google、ExponentTilde 联合推出了一个新的 JS 包管理工具

  • 早期的npm存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题

  • yarn 是为了弥补早期 npm 的一些缺陷而出现的

  • 虽然从 npm5 版本开始,进行了很多的升级和改进,但是依然很多人喜欢使用yarn

  • 命令区别如下图:

cnpm

由于一些特殊的原因,某些情况下我们没办法很好的从 https://registry.npmjs.org 下载下来一些需要的包,这时我们有两种方式解决:

  • 可以直接设置npm的镜像

    • npm config get registry:查看镜像地址

    • npm config set registry https://registry.npm.taobao.org:设置淘宝镜像

  • 可以使用cnpm,并且将cnpm设置为淘宝的镜像

    • npm install -g cnpm -registry=https://registry.npm.taobao.org :安装 cnpm

    • cnpm config set registry https://registry.npm.taobao.org :设置 cnpm 镜像地址为淘宝地址

    • npm config get registry:查看镜像地址

npx

npxnpm5.2之后自带的一个命令npx 的作用非常多,但是比较常见的是使用它来调用项目中的某个模块的指令

  • 我们拿yarn做例子,我项目安装yarn版本是1.22.0,全局安装yarn版本1.22.22

  • 当我在项目目录的终端下执行 yarn -v 命令时打印的是全局安装的版本

  • 那么在项目中想要使用项目局部中的yarn时,有以下三种方式:

    • 方式一:在项目目录的终端执行 ./node_modules/.bin/yarn --version

    • 方式二:在项目目录的终端执行 npx yarn -vnpx的原理非常简单,它会到当前目录的node_modules/.bin目录下查找对应的命令

    • 方式三:package.json文件配置 "scripts": {"yarn": "yarn -version"} ,在执行npm run yarn

pnpm

pnpm :我们可以理解成是performant npm缩写,包括Vue在内的很多公司或者开源项目的包管理工具都切换到了pnpm

安装和使用

硬链接和软链接

我们都知道 pnpm 是很快速高效的,那么在它的内部是怎么做到的?首先先理解硬链接和软链接这两个概念

  • 硬链接(hard link

    • 是电脑文件系统中的多个文件平等地共享同一个文件存储单元

    • 指向文件在文件系统中的实际数据块

    • 不会创建新的文件,而是创建一个新的目录项指向同一数据块

    • 删除其中一个链接不会影响其他链接,文件内容只会在所有链接都被删除时才会消失

  • 软链接(soft link)也称符号链接(Symbolic link:快捷桌面就是软链接

    • 是一类特殊的文件,其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用

    • 也可以说是一个指向另一个文件或目录的路径,如果原文件被删除,软链接会失效成为"悬空链接"

我们来练习并理解一下这两个概念:vsCodecmd终端演练

  • copy copy.js ccopy.js :文件的拷贝,会在硬盘中复制出来一份新的文件数据,这两个文件数据互不影响

  • mklink /H hhard.js hard.js :文件的硬链接,hard.jshhard.js 平等地共享同一个文件数据单元,文件之间会互相影响

  • mklink ssoft.js soft.js :文件的软连接,文件之间会互相影响,打开软链接文件会打开原文件,删除原文件,软链接文件也就无效了

非扁平node_modules

非扁平化通常指的是在数据结构或存储方式中,保持层级或嵌套关系的状态,而不是将所有数据都放在同一层级上

那么为什么说pnpmnode_modules是非扁平的呐?下面以安装axios依赖为例理解一下:

  • 我们可以看到下图右边使用 npm i axios 安装依赖包,axios 和它的依赖包都将被提升到 node_modules 的根目录下,这样会造成可以访问本不属于当前项目所设定的依赖包

  • 而左边的pnpm使用的非扁平化管理,在node_modules中只有axios.pnpm文件夹,不会出现npm的问题

上图有的文件我们看到了软链接的符号,各个依赖的软硬链接关系图如下:

在上图中能非常清晰的看到依赖包都是从.pnpm store硬链接的,下面学习.pnpm-store,它是 pnpm 通过高效的缓存机制优化依赖管理的核心部分

存储store

.pnpm-storepnpm 的一个特定目录,用于存储依赖包的缓存 ,与其他包管理工具不同,pnpm 通过将所有的依赖包存储在一个全局的存储位置(即 .pnpm-store)中,而不是每个项目都重复下载和存储这些包

  • 它解决了使用 npmYarn 时,有超多项目且所有项目都有一个相同的依赖包,在硬盘上就需要保存超多份该相同依赖包的副本的问题

  • 如果项目中使用.pnpm-store中存在的依赖包版本,pnpm 会创建一个软链接,将该依赖指向 .pnpm-store 中的缓存目录,而不是重新下载

  • 虽然使用了软链接(符号链接),但这些链接指向的是存储在 .pnpm-store 中的特定版本文件 ,修改这些依赖的文件不会影响原始的版本文件,因为 pnpm.pnpm-store 中保持每个版本的独立副本

  • 如果使用的版本不在 .pnpm-store 中,pnpm 会自动下载该版本并存储到 .pnpm-store,以便将来使用

  • 不同版本之间有相同的文件,pnpm 也会通过硬链接的方式将相同的文件链接到不同的版本上,而不是重复存储

  • 只有在版本之间存在文件差异时,pnpm 才会存储这些不同的文件,确保了磁盘空间的高效利用

.pnpm-score的存储位置如下:

  • pnpm7.0之前,统一的存储位置是 ~/.pnpm-score中的

  • pnpm7.0之后,统一的存储位置进行了更改 <pnpm home directory>/store

    • Linux 上,默认是 ~/.local/share/pnpm/store

    • Windows 上: C:\Users\XXX\AppData\Local\pnpm\store ,可以看到它存储的是编码后的文件

    • macOS 上: ~/Library/pnpm/store

  • pnpm store path :获取当前活跃的store目录

  • pnpm store prune :从store中删除当前未被引用的包来释放store的空间

相关推荐
云泽8081 分钟前
C++ list容器模拟实现:迭代器、构造与STL风格编程
开发语言·c++·list
LFly_ice2 分钟前
Next-1-启动!
开发语言·前端·javascript
小时前端4 分钟前
谁说 AI 历史会话必须存后端?IndexedDB方案完美翻盘
前端·agent·indexeddb
2201_757830877 分钟前
条件分页查询
java·开发语言
wordbaby9 分钟前
TanStack Router 基于文件的路由
前端
wordbaby14 分钟前
TanStack Router 路由概念
前端
努力学习的小廉15 分钟前
【QT(六)】—— 常用控件(三)
开发语言·qt
wordbaby16 分钟前
TanStack Router 路由匹配
前端
cc蒲公英17 分钟前
vue nextTick和setTimeout区别
前端·javascript·vue.js
Z.yping21 分钟前
qt语言家一键更新或发布多个模块且多个国家的语言
开发语言·qt·restful