記錄--如何修改第三方npm包?

林恒發表於2024-07-18

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

有這樣一種場景:如果你在開發過程中,發現某個npm包有Bug,應該怎麼辦?

第一選擇當然是向原作者提issue,或者Fork該倉庫程式碼,修改以後,提交合並請求。

但這種情況有個嚴重的問題,就是耗時,如果遇到嚴格的作者,或者不活躍的作者,時間線可能會拉得很長。你的專案未必能等這麼長時間。

還有一種可能是,你修改的程式碼並不具備普適性,只有你的業務場景能用到,合併被拒的機率會大大增加。

總而言之,如果能修改npm包的源包,再好不過,如果不行,則需要有個臨時方案,或者替代方案。

這時,又有下面兩種情況:

  1. 程式碼量少,可以直接修改npm包程式碼的,考慮補丁方案。
  2. 程式碼量多,或者npm包程式碼是壓縮混淆過的,不具備修改條件。修改原始碼後,再修改包名,重新發布,在應用程式碼中更換引用。為敘文方便,我將這種方案命名為換日方案(偷天換日,李代桃僵)。

下面,詳細介紹下這兩種不同方案。

補丁方案

patch-package

patch-package是一個用於修復第三方依賴包的工具,使用方式非常簡單。

它支援npm和yarn v1,如果是yarn v2+或者pnpm,則使用自帶的patch方案(下文會介紹pnpm方案)。

安裝:

$ npm i patch-package
$ yarn add patch-package postinstall-postinstall

如果只是前端使用,可以新增--dev或-D引數。如果是後端使用,為保障生產模式(會去除devDendencies依賴)也能正常使用,就不要加了。

在node_modules中找到你要修改的npm包,修改內容後,就可以執行patch-package建立patch檔案了。

$ npx patch-package package-name   # 使用npm
$ yarn patch-package package-name  # 使用yarn

執行後會在專案根目錄下建立一個patches資料夾,並生成一個名為package-name+version.patch的檔案。將該patch檔案提交至版本控制中,即可在之後應用該補丁了。

以我修改的verdaccio為例,會生成一個verdaccio+4.4.0.patch的檔案,內容大致如下:

diff --git a/node_modules/verdaccio/build/index.js b/node_modules/verdaccio/build/index.js
index 3a79eaa..d00974b 100644
--- a/node_modules/verdaccio/build/index.js
+++ b/node_modules/verdaccio/build/index.js
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
 });
 exports.default = void 0;
 
+console.log('---------------')
+
 var _bootstrap = require("./lib/bootstrap");

完成上述操作後,最後在package.jsonscripts中加入"postinstall": "patch-package"
"scripts": {
  "postinstall": "patch-package"
}

這樣當其他同事拉下程式碼,執行npm install或是yarn install命令時,便會自動為依賴包打上我們的補丁了。

簡單來說,這個方案的原理就是記錄補丁的程式碼與位置,利用npm的hook(postinstall會在npm install後觸發),在安裝完依賴以後,觸發相應的指令碼,將補丁覆蓋到node_modules對應的包裡。

當然,補丁是對應具體版本的,需要鎖定版本號。這樣的缺點是如果要升級的話,還得重新來一遍,不過不是有Bug或效能問題,通常不必追求新的版本。

pnpm patch

pnpm的patch自稱靈感來自yarn的類似命令。由於yarn v2可能走了邪路,我們就不介紹了。

首先,執行pnpm patch <pkg name>@<version>。該命令會將指定的軟體包提取到一個可以隨意編輯的臨時目錄中。

完成修改後, 執行pnpm patch-commit <path>( 是之前提取的臨時目錄,這個臨時目錄會長到你根本記不住,不過不用擔心,命令列裡會有完備的提示) 以生成一個補丁檔案,並提供patchedDependencies 欄位註冊到你的專案中。

比如,我想修改一個is-even的包:

pnpm patch is-even  
You can now edit the following folder: /private/var/folders/sq/0jfgh1js6cs8_31df82hx3jw0000gn/T/29ba74c7c7ffd7aa157831c6436d3738

Once you're done with your changes, run "pnpm patch-commit /private/var/folders/sq/0jfgh1js6cs8_31df82hx3jw0000gn/T/29ba74c7c7ffd7aa157831c6436d3738"
按照提示,開啟這個資料夾,加一行程式碼:

執行上面控制檯的提示:

pnpm patch-commit /private/var/folders/sq/0jfgh1js6cs8_31df82hx3jw0000gn/T/e103de90617a18eee7942d1df35a2c48
Packages: -1
-
Progress: resolved 5, reused 6, downloaded 0, added 1, done
這時你會發現package.json中多了一段內容:
"pnpm": {
    "patchedDependencies": {
      "is-even@1.0.0": "patches/is-even@1.0.0.patch"
    }
}
根目錄下,也多了個資料夾patches,開啟以後,你就能找到新增的程式碼:

開啟node_modules/is-even/index.js,可以看到已經多了我們新增的程式碼:

刪除node_modules,重新pnpm i安裝依賴,仍然與現在一樣,這就代表成功了。

整個流程下來,我們看得出來相比於patch-package,要稍微複雜點兒,但也是可以接受的。

注意:patches目錄是一定得提交到git的。

換日方案

上面說過,如果要修改的程式碼較多,或者不具備修改條件,這時就需要修改原始碼。 到GitHub上找到npm包的原始碼,Fork該專案,修改程式碼後,再修改包名,重新發布,比如你要修改的包是lodash,可以修改為awesome-lodash,在應用程式碼中更換引用。

本來這個方案沒什麼好說的,但有一種情況,如果你修改的是個底層包,也就是說並不是你的應用程式碼中直接引用的,而是你引用的npm包A所依賴的,甚至可能同時被包B依賴的,這時就比較尷尬了,你不可能再去修改A和B的原始碼,那就太不值當了。

pnpm提供了一種別名(Aliases)的能力。

假設你釋出了一個名為awesome-lodash的新包,並使用lodash作為別名來安裝它:

$ pnpm add lodash@npm:awesome-lodash

不需要更改程式碼,所有的lodash引用都被解析到了awesome-lodash。 就這麼簡單,上面說的問題就解決了。

再說點兒題外話,有時你會想要在專案中使用一個包的兩個不同版本,很簡單:

$ pnpm add lodash1@npm:lodash@1
$ pnpm add lodash2@npm:lodash@2

現在,您可以透過 require('lodash1') 引入第一個版本的 lodash 並透過 require('lodash2') 引入第二個。

與pnpm的鉤子結合使用功能會更加強大,比如你想將node_modules裡所有的lodash引用也替換為awesome-lodash,你可以用下面的.pnpmfile.cjs 輕鬆實現:

function readPackage(pkg) {
  if (pkg.dependencies && pkg.dependencies.lodash) {
    pkg.dependencies.lodash = 'npm:awesome-lodash@^1.0.0'
  }
  return pkg
}

module.exports = {
  hooks: {
    readPackage
  }
}

pnpm功能非常強大,後面我會再詳細寫篇文章介紹下。

總結

在開發過程中發現npm包的Bug,首先向原作者提交issue或Fork程式碼修改後提交合並請求。但遇到不活躍或拒絕修改的情況,專案等待時間會很長。這時可以使用補丁方案或換日方案進行解決。

補丁方案中,如果是npm或yarn v1,可以使用patch-package工具包處理;如果是yarn v2或pnpm,可以使用各自的patch命令。

換日方案,則是修改原始碼,釋出新的npm包後,利用pnpm的別名功能,將所有依賴原npm包的地方,全部替換為新的包。

這種場景在日常開發中還是比較常見的,這裡為大家提供一種思路。當然,如果真是個Bug,別忘了提issue或PR,為開源貢獻自己的一份力量,在與作者的溝通交流中,相信你也能受益匪淺。

本文轉載於:https://juejin.cn/post/7356534347509497919

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

記錄--如何修改第三方npm包?

相關文章