輕松掌握開源項(xiàng)目的二次開發(fā)技巧
共 6888字,需瀏覽 14分鐘
·
2024-04-30 12:00
大廠技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
本文作者:@方長_beezen
原文鏈接:https://juejin.cn/post/7358647992608489535
前言
隨著軟件行業(yè)的迅速發(fā)展,開源項(xiàng)目的重要性已經(jīng)成為不言而喻的事實(shí)。它能夠?yàn)殚_發(fā)人員節(jié)省大量時(shí)間和成本,避免重復(fù)開發(fā)已存在的功能。其次,開源項(xiàng)目經(jīng)過廣泛的社區(qū)審查和測(cè)試,具有較高的質(zhì)量,從而降低了開發(fā)風(fēng)險(xiǎn)。另外,龐大的社區(qū)支持和生態(tài)系統(tǒng)能夠提供及時(shí)的技術(shù)支持和解決方案。
然而,并非所有開源項(xiàng)目都能直接滿足企業(yè)的特定業(yè)務(wù)需求。在這種情況下,開發(fā)者需要對(duì)其進(jìn)行二次開發(fā),根據(jù)自身需求對(duì)特定功能進(jìn)行修改和優(yōu)化。傳統(tǒng)的二次開發(fā)模式是通過 Fork 源碼進(jìn)行的,然而這種方式存在一些弊端。一方面,對(duì)開發(fā)人員的專業(yè)能力要求較高;另一方面,容易使開發(fā)者與開源社區(qū)脫節(jié),后續(xù)的技術(shù)方案可能無法直接使用。
相比之下,漸進(jìn)式開發(fā)模式是一種新型的二次開發(fā)模式,它既能夠與社區(qū)保持緊密聯(lián)系,又能夠直接基于源碼進(jìn)行修改和優(yōu)化,是一種需要掌握的二次開發(fā)技巧。
開源項(xiàng)目的神秘面紗
Github 是全球最大的開源項(xiàng)目代碼托管平臺(tái),為開發(fā)者提供了協(xié)作,代碼管理和項(xiàng)目托管等服務(wù)。很多國內(nèi)外優(yōu)秀的開源項(xiàng)目都可以在 Github 上找到,例如 Vue、Ant Design、Taro、Nodejs、React、Bun、express.js、Webpack 和 Babel 等等。
這些優(yōu)秀的開源項(xiàng)目都有一些相同的特點(diǎn),社區(qū)活躍度高、項(xiàng)目透明和開放、使用場(chǎng)景較廣泛。從 Github 平臺(tái)數(shù)據(jù)上也能看到一些共同特點(diǎn),參考如下:
-
Star 數(shù)較高:代表項(xiàng)目受歡迎程度越高,項(xiàng)目質(zhì)量也可能更高。 -
貢獻(xiàn)者數(shù)量多:反映了項(xiàng)目的活躍程度和社區(qū)參與度較高,意味著項(xiàng)目的開發(fā)和維護(hù)工作得到了更多人的關(guān)注和參與。 -
提交頻率高:反映項(xiàng)目的更新速度和活躍程度,頻率越高,意味著項(xiàng)目的開發(fā)和維護(hù)工作越活躍。 -
問題和拉取多:反映項(xiàng)目的開發(fā)和維護(hù)工作活躍,也意味著項(xiàng)目功能特性增加較快。
然而,優(yōu)秀的開源項(xiàng)目也意味著項(xiàng)目工程的復(fù)雜度極高,面對(duì)來自全球各地開發(fā)者的代碼提交,如果沒有一個(gè)全面且穩(wěn)定的項(xiàng)目架構(gòu),是無法做好項(xiàng)目管理和發(fā)展的。所以對(duì)于開源項(xiàng)目的二次開發(fā),主要要面對(duì)如下這些難點(diǎn):
-
理解項(xiàng)目結(jié)構(gòu)和架構(gòu):對(duì)于復(fù)雜的開源項(xiàng)目,首先需要花費(fèi)大量的時(shí)間和精力去理解項(xiàng)目的結(jié)構(gòu)和架構(gòu),包括代碼組織方式、模塊之間的關(guān)系、依賴關(guān)系等,這對(duì)于新手來說可能是一個(gè)挑戰(zhàn)。 -
閱讀和理解源代碼:開源項(xiàng)目的源代碼可能包含大量的代碼和注釋,需要開發(fā)者具備良好的閱讀和理解能力,以便理解代碼的邏輯和功能,找到需要修改或擴(kuò)展的部分。 -
遵循項(xiàng)目規(guī)范和約定:開源項(xiàng)目通常有自己的代碼風(fēng)格、命名規(guī)范、提交規(guī)范等,開發(fā)者需要遵循這些規(guī)范和約定,以便保持代碼的一致性和可維護(hù)性。 -
處理依賴關(guān)系和兼容性:開源項(xiàng)目可能依賴于其他的開源庫或者框架,開發(fā)者需要處理好這些依賴關(guān)系,確保項(xiàng)目的穩(wěn)定性和兼容性。 -
與社區(qū)保持同步:二次開發(fā)并不是一次性的任務(wù),隨著項(xiàng)目的演進(jìn)和需求的變化,開發(fā)者需要不斷地維護(hù)和更新自己的代碼,保持與原始項(xiàng)目的同步和一致性
傳統(tǒng)的二次開發(fā)方式
通過 Fork 某個(gè)固定版本,并進(jìn)行后續(xù)的迭代開發(fā)。
操作步驟
1、在 Github 上 Fork 開源項(xiàng)目到自己的代碼倉庫。
2、在自己代碼倉庫中克隆已 Fork 的項(xiàng)目到本地。
3、從 develop 開發(fā)分支拉取 feat 特性分支進(jìn)行代碼修改,然后提交代碼到自己倉庫。
4、在 Github 上發(fā)起對(duì)開源項(xiàng)目的 Pull requests 請(qǐng)求。(可以對(duì)開源項(xiàng)目進(jìn)行貢獻(xiàn))。
5、然后就等開源項(xiàng)目維護(hù)者接受(merge)或者拒絕(close)你的請(qǐng)求了。
優(yōu)劣分析
優(yōu)點(diǎn):
-
穩(wěn)定性: 團(tuán)隊(duì)能夠完全掌握特定版本的代碼,避免因?yàn)樵?xiàng)目的更新引入新的問題。 -
可控性: 團(tuán)隊(duì)可以更自由地管理自己的代碼庫,不受原項(xiàng)目后續(xù)更新的干擾。 -
預(yù)測(cè)性: 團(tuán)隊(duì)能夠更好地預(yù)測(cè)和計(jì)劃開發(fā)進(jìn)度,因?yàn)榇a庫相對(duì)固定。
缺點(diǎn):
-
滯后性: 由于固定版本,可能錯(cuò)過原項(xiàng)目后續(xù)版本的新功能、性能優(yōu)化和安全修復(fù)。 -
維護(hù)成本: 長期來看,需要團(tuán)隊(duì)付出更多的維護(hù)成本,尤其是當(dāng)項(xiàng)目規(guī)模增大或持續(xù)時(shí)間較長時(shí)。
經(jīng)歷:
我們?cè)?Taro v1.3.21 版本進(jìn)行了 Fork,也修復(fù)了數(shù)百個(gè)功能點(diǎn)。我們的業(yè)務(wù)項(xiàng)目在此基礎(chǔ)上運(yùn)行了兩年多,編寫了大量兼容代碼以適配 Taro v1.3.21。然而,隨著時(shí)間推移,我們發(fā)現(xiàn)業(yè)務(wù)項(xiàng)目的維護(hù)變得越來越困難。
備注:
很多國內(nèi)大公司采用這種方式,投入大量人力對(duì)代碼進(jìn)行修改,從而將其轉(zhuǎn)變?yōu)橐粋€(gè)全新的項(xiàng)目或產(chǎn)品,并將其與原開源項(xiàng)目完全分開。他們之所以能夠成功,主要是通過投入大量資源深入研究開源項(xiàng)目,但對(duì)于小團(tuán)隊(duì)來說,這種任務(wù)通常是難以勝任的。
漸進(jìn)式開發(fā)模式
根據(jù)大量的實(shí)踐經(jīng)驗(yàn),純粹采用 Fork 固定版本進(jìn)行二次開發(fā)或通過引用的方式持續(xù)更新這兩種方式都不夠可行。我們通過長時(shí)間的探索和創(chuàng)新,發(fā)現(xiàn)將這兩種模式結(jié)合,并結(jié)合工程化手段,能夠更好地應(yīng)對(duì)二次開發(fā)的場(chǎng)景,我們稱這類開發(fā)模式為漸進(jìn)式開發(fā)模式。
基本架構(gòu)
首先,我們需要組件基礎(chǔ)框架團(tuán)隊(duì),開發(fā)人員的專業(yè)度要求較高,他們會(huì)持續(xù)對(duì)開源項(xiàng)目進(jìn)行調(diào)研,并選擇 Fork 一個(gè)穩(wěn)定的源碼版本(代號(hào) 1.0.0),并負(fù)責(zé)將其推廣至業(yè)務(wù)團(tuán)隊(duì)項(xiàng)目中。
業(yè)務(wù)團(tuán)隊(duì)在項(xiàng)目開發(fā)過程中,發(fā)現(xiàn)了 1.0.0 版本上的缺陷,基礎(chǔ)框架團(tuán)隊(duì)則負(fù)責(zé)在當(dāng)前版本的源碼上進(jìn)行問題修復(fù),并發(fā)布一個(gè)補(bǔ)丁包,暫且稱為 @xx/patch。然后,業(yè)務(wù)開發(fā)人員只需要在項(xiàng)目中添加一個(gè)補(bǔ)丁配置 @xx/patch,并重新安裝依賴,通過一種工程化手段就可以將補(bǔ)丁代碼生效于開源項(xiàng)目中。
另一方面,基礎(chǔ)框架團(tuán)隊(duì)會(huì)定期進(jìn)行開源項(xiàng)目的版本更新(比如每六個(gè)月進(jìn)行一次版本調(diào)研和升級(jí)),重新 Fork 一個(gè)較新的穩(wěn)定版本(代號(hào) 2.0.0),并推廣到業(yè)務(wù)團(tuán)隊(duì)項(xiàng)目中。對(duì)于業(yè)務(wù)團(tuán)隊(duì)而言,他們可以按照自己的項(xiàng)目規(guī)劃,有選擇性地考慮是否要對(duì)框架版本進(jìn)行升級(jí)。
在該模式中,有兩個(gè)關(guān)鍵點(diǎn)需要注意:
-
代碼修復(fù)與優(yōu)化策略 -
智能補(bǔ)丁模塊替換方案
代碼修復(fù)與優(yōu)化
開源項(xiàng)目的問題修復(fù)或新增需求,我們需要制定一定的管理策略:
-
當(dāng)前維護(hù)版本存在問題,但最新的開源版本已修復(fù)。此時(shí)只需對(duì)維護(hù)版本進(jìn)行補(bǔ)丁修復(fù)。 -
當(dāng)前維護(hù)版本和最新開源版本都存在問題。我們將對(duì)當(dāng)前維護(hù)版本進(jìn)行補(bǔ)丁修復(fù),并提交 Pull Request(PR)到開源社區(qū)。 -
對(duì)于新增需求,我們將僅在最新的開源版本上提交 PR。 -
每個(gè)需求都必須經(jīng)過內(nèi)部審核機(jī)制。
在這里,我們需要注意維護(hù)好補(bǔ)丁包與開源版本源碼的關(guān)系。建議補(bǔ)丁包的包名與源碼包模塊保持一致,例如:@tarojs/router:3.6.22 模塊存在問題,我們的補(bǔ)丁包模塊可以命名為 @xx/router:3.6.22-patch.1。如果后續(xù)還有繼續(xù)更新,可以遞增補(bǔ)丁號(hào),如 @xx/router:3.6.22-patch.2。這樣有助于清晰地管理和追蹤補(bǔ)丁包與開源版本的對(duì)應(yīng)關(guān)系。
智能補(bǔ)丁模塊替換
補(bǔ)丁模塊替換邏輯主要采用 yarn 包管理工具的 resolutions 能力,對(duì)于模塊下載方式,主要有如下三種:
-
從源鏡像下載模塊 -
在線資源模塊 -
本地文件模塊
示例如下:
"resolutions": {
"@tarojs/taro": "3.6.22",
"@tarojs/components": "http://patch.xxx.com/components-3.6.22.tgz",
"@tarojs/router": "file:./lib/router-3.6.22.tgz"
}
在這里,我們采用了本地文件加載的方式,以方便后續(xù)實(shí)現(xiàn)資源緩存能力。當(dāng)開發(fā)者在項(xiàng)目根目錄下執(zhí)行 yarn install 命令進(jìn)行依賴安裝時(shí),首先會(huì)觸發(fā) preinstall 勾子,提前進(jìn)行補(bǔ)丁包的資源下載,并將補(bǔ)丁配置信息植入到 package.json 文件中的 resolutions 字段。然后,在項(xiàng)目依賴安裝時(shí),將會(huì)把配置文件中定義的補(bǔ)丁資源安裝到指定的模塊中,而不會(huì)再拉取線上資源。這樣,我們成功實(shí)現(xiàn)了智能替換補(bǔ)丁模塊的能力。
package.json 配置文件:
// package.json
"scripts": {
"preinstall": "node scripts/preinstall.js"
},
"patch": {
"@xx/patch": "1.0.0"
},
preinstall.js 勾子文件:
// preinstall.js 勾子模塊
const http = require("http");
const fs = require("fs");
const path = require("path");
const pkg = require("../package.json");
const patchVersion = pkg.patch["@xx/patch"]; // 獲取 package.json 中關(guān)于補(bǔ)丁包相關(guān)信息
const serverUrl = `https://xx.patch.com?version=${patchVersion}`; // 補(bǔ)丁資源服務(wù)
// 發(fā)起 HTTP 請(qǐng)求
http.get(serverUrl, (res) => {
const filePath = path.join(__dirname, ".patch");
const fileStream = fs.createWriteStream(filePath);
res.pipe(fileStream);
// 處理請(qǐng)求完成事件
res.on("end", () => {
const patchConfig = require(".patch/config");
pkg.resolutions = patchConfig.resolutions;
// resolutions 配置內(nèi)容如下
// {
// "@tarojs/router": `file:${path.join(__dirname, ".patch/@xx/router-3.6.22-patch.1.tgz")}`
// }
fs.writeFileSync(
path.join(__dirname, "../package.json"),
JSON.stringify(pkg)
); // 重新寫入 package.json 配置文件
});
});
上述邏輯簡要說明了智能補(bǔ)丁的核心流程。我們可以將 preinstall 中的邏輯封裝到全局的 CLI 模塊中,也可以通過在依賴安裝完成后觸發(fā) postinstall 勾子來移除 package.json 文件中的 resolutions 配置。另外,提到的 serverUrl 補(bǔ)丁包下載服務(wù),我們不一定需要自己搭建服務(wù),可以通過 npm publish 方式將補(bǔ)丁包發(fā)布到 npm 鏡像源,然后通過 https://registry.npmmirror.com/@xx/router/-/router-3.6.22-patch.1.tgz 方式進(jìn)行下載。此外,我們還可以提供更多的配置參數(shù),以滿足更多定制化的需求。
最后
在進(jìn)行開源項(xiàng)目的二次開發(fā)過程中,我們還需要重點(diǎn)關(guān)注二次開發(fā)本身,從收集產(chǎn)品需求到驗(yàn)證項(xiàng)目質(zhì)量和性能,一直到最終的方案落地,每一個(gè)環(huán)節(jié)都很重要。
值得一提的是二次開發(fā)方式各自有優(yōu)劣,選擇取決于項(xiàng)目的需求、團(tuán)隊(duì)的開發(fā)流程和維護(hù)能力。
最后
Node 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點(diǎn)贊、在看” 支持一下
