如何保存和应用第三方依赖包的改动?

前言

在我们项目开发时,使用的第三方依赖难免会存在问题,这时候给包作者反馈问题或提 PR 不一定能及时被修复,而且我们有可能需要基于它做定制,就需要维护一个改动的版本。

第一种方式是 fork 依赖包的源码仓库,自己修改完新发布一个 npm 包,改用我们自己发的包,这种方式适合需要定制或大量改动时采用;

另一种方式直接修改 node_modules 下的依赖包代码,但 node_modules 下的改动会被 git 忽略,需要将改动保存起来,可以使用 patch-package 这个包,或者包管理器的 cli 命令 yarn patch ( yarn v2+ )、pnpm patch ,这种方式适合小改动;

本文将学习如何保存和应用 node_modules 下的代码改动,并通过调试源码了解 patch-package 原理。

patch-package 使用

命令格式:npx patch-package <package-name>

PS:npx 命令可以让我们不用主动安装包就可以执行相应命令,它会先从本地项目依赖中判断是否已安装该包,未安装则会从远程拉取到 npm 缓存目录中,然后添加到系统环境变量 PATH 中。

例如对 node_modules 下的包 vue-countup-v3 做了修改,将 duration prop 默认值改为 5,如下图:

  1. 在终端执行 npx patch-package vue-countup-v3 ,会创建一个 patches 目录,里面是改动的包文件,文件名格式 包名+版本号.patch ,文件内容就是改动的前后内容,是不是跟 git diff 结果一样,其实就是依赖 git diff 实现的。
  2. 当别人拉取项目需要应用改动时可以运行 npx patch-package ,或者可以配到 scripts 中的 postinstall,这样每次安装完依赖就会自动执行该命令(需要在项目中安装下该包)。

包管理器 patch 命令使用

如果使用 yarn(v2+) 或者 pnpm 作为包管理器,则可以直接使用 patch 命令来保存改动,而不用 patch-package 工具。

pnpm patch 使用示例(yarn patch 用法类似),假如我们要改动包 vue-countup-v3

  1. 在项目根目录终端运行 pnpm patch vue-countup-v3 ,会创建一个临时目录,进到临时目录去修改包。 duration prop 改为 5
  2. 修改完之后,运行 pnpm patch-commit <path> (path 就是临时路径),会创建一个 patches 目录,里面是改动的包文件,文件名格式 包名+版本号.patch (跟 patch-package 一样),并且还会在 package.json 中新增一个字段 "patchedDependencies"。 当别人拉取项目执行 pnpm install 就会应用改动。当需要删除改动恢复原样,可以执行 pnpm patch-remove vue-countup-v3@1.4.0 (需要指定版本号)。

patch-package 原理

调试源码准备

  1. 克隆仓库源码git clone https://github.com/ds300/patch-package.git ,并安装依赖 yarn install

  2. 从 package.json 的 bin 字段可以看到 patch-package 命令的入口是 index.js 文件,

    而 index.js 中引入了 dist/index.js 文件,因此我们需要先执行构建命令 yarn build 来生成 dist;

    从生成的产物中,可以看到每个文件是带有 sourcemap 的,以 base64 内联方式在文件末尾,是通过 tsconfig.json 的 inlineSourceMap 配置生成的。

  3. 添加调试配置;在 launch.json 中添加一个 type 为 node 的调试配置,program 设置为 dist/index.js (即 patch-package 命令的入口文件),通过 args 传参(即填写改动过的包名),这里我随便找了一个包 ajv,往它里面新增了一个 add.js 文件;

    json 复制代码
    // launch.json
      {
        "version": "0.2.0",
        "configurations": [
          {
            "name": "调试 patch-package 生成 patch 文件",
            "program": "${workspaceFolder}/dist/index.js",
            "request": "launch",
            "skipFiles": ["<node_internals>/**"],
            "console": "integratedTerminal",
            "args": ["ajv"],
            "type": "node"
          }
        ]
      }
  4. 启动调试;源码的入口文件是 src/index.ts,我们可以通过执行命令时控制台打印的信息来找查打断点的位置。

要了解 patch-package 的实现原理可以从使用方面入手,当执行 npx patch-package <package-name> 用于保存改动生成 patch 文件;执行 npx patch-package 可以应用改动到 node_modules 中的相应包。

保存改动生成 patch 文件

执行 npx patch-package <package-name> 用于保存改动生成 patch 文件;

从控制台打印的信息可以看到大致步骤:

  1. 创建一个临时目录;
  2. 安装运行 patch-package 命令时指定的包;
  3. git diff 包改动前后的内容;
  4. 创建相应的 patch 文件。

主要看 makePatch 函数,进到该函数中看具体实现;

我们看主要流程的每一个步骤,其他的一些高级功能和选项可以忽略;

  1. 创建一个临时目录;通过第三方包 tmpdirSync 方法同步创建了一个临时目录,然后创建一个 package.json 文件;

  2. 安装执行 patch-package 命令时指定的包;推断项目的包管理器(yarn 或 npm,不支持 pnpm),然后在临时目录中执行依赖安装命令;

  3. git diff 包前后改动的内容;先执行 git initaddcommit ,生成一次 commit;

    然后把当前项目 node_modules 下对应包文件复制到临时目录的 node_modules 中,再执行 git add,然后执行 git diff 得到改动内容;

  4. 创建相应的 patches 目录及 patch 文件,将 git diff 的结果写入到 patch 文件中。

patch 文件应用改动到相应包

执行 npx patch-package 可以应用改动到 node_modules 中的相应包。

再添加一个调试配置用于调试应用改动

json 复制代码
  // launch.json
  {
      "name": "调试 patch-package 应用改动",
      "program": "${workspaceFolder}/dist/index.js",
      "request": "launch",
      "skipFiles": ["<node_internals>/**"],
      "console": "integratedTerminal",
      "type": "node"
  }

从打印出的信息找到执行的函数是 applyPatchesForApp,进到该函数中看具体实现;

通过调试的 step into 可以看到函数的调用过程

发现在 readPatch 函数中会读取生成的 patch 文件,然后传递给 parsePatchFile 函数

parsePatchFile 中会对 patch 文件内容一行一行进行判断,根据 diff 内容判断是什么操作进行分类处理,生成一个包含 diff 信息的对象。

然后执行 executeEffects 函数,在其中处理 parse 得到的 diff 信息对象进行处理;

在 executeEffects 中,会根据不同的类型进行不同的处理,例如改动后删除了文件,那么 type 就是 "file deletion" ,就会通过 fs.unlinkSync 删除文件;这样就把 patches 文件里的改动内容应用到相应的包里面。

总结

当需要保存对安装的依赖包代码改动时,可以通过 patch-package 这个工具,执行 npx patch-package <package-name> 来保存改动生成 patch 文件,执行 npx patch-package 来应用改动;它的实现原理可以从使用角度通过调试源码来理解:

保存改动生成 patch 文件:首先会创建一个临时目录及 package.json 文件,执行依赖安装,再初始化一个 git 仓库,先提交一个 commit,然后把改动的包代码复制到临时目录,执行 git diff 得到改动内容,保存到 patch 文件中;

patch 文件应用改动到相应包:首先会去读取 patch 文件,一行行解析文件内容,根据不同的改动类型将解析结果分类,然后再根据不同类型做不同的文件操作。

除了使用 patch-package,也可以使用包管理器的 cli 命令 yarn patch ( yarn v2+ ) 和 pnpm patch

相关推荐
M_emory_4 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito8 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员41 分钟前
响应式网页设计--html
前端·html
fighting ~44 分钟前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录1 小时前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
老码沉思录1 小时前
React Native 全栈开发实战班 - 状态管理入门(Context API)
javascript·react native·react.js
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript