🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
有這樣一種場景:如果你在開發過程中,發現某個npm包有Bug,應該怎麼辦?
第一選擇當然是向原作者提issue,或者Fork該倉庫程式碼,修改以後,提交合並請求。
但這種情況有個嚴重的問題,就是耗時,如果遇到嚴格的作者,或者不活躍的作者,時間線可能會拉得很長。你的專案未必能等這麼長時間。
還有一種可能是,你修改的程式碼並不具備普適性,只有你的業務場景能用到,合併被拒的機率會大大增加。
總而言之,如果能修改npm包的源包,再好不過,如果不行,則需要有個臨時方案,或者替代方案。
這時,又有下面兩種情況:
- 程式碼量少,可以直接修改npm包程式碼的,考慮補丁方案。
- 程式碼量多,或者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.json
的scripts
中加入"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
"pnpm": { "patchedDependencies": { "is-even@1.0.0": "patches/is-even@1.0.0.patch" } }
開啟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
如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。