如何封裝不被嫌棄的組件SDK
你在一家小互聯(lián)網(wǎng)公司做前端。最近公司發(fā)展勢(shì)頭不錯(cuò),已經(jīng)有了穩(wěn)定的商業(yè)模式。老板決定嘗試付費(fèi)推廣。
馬上五一了,老板想策劃一個(gè)活動(dòng)玩法??墒枪厩岸巳肆τ邢?,不能每個(gè)業(yè)務(wù)都單獨(dú)開(kāi)發(fā)活動(dòng)。
于是老板找到了你,希望你封裝一個(gè)活動(dòng)SDK組件供公司幾個(gè)業(yè)務(wù)接入。
你心里嘀咕:平時(shí)組件寫的倒是很多,也寫過(guò)公共組件,活動(dòng)組件感覺(jué)就是帶業(yè)務(wù)邏輯的公共組件,應(yīng)該沒(méi)啥難度吧?
但是你心里沒(méi)底,怕自己封裝的組件SDK被接入的業(yè)務(wù)方嫌棄,就去請(qǐng)教公司最資深(發(fā)量最少)的前端老卡。
待說(shuō)明來(lái)意,老卡深深啄了一口保溫杯里的菊花枸杞茶。
“這封裝組件SDK的門道啊,分為組件設(shè)計(jì)、開(kāi)發(fā)、接入、上線,待我一一道來(lái)”。
組件設(shè)計(jì)好的組件設(shè)計(jì)需要做到「職責(zé)明確」。在設(shè)計(jì)階段需要與3個(gè)角色明確職責(zé):
與提供數(shù)據(jù)的服務(wù)端明確職責(zé)
活動(dòng)內(nèi)部需要的數(shù)據(jù)通常由服務(wù)端提供,此時(shí)需要明確字段的粒度。
比如:邀請(qǐng)新用戶得xxx元獎(jiǎng)勵(lì)
xxx是變量,通常會(huì)作為一個(gè)字段。
那么「邀請(qǐng)新用戶得 元獎(jiǎng)勵(lì)」這段文案呢?活動(dòng)進(jìn)程中,有沒(méi)有可能PM發(fā)現(xiàn)這段文案效果不好想修改。
如果前端寫死了文案,要修改意味著組件提供方(你)與業(yè)務(wù)接入方都有重新上線的成本。
所以,如果評(píng)估有修改的可能,更好的方式可能是將這段文案下發(fā)為類似結(jié)構(gòu):
- data: {
- title: "邀請(qǐng)新用戶得{{bonus}}元獎(jiǎng)勵(lì)",
- params: {
- bonus: 123
- }
- }
與業(yè)務(wù)接入方明確職責(zé)
為了讓活動(dòng)SDK組件輕量,SDK內(nèi)使用的能力(比如:數(shù)據(jù)請(qǐng)求、登錄、錯(cuò)誤監(jiān)控)通常由宿主環(huán)境(接入組件的業(yè)務(wù))提供。
這類能力分為兩類:
- 運(yùn)行時(shí)業(yè)務(wù)方能提供的方法
- 業(yè)務(wù)方依賴的庫(kù)提供的能力
其中「運(yùn)行時(shí)方法」可以作為props傳給SDK組件,比如登錄方法。
庫(kù)的能力,SDK需要將其定義為peerDependencies,比如React、ReactDOM。
- React技術(shù)棧需要確定SDK使用的React版本和業(yè)務(wù)使用的React版本需要同時(shí)在v16.14之前或之后,以防JSX被編譯為不同結(jié)果(_jsx.createElement與React.createElement)
與PM敲定活動(dòng)流程
這一步和產(chǎn)品撕過(guò)的朋友都懂。
組件開(kāi)發(fā)
完成了職責(zé)劃分,產(chǎn)出技術(shù)文檔,接下來(lái)就能開(kāi)始「組件開(kāi)發(fā)」了。
此時(shí)有兩點(diǎn)需要注意:
完善的類型提示
使用ts編寫組件,導(dǎo)出類型聲明文件,可以極大規(guī)范業(yè)務(wù)方接入,減少接入溝通成本。
錯(cuò)誤邊界
如果SDK組件拋出錯(cuò)誤,導(dǎo)致接入的頁(yè)面崩潰了,妥妥的p0級(jí)bug。
所以,一定要將SDK的錯(cuò)誤catch在組件內(nèi)部。
對(duì)于React組件,用ErrorBoundary包裹是必不可少的。
業(yè)務(wù)接入
SDK組件終于開(kāi)發(fā)完了,發(fā)布到公司內(nèi)部npm平臺(tái)。
業(yè)務(wù)方將SDK以npm包的形式引入。
此時(shí)需要考慮如下問(wèn)題:
業(yè)務(wù)接入方以什么模塊規(guī)范導(dǎo)入(ESM還是CJS)?
如果接入方以SSR的形式在服務(wù)端接入組件,可能使用CJS規(guī)范。
CSR的情況通常使用ESM。
所以SDK組件在打包編譯時(shí)需要輸出ESM、CJS兩種規(guī)范的文件。
如果以ESM導(dǎo)出,需要考慮業(yè)務(wù)方treeShaking的需要
如果SDK會(huì)導(dǎo)出幾個(gè)組件(比如同一個(gè)活動(dòng)組件對(duì)不同業(yè)務(wù)輸出不同版本):
- // index.tsx
- export { default as Base } from './components/Base';
- export { default as SDKForA } from './components/SDKForA';
- export { default as SDKForB } from './components/SDKForB';
- export { default as SDKForC } from './components/SDKForC';
就需要考慮業(yè)務(wù)方的treeShaking需要。
當(dāng)前業(yè)界比較通用的方式是:將不同組件編譯到不同目錄,業(yè)務(wù)方通過(guò)組件目錄的形式引用,比如:
- // 業(yè)務(wù)方代碼
- import SDKForA from 'SDK/dist/modern/components/SDKForA';
其中SDK為活動(dòng)組件導(dǎo)出的npm包。
dist為編譯后產(chǎn)物打包的目錄。
modern為ESM規(guī)范的打包路徑,如果要引入CJS的包,可以將modern改為node。
SDKForA為要引入的組件。
如果業(yè)務(wù)方使用了babel-plugin-import,以上寫法可以用如下寫法替代:
- // 業(yè)務(wù)方代碼
- import { SDKForA } from 'SDK';
除了js文件以外,還要考慮業(yè)務(wù)方對(duì)css文件的編譯需要。
所以組件樣式文件最好與組件分離,比如將如下路徑:
- - components
- - SDKForA
- - index.tsx
- - style.less
其中index.tsx內(nèi)引入了style.less
修改為:
- - components
- - SDKForA
- - index.tsx
- - styles
- - SDKForA
- - style.less
- - style.css
index.tsx不引入樣式文件,由業(yè)務(wù)方單獨(dú)引入。
樣式產(chǎn)出.css與.less兩種格式,當(dāng)業(yè)務(wù)方需要對(duì)樣式有進(jìn)一步編譯需求,可以引入.less,否則直接引入.css。
業(yè)務(wù)方在接入時(shí),可以這樣接入:
- // 業(yè)務(wù)方代碼
- import SDKForA from 'SDK/dist/modern/components/SDKForA';
- import 'SDK/dist/modern/styles/SDKForA/style.less';
上線
上線后前端就解放啦?NO!
刺激的事兒可都發(fā)生在線上~
隨著用戶量級(jí)提升,發(fā)生各種bug的概率也隨之提升,主要包括:
- 接口異常
- 靜態(tài)資源加載失敗
- 各種奇奇怪怪的宿主環(huán)境造成的報(bào)錯(cuò)
- 關(guān)鍵活動(dòng)進(jìn)程的異常
這就需要做好線上監(jiān)控預(yù)警。
如果業(yè)務(wù)方引入了sentry,活動(dòng)SDK可以為以上case埋點(diǎn),并建立對(duì)應(yīng)監(jiān)控看板。
當(dāng)錯(cuò)誤指標(biāo)超過(guò)閾值,可以隨時(shí)從被窩里爬起來(lái)排查問(wèn)題。
除了代碼的埋點(diǎn),業(yè)務(wù)埋點(diǎn)也很重要。某位不知名互聯(lián)網(wǎng)人說(shuō)過(guò):
我知道我做的活動(dòng)會(huì)被薅羊毛,但我不知道究竟有多少羊毛被薅了
業(yè)務(wù)埋點(diǎn)能讓你知道。
總結(jié)
為了封裝一個(gè)不被吐槽的SDK組件,需要做到如下幾點(diǎn):
明確組件職責(zé),知道SDK能從宿主環(huán)境獲得什么能力
完善的ts聲明與錯(cuò)誤邊界
靈活的導(dǎo)出產(chǎn)物,讓業(yè)務(wù)能舒服接入
上線后業(yè)務(wù)、代碼層面的監(jiān)控
說(shuō)完這些,老卡又啄了一口保溫杯里的菊花枸杞茶,才發(fā)現(xiàn):
茶,早已涼透。