Protobuf to Typescript Definition

源碼github地址在此,記得點星:brandonxiang/pb-to-typescript
playground頁面歡迎試玩:https://brandonxiang.github.io/pb-to-typescript/example/

Why

深入 ProtoBuf - 簡介》一文介紹了Protobuf是一種有別于JSON 或 XML文本傳輸的新型二進制數據格式,具有安全性好,傳輸效率高的特點。

Protobuf文件(簡稱pb文件)在go語言的微服務中被廣泛使用,實際開發的情景中,后端同事會丟給我們一個pb文件,也就是接口定義的參數,讓前端同事按照結構體傳參。這份pb往往成為前后端溝通的橋梁,所以是非常重要的。

定義一個最簡單的結構體,通常如下,message 關鍵字后跟上消息名稱,結構體內部則是字段和其數據類型。

message student  {
  string name = 1;
  int32 age=2
}

作為前端,你可能會想起js對象或者typescript的類型定義文件。ts類型定義文件在接口聯調的過程中能夠提高開發效率,有效縮減聯調時間,提高代碼質量。回想一下,你是不是有抱怨過后端給你的字段,在沒有數據的時候,有可能是undefined,null,空數組,空字符串,這些都不算惡劣,還有"null"的字符串。這樣直接讓你的代碼里面充滿了各種奇怪的判斷邏輯,這種“臟代碼”直接降低你的代碼可讀性,嚴重的還會帶給你bug。

var student = {
  name: 'Peter',
  age: 18
}
interface student {
  name: string;
  age: number;
}

這時候聰明的同學就會想到,能不能讓protobuf直接轉換成為typescript的類型定義文件。前端同事在聯調的時候直接對請求函數進行類型限制,也可以根據請求返回參數的數據結構mock假數據。這種想法直接形成了有效的工作流程,給頁面交互聯調提高效率。

How

怎么去實現protobuf到typescript的轉換,首先受到開源工具geotho/protobuf-to-typescript的啟發,但是它的源碼非常簡單,原理不過是字符串替換,非常容易出現報錯的情況。所以我需要一個標準的解釋器能夠精確將protobuf文件轉化為抽象語法樹(AST)。這時候,我閱讀了@grpc/proto-loader的源碼,它依賴于開源庫protobuf.js。但是protobuf.js所提供的命令行工具(命令如下)不能滿足我們的需求,它的轉換結果更多是服務于nodejs,應用于服務端工作。

pbjs -t static-module file1.proto file2.proto | pbts -o bundle.d.ts -

現在我們的目標很明確,如何將protobuf的內容轉換為我們想要的typescript類型文件。首先,引入protobufjs,使用 protobuf.parse(source, { keepCase: true })將pb進行解析(keepCase是保護參數大小寫的選項),得到的是官方提供的protobuf的js對象,可以直接針對它進行解析,或者將它進行json序列化toJSON()

Type (T) Extends Type-specific properties
ReflectionObject options
Namespace ReflectionObject nested
Root Namespace nested
Type Namespace fields
Enum ReflectionObject values
Field ReflectionObject rule, type, id
MapField Field keyType
OneOf ReflectionObject oneof (array of field names)
Service Namespace methods
Method ReflectionObject type, requestType, responseType, requestStream, responseStream

以上是protobuf.js解析出來的數據類型,因為目標主要是面向前端使用的typescript文件,所以我們主要需要針對FieldEnumMethod這三種數據類型進行typescript轉換。

Field

Field主要指的就是protobuf中的message,它可以轉換為ts里面的interface。其中PB類型和TS類型存在一個轉換關系。這里存在一個問題即是int64超出js的number類型,如果強制轉換則會丟失精度,默認我們將它轉換為string。

repeated關鍵字則是數組的一個表現。

Field type Expected JS type (create, encode)
s-/u-/int32 s-/fixed32 number
s-/u-/int64 s-/fixed64 number或者string
float double number
bool boolean
string string
bytes string
Enum

枚舉是Protobuf類型也是Typescript類型,只需做簡單轉換即可,但是Protobuf中的Enum有可能存在于message當中,而此時,它并非接口參數實現,需要注意。

Method

rpc服務里面的一個方法即是一個method,一般里面包含兩個message,分別是入參和出參。

 syntax = "proto3";
  service MyService {
      rpc MyMethod (MyRequest) returns (MyResponse);
  }
  
  message MyRequest {
      string path = 1;
  }
  
  message MyResponse {
      int32 status = 1;
  }

同樣,我們需要把Method轉換為typescript中的interface,如下,這是因為請求一般返回的是Promise對象。

interface MyMethod {
  (params: MyRequest): Promise<MyResponse>;
}

類型文件可以應用于你自行封裝的request方法,此時代碼的入參出參則被有效限制。

const myMethod: MyMethod = (params) => {
  return request('/my_method', params)
}

通過以上說的這些轉換,已經可以滿足對請求函數的入參出參的typescript類型限制,有效地提高和后臺開發這聯調的效率。

What

功能實現后,可能有人向我推薦easy-mock。首先,easy-mock暫時沒支持protobuf。它更多是一個大而全的系統,功能很齊全,但是同樣也帶來一些問題----接口維護成本。“mock平臺需要誰來維護?”這個問題成為最大的阻礙。接口參數應該是由后端工程師定義,但是他們改了參數往往忘了維護在mock平臺上。如果交給前端維護,有時候難免會導致通知不到位,溝通成本上升的情況。因此,我更崇尚于“小而美”的工具。

pb-to-typescript它即可以是運行在nodejs上,也可以跑在瀏覽器上。更簡單的是你可以直接打開頁面https://brandonxiang.github.io/pb-to-typescript/example,直接將protobuf文件復制進行轉換,右方輸出的則是類型定義文件。這里可以輸出d.ts或typescript文件。

d.ts是類型文件,它的優點在于自動引入。但是重復的名稱會帶來變量污染,生產的類型文件是沒法管控重名的情況,開發者需要自行增加namespace。我個人更推薦ts文件,通過importexport解決作用域的問題。

Mock

有同學肯定還是會煩惱于mock的問題。正因為我們是從“小而美”的角度觸發,我們只需要mock返回參數即可。

在example頁面中同樣提供了這個功能,將proto文件整個復制進去,點擊需要mock的方法名,mock的結果會根據typescript的類型進行簡單的mock處理,基本滿足頁面UI的編寫。以上面栗子為例,mock數據可以直接通過Promise.resolve填入request方法中,這樣保證了類型檢查通過,而且返回了一個標準的Promise對象。

const myMethod: MyMethod = (params) => {
  //return request('/my_method', params)
  return Promise.resolve({
    "status": 10
  });
}

Conclusion

pb-to-typescript解決的問題是接口聯調中的一個痛點,就是接口還沒好,后端同事只提供你一個proto文件。前端同事可以通過proto的內容轉換為ts的類型定義并簡單mock返回數據,將UI和交互工作前置,提高工作效率,早點下班。

祝大家新的一年不用加班。

題外話

shopee,又稱蝦皮,是一家騰訊投資的跨境電商平臺。這里加班少,技術氛圍好。如果想和我并肩作戰一起學習,可以找我內推。郵箱weiping.xiang@shopee.com,非誠勿擾。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。