基於 Egg.js 的 Typescript 開發約定

有一種說法是 9102 年作 Node 後臺開發還不上 Typescript 不是太懶惰就是太菜了,其實假如你使用 Egg.js, 他們已經對基於 ts 開發作了許多支持,在項目裏引入 ts 並無那麼困難,且對減小開發過程當中的低級錯誤很是有幫助。
在這裏記錄一下我的在 Egg.js 項目裏引入 ts 的一些約定,其實都在官方文檔、文章和 Github issue 裏了。javascript

從 JS 遷移到 TS

web 開發不是瀑布流,定了需求閉門開發幾個月上線,每每是基於線上運行的 js 項目,要改形成基於 ts,前提是不能中斷服務,初步設想有如下三種方案:html

  1. 逐步替換文件,.js 和 .ts 後綴文件並存,經過 .d.ts 文件來標明類型獲取提示;
  2. 分紅 js base 和 ts base 兩個項目,逐步替換接口,新接口完成並經過測試後,流量切換到新項目;
  3. 直接作項目全量文件的 ts 化,利用 any 類型先完成遷移,後期再逐步以【用到了就重構】的原則重構全部代碼。

第一種方式在定義 .d.ts 文件時很是繁瑣且會有一些坑,如很難推斷函數返回類型,且在使用一些 es 原生方法時覆蓋類型會有坑;前端

第二種方式須要實現一種流量切換的方式,如加入 haproxy 中間層,經過 header 或是 path 等方式來識別新老接口流量。碰到的主要問題是在新老項目一個服務每每要改動多個公共文件,很難保持同步,會有矛盾發生;java

第三種方式是最後採用的方式,從 js 代碼中切出一個新分支,寫了腳本批量替換文件後綴,花了大約 2-3 天完成遷移,期間在舊 js 代碼中添加的新功能,須要確認在新 js 代碼中也實現一遍。
過程當中也是發現了一些坑,舉例:node

  • ['dev', 'stage'].includes(item), 若 item 有可能爲 undefined,則代碼會報錯,這裏就不像 js 那麼靈活
  • parseInt 只接受 string 參數,而以前有時爲了確保獲得的必定是數字,會往這個函數裏傳入 number 類型參數,ts 對這種狀況會報錯。值得注意的是,因爲 any 類型可賦值給全部類型,因此若是 ts 推斷傳入參數是 any 類型,parseInt(any) 是不會報錯的。這也是爲何有人推薦在 tsconfig.json 中打開 noImplicitAny 選項的緣由。保證 parseInt(item) !== NaN 這種邏輯,仍是應該手動實現,不能徹底依賴於 ts 的判斷;
  • Object.entries, array.map 等 es 內置方法有本身的類型定義,有可能會致使你傳入的參數類型丟失(降級),如 Object.entries 函數原型定義裏,`entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];

entries(o: {}): [string, any][];` 假如傳入的參數類型是某種字符串字面量,這裏變到 s 以後類型就成了 stringgit

  • 在改造過程當中不免處於簡化需求把 array 類型設爲 any[], 這時再 map,裏面的參數也會跟着變成 any 類型,想從裏面的參數中取值賦值,有時就會發生報錯。從快速遷移的目的上來說,能夠靈活使用 any + // 來臨時忽略報錯。

基於 Egg.js 開發的約定

參考 該文章github

編譯開發

在本地開發時,使用 egg-bin dev 命令啓動應用,egg-bin 自帶 ts-node,能夠直接運行 ts 代碼,無需編譯過程;web

發佈到線上時,egg 官方建議是仍是使用 Node + js 原生代碼,使用 egg-script start 啓動,因此須要將 ts 代碼編譯成 js 的過程。docker

在後端開發時能夠主動升級 Node 版本,使用一些先進的 es 語法新特性,不像前端爲了編譯成瀏覽器廣泛能兼容的 es5 代碼會使用不少 polyfill 的技巧,因此 ts 代碼編譯成 js 後其實並無太難懂(tsconfig.json 裏 compilerOptions => target -> es2017)。儘管如此使用編譯後的 js 代碼仍是須要映射回 ts 代碼,以便線上報錯時能有比較清晰的提示,ts 採用的方案是 sourceMap (tsconfig.json => compilerOptions =>inlineSourceMap -> true)。
關於 sourceMap 的原理能夠參考阮一峯的這篇文章typescript

編譯產物位置

egg-bin 在加載文件時,若是識別到同目錄下的 .ts 文件和 .js 文件,會優先加載 .js 文件。

真實環境下,爲了不把 .ts .js 文件同時託管在 git 上,形成混亂,最好是引入 ci 層完成編譯的工做,這時就可使用單獨的 dist 目錄來託管編譯後的 js 代碼,無需上述 egg-ts-helper 操做,設置 tsconfig.json => compilerOptions => outDirs -> ./dist 便可。

內置對象類型推斷

egg 擴展了 koa 的application、context 對象,掛載了 controller、service、model 等對象在上面,然而你本身定義的 controller 文件 ts 沒法感知他們和 egg 的關係,因此 egg 提供了 egg-ts-helper 工具來完成來自動生成 .d.ts 文件,放在 typings 目錄。參考文章

因爲 model 目錄並非 egg 默認約定會使用的目錄,只是大多數開發者的一個通識,因此針對 model 目錄 egg-ts-helper 並不會主動生成 .d.ts 文件,你須要主動提供 tshelper.js 文件,利用 egg-ts-helper 提供的 api 來 watch model 目錄。

啓用 egg-ts-helper 的方式很簡單,老的方式是 egg-bin dev -r egg-ts-helper/register 新的方式是設置 package.json => egg => declarations -> true

tsconfig.json
  • resolveJsonModule => ts 默認沒法加載 json 模塊,須要開啓此選項
  • paths => 該選項是爲了簡化在代碼中引用一些模塊時,須要注意相對路徑的問題,配合 baseUrl 選項便可比較方便的使用一些模塊,詳情參考文檔。實際使用有一些注意事項,參考 egg 官方的提示

發佈流程

線上使用 ci 負責編譯工做,ci 服務器須要下載所有依賴(包括 dependencies 和 devDependencies),獲得編譯產物後,再發布到 staging 服務器,staging 服務器只運行 npm install --production, 打包成 docker 鏡像再發布

相關文章
相關標籤/搜索