日常团队协作过程中,由于现状不得不使用相对滞后版本的依赖包,但又期望使用新版本的某些功能…


一、背景

通常情况下,遇到需要支持的底层功能,开发人员直接升级依赖包版本即可,但总有意外。比如:

  • 日常团队协作开发不同于个人独立开发,因为历史原因,不得不使用某个依赖包相对滞后的某个版本,但又需要新版本依赖包的某些功能。
  • 新版本包存在未知的bug,但你的团队期望使用新版本中的某些已验证功能。
  • 依赖包已经更新到最新版本,但存在官方未修复的bug

构建补丁(patch) 通常用于修复特定包中的问题,而不需要等待上游包发布新的版本。


二、patch-package打补丁

以下是一个典型的步骤:

1.生成补丁

I. 安装目标package & patch-package

# 安装目标package
$ pnpm add <package-name> # pnpm 安装需修补的包
$ pnpm list <package-name> # pnpm 查看包的安装位置
$ npm install <package-name> # npm 安装需修补的包

# 安装 patch-package 工具,以便管理和应用补丁
$ npm i patch-package --save-dev

II. 修改npm包
以本次需求为例,需要打补丁的包是@vant/cli 5.1.0,无论是新老版本都没有支持我需要的功能。所以,

  • 打开目标项目工程(A)/node_modules文件夹,确认版本是5.1.0
  • 稳妥起见,打开一个废弃的前端工程(B),安装@vant/cli 5.1.0(我这里偷懒使用的项目工程),打开node_modules/@vant/cli目录
    • 场景1: 将你期望的新版本对应功能,复制粘贴到当前工程(B)对应node_modules/xxx目录文件内
    • 场景2: 自行修复bug或diy你期望的功能,到当前工程(B)node_modules/xxx对应目录文件内
  • npm run dev启动工程(B),测试功能是否满足期望。

III. 生成补丁文件
功能符合预期,此时cd到工程(B)根木录下,执行如下命令生成补丁文件:

$ npx patch-package <package-name> # 示例: npx patch-package @vant/cli

终端可看到执行记录如下:

$ npx patch-package @vant/cli
patch-package 6.4.7
• Creating temporary folder
• Installing @vant/cli@5.1.0 with npm
• Diffing your files with clean files
✔ Created file patches/@vant+cli+5.1.0.patch

💡 @vant/cli is on GitHub! To draft an issue based on your patch run

    npx patch-package @vant/cli --create-issue

最终在工程(B)根目录下,生成工程(B)/patches/@vant+cli+5.1.0.patch补丁文件,将它提交到git中。

补丁原理也不复杂,其实就是一些git diff记录描述:
patch-package会将当前node_modules下的源码与原始源码进行git diff,并在项目根目录下生成一个patch补丁文件。以我当前的为例:

diff --git a/node_modules/@vant/cli/site/index.html b/node_modules/@vant/cli/site/index.html
index 5764eed..e32fb9e 100644
--- a/node_modules/@vant/cli/site/index.html
+++ b/node_modules/@vant/cli/site/index.html
@@ -33,5 +33,8 @@
   <body>
     <div id="app"></div>
     <script type="module" src="/desktop/main.js"></script>
+    <script>
+      window.localStorage.setItem("vantTheme", "light")
+    </script>
   </body>
 </html>
diff --git a/node_modules/@vant/cli/site/mobile.html b/node_modules/@vant/cli/site/mobile.html
index e5ff77b..084a028 100644
--- a/node_modules/@vant/cli/site/mobile.html
+++ b/node_modules/@vant/cli/site/mobile.html
@@ -42,5 +42,8 @@
   <body>
     <div id="app"></div>
     <script type="module" src="/mobile/main.js"></script>
+    <script>
+      window.localStorage.setItem("vantTheme", "light")
+    </script>
   </body>
 </html>

2.执行补丁

基于以上操作,切回到目标项目工程(A),在package.json中添加命令如下:

  • 安装patch-package工具
    # 安装patch-package工具,以便管理和应用补丁
    $ npm install --save-dev patch-package
    
  • 创建补丁文件夹:在项目根目录下新建一个名为patches的文件夹,用于存放补丁文件
  • 切回到目标项目工程(A),在package.json中添加命令
    {
       "scripts": {
         "postinstall": "patch-package" // 会在包安装完成后执行
       }
    }
    
  • 执行npm install命令:postinstall脚本确保每次执行pnpm installnpm install安装依赖后,自动应用存放在patches文件夹中的补丁
    $ npm i
    install 依赖包
    ...
    
    > @zto/mzui@3.1.1-beta.1 patch
    > patch-package
    patch-package 6.4.7
    Applying patches...
    @vant/cli@5.1.0 ✔
    ...
    patch-package finished.
    
  • 验证补丁效果:重新运行项目,检查补丁是否成功应用,并确保业务运行正常

三、其他方式:修复特定问题

想要单纯修改npm包还有其他方式

1.对于单文件

  • 拷贝覆盖法
  • 修改引用法

利用postinstall勾子,执行cp 修改过的文件 ./node_modules/包名/原始文件,最终node_modules下的文件被覆盖成修改后的文件:

{
  "scripts": {
    // 即每次install包后用修改后文件覆盖原始文件
    "postinstall": "cp ./patches/vant-cli/* ./node_modules/vant/cli/site"
  }
}

配置一个webpack alias别名,如**‘原始文件的引用路径’: ‘修改后文件的引用路径’**,使得最终修改后的文件被引用,如:

// vite 原理类似
resolve: {
      alias: {
          'antd/upload': path.resolve(__dirname, './patched/upload/*'),
      }
  }

2.多个文件/整体项目

  • 直接使用完成的源码,不再通过npm包方式引用
  • 修改后的源码,发布到私有的npm仓库上,供项目使用

四、补丁管理常见问题

  • patch-package优势: 使用git diff来记录补丁,比重写一份源码的方法更节省空间、安全和便捷
  • patch-package可在日常开发中优雅的解决鱼和熊掌不可兼得的难题。但问题出现时,最好还是从官方渠道寻求解决方案,如提issue并关注版本更新和bug修复情况,以便及时更新或者移除补丁

1.补丁包的日常管理和自动化管理

补丁包的日常管理: 一定要遵循 及时移除补丁 的原则,一旦官方package发布了修复版本,请立即移除旧的补丁,避免可能的未知影响。

如果你想实现自动化管理,请参考 cloud_notes 补丁的管理

2.首次构建失败

新增补丁在 “构建平台"首次构建时 可能会面临构建失败,可清除工作目录后重新尝试构建

3.某个package,存在多个版本的补丁包文件时,如何patch?

实际场景,描述如下:

  1. 如果存在@lianpf/umi的多个版本补丁包:如@lianpf+umi+1.6.0.patch@lianpf+umi+1.5.3.patch@lianpf+umi+1.5.0.patch
  2. 且以上补丁包都位于项目的 patches 目录下

那么在执行 "postinstall": "patch-package" 时,patch-package将如何处理这些补丁包?

以上例子中,假设项目中安装 @lianpf/umi 的某个版本(例如 1.5.3,则 结论如下:

  1. patch-package 将只尝试应用 @lianpf+umi+1.5.3.patch。即只会针对已安装版本,尝试对应版本的补丁。
  2. 如果不存在1.5.3版本的补丁,则不会应用任何补丁

所以,要 确保补丁文件与已安装的包版本相匹配 ,以避免潜在的错误和冲突。

补丁包匹配的原理和详细步骤如下(若不关注,则可直接忽略): 1.补丁扫描patch-package会扫描patches目录下的所有.patch文件,并识别出每个补丁文件的目标包名和版本号

2.对每个补丁文件,进行版本匹配patch-package会检查node_modules中是否存在与【补丁文件中指定的版本号】相匹配的【包】

3.补丁应用

  • 找到与补丁文件中指定的版本号相匹配的包: patch-package会尝试将该补丁应用到该包上
  • 没有找到完全匹配的版本号,但找到了一个可以“向下兼容”的较低版本:(例如,安装了@lianpf/umi@1.6.0但存在@lianpf+umi+1.5.3.patch),patch-package通常不会自动应用该补丁(因为补丁是为特定版本设计的,并且可能包含仅适用于该版本的更改)。但是,如果项目配置或patch-package 的某个特定版本允许这种“宽松”的匹配(这在标准行为中是不常见的),则可能会尝试应用补丁,但结果可能是不确定的。

4.处理多个补丁

  • 如果存在某个包的多个版本补丁文件,并且项目中安装了与这些版本之一相匹配的包,则 patch-package 将只尝试应用与已安装版本相匹配的补丁。

5.错误和警告

  • 指出补丁未被应用: 尝试应用一个与任何已安装包版本都不匹配的补丁,patch-package可能会记录一个错误或警告,指出补丁未被应用。
  • 错误记录: 尝试应用补丁时遇到任何问题(如文件冲突、语法错误等),patch-package会记录相应的错误或警告。

参考


最后, 希望大家早日实现:成为编程高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!