Cesium中文網:http://cesiumcn.org/ | 國內快速訪問:http://cesium.coinidea.com/
在當前的1.70版本中,CesiumJS現在附帶了正式的TypeScript類型定義!
TypeScript定義是一個長期以來被要求的特性。雖然社區已經完成了一項支持各種手動方式的工作,其中最受歡迎的是@types/cesium,但是cesium代碼庫的龐大規模和不斷發展的特性使得手工維護成為一項永無止境的任務。官方定義文件Cesium.d.ts的數據量超過42000行,達1.9MB。
即使您不是TypeScript用戶,此工作的性質也提高了CesiumJS API參考文檔的正確性和完整性,並在IDE中實現了更好的intellisense支持,可以將TypeScript定義應用於推斷類型,從而使整個CesiumJS社區獲得了巨大的成功。
更新CesiumJS到1.70將自動利用TypeScript應用程序中的類型檢查。我們使用package.json中的types字段,在大多數情況下不需要額外的配置。但是,如果直接導入單個Cesium源文件,則需要將“types”:[“cesium”]添加到tsconfig.json配置以便獲取定義。如果您以前使用過@types/cesium,則可以將其移除。
來自CesiumJS團隊的官方支持意味着最新和正確的定義文件將隨每個版本一起發布。這也意味着TypeScript支持將作為CesiumJS GitHub存儲庫的一部分進行正式跟蹤。如果您在使用帶有TypeScript的CesiumJS時發現一個bug,請打開一個問題(issue)或更好的方法,一個pull request請求來解決它。如果您對CesiumJS/TypeScript有疑問,或者需要幫助調試您的項目,請在社區論壇(community forum)上提問。
如果您正在使用自定義的或@types/cesium,但尚未准備好切換,則可以在安裝后刪除Source/cesium.d.ts。然后,TypeScript工具將返回到它找到的下一組CesiumJS類型定義。
官方的類型定義文件,Cesium.d.ts記錄了超過42000行的聲明和文檔,其大小達1.9MB。
深入了解
雖然我們很高興終於正式支持TypeScript,但要做到這一點還需要一些努力。最初,我們探討了3種選擇:
手動維護定義文件
我們可以手動管理和維護自己的TypeScript定義文件,作為CesiumJS代碼庫的一部分,很可能是每個JavaScript文件都有一個單獨的定義文件,使其易於管理,比如Cartesian3.js對應Cartesian3.d.ts。在技術層面這樣易於實現,但對文件同步和維護性上來說,會造成較大傷害。
另外,我們不想只包含聲明接口,也不想包含內聯文檔,這樣用戶就可以充分利用intellisense。這是我們最后的選擇,但如果結果證明這是唯一可行的選擇,那就是我們最終的選擇。
移植CesiumJS到TypeScript
您可能會驚訝地聽說我們實際上評估過用TypeScript重寫所有CesiumJS。對於TypeScript開發人員來說,這將是一個巨大的改進,對於CesiumJS維護人員和代碼庫來說,這也是一個真正的勝利。除了強類型檢查之外,它還將使我們快速使用現代約定,如template literals、arrow functions和async/await,由於兼容性和工具的原因,我們目前不允許在CesiumJS代碼庫中使用這些約定。
不幸的是,所需的努力程度和所工作量使它在短期內不會成為一個有吸引力的選擇。這個選項仍然擺在桌面上,但正如我們去年所做的大規模ES6遷移一樣,它需要大量仔細的規划、研究和基礎設施工作才能正常進行。
使用TypeScript編譯器生成定義文件
從TypeScript 3.7開始,編譯器可以編譯帶有JSDoc注釋的JavaScript代碼,並為我們生成對應的類型定義文件。這種方法完全不需要手動維護.d.ts文件,而且還具有驗證和改進我們自己的JSDoc注釋的額外好處,因為它們需要准確才能生成正確的類型定義。不用說,這個選項對我們非常有吸引力,我們決定在一些初步的原型設計實驗表明它可以工作后運行它。
實際上,我們花了幾個星期的時間來研究這種方法Marco Hutter參與了大量的文檔修復和源代碼調整工作,以使編譯器滿意。早期的工作很有希望。正如預期的那樣,它暴露了JSDoc注釋中的錯誤和不一致,並在較小程度上暴露了我們修復的CesiumJS API。不幸的是,我們很快就碰壁了。
依賴TypeScript編譯器意味着當它做了一些錯誤或意外的事情時,我們缺乏選擇。雖然編譯器在某些情況下使用JSDoc注釋,但在許多情況下,它依賴於自己的類型推斷,並且沒有為我們提供重寫它的方法。它還完全忽略了大部分JSDoc,比如在對象定義屬性,並將所有私有下划線變量作為定義的一部分公開。這導致我們開始以我們不習慣的方式彎曲CesiumJS基礎代碼,只是為了讓TypeScript編譯器滿意。我們提出了嘗試修改TypeScript編譯器本身的想法,但是我們必須深入研究編譯器代碼,我們甚至不確定維護人員會接受什么,也不知道這個過程需要多長時間。最終,我們最喜歡的解決方案變成了一場曠日持久的賭博,我們對這種方法失去了信心。
左圖:UrlTemplateImageryProvider的JSDoc, 意外地添加了屬性到BingMapsImageryProvider;右圖:生成的BingMapsImageryProvider定義,包含了重復定義,導致編譯失敗。
繪圖板
結果,我們對TypeScript擁有官方JSDoc支持非常興奮,以至於完全忽略了類似的選項 tsd-jsdoc。tsd jsdoc是jsdoc的插件,它從jsdoc輸出生成類型腳本定義。這使得它非常類似於TypeScript編譯器方法,但提供了對生成的類型定義有了更大的自由度。
tsd-jsdoc不直接解析JavaScript,而是依賴於jsdoc生成的抽象語法樹(AST)。這意味着它不受類型推斷問題或缺乏JSDoc完整性的影響,這使得TypeScript編譯器接近失敗。如果我們可以使用JSDoc注釋來表示類型,那么它就是我們希望它出現在類型定義文件中的類型。
我們已經從以前的TypeScript編譯器方法的失敗中學到了很多,所以我們能夠相當快地完成一個可行性評估,並且我們現有的JSDoc不正確的所有問題仍然適用。事情的進展比我們想象的要快,我們知道我們找到了解決辦法。
作為開發人員,有時我們過於專注於某項技術,以至於忽略了其他選擇。在這種情況下,社區成員 @bampakoa去年甚至向CesiumJS和tsd-jsdoc提交了pull請求,以使它們更加兼容。我們已經知道tsd jsdoc存在,但是我們在最初的評估中忽略了它,因為我們假定了TypeScript編譯器選項會更好,並且我們意外地選擇性地忽視了tsd-jsdoc。
Post-processing和驗證
雖然tsd-jsdoc輸出是相當高質量的開箱即用,但是我們做了一些額外的后處理來進一步改進它。這包括簡單的字符串操作、正則表達式查找和替換,甚至使用TypeScript編譯器重寫部分文件。所有這些都是作為新構建ts gulp任務的一部分發生的。如果你好奇,可以獲得這些代碼check out the code.。最終的結果是一個單獨的Cesium.d.ts,與生成的Cesium.js模塊的入口點。
除了生成輸出,build-ts任務還通過使用TypeScript編譯文件來驗證文件。如果開發人員在JSDoc中犯了錯誤,比如拼錯類名或引用私有或不存在的類型,那么構建過程將失敗。雖然這個驗證過程非常有用,但它只捕獲某些類型的錯誤。例如,如果有人實現了一個新的ImageryProvider,但不符合正確的接口,則定義文件將編譯而不出錯,但TypeScript將在嘗試將新類用作ImageryProvider的應用程序中發出編譯錯誤。
我們仍在探索添加額外驗證的想法,例如用TypeScript編寫一些單元測試,以識別開發過程中潛在的問題區域。
JSDoc錯誤
我已經多次提到,基於JSDoc的方法的一個特別令人興奮的地方是,它為我們的文檔添加了另一個級別的檢驗和驗證,使每個人都受益,而不僅僅是TypeScript開發人員。我們的文檔審查過程的一大部分現在已經自動化了。我們在代碼庫中發現的問題可以分為以下幾類:
- 不正確或不完整的類型- 在許多情況下,我們對類型使用了非正式或不正確的名稱,例如,Image實際上是HTMLImageElement,Canvas是htmlCanvaseElement。一個有趣的例子是TypedArray,它甚至不存在於規范級別,而是完整類型列表的通用術語,例如Int8Array、Float32Array等等……我們還有不完整的泛型,例如Promise而不是Promise
。 - @exports - 我們使用JSDoc的@exports標簽作為最終的支撐。如果開發人員無法在生成的HTML中顯示某些內容,他們可能會添加@exports,這將“正常工作”。我們將@exports用於枚舉、命名空間和函數,而不是@enum、@namespace和@function標記。這導致了不正確的類型生成。實踐證明我們根本不需要在代碼中的任何地方使用@exports。
- 私有類型泄露 - 公共API中引用了很多私有類型。這些私有類型不存在於HTML輸出中,對於我們的文檔構建步驟來說只是無聲的失敗。在大多數情況下,只公開私有類型是有意義的。謝天謝地,我們也有在CesiumJS中記錄私有類型的習慣,因此不必編寫新的JSDoc。
- 復制粘貼錯誤 - 最后一種JSDoc錯誤是與復制和粘貼相關的重復參數條目,例如,讓ImageryProvider A,聲明它正在記錄ImageryProvider B上的屬性,等等…
下一步
一旦社區開始使用這些定義,我們希望在接下來的幾個CesiumJS版本中會出現一些小問題。我們還開始開發一個我們想要探索的想法列表,比如為實體API使用的屬性接口利用泛型。最終,我們依靠社區告訴我們對開發者最重要的是什么,這樣我們就可以用TypeScript路線圖來塑造我們的CesiumJS 。
我們還想找出一種在CesiumJS基礎代碼中使用TypeScript定義的方法。我們相信VSCode有一些機制可以實現這一點,但是我們還沒有探索它們。如果這被證明是可行的,那么這將是一個重大的勝利,並允許通過普通JavaScript進行另一個級別的驗證,更不用說讓開發CesiumJS成為比現在更好的體驗了。
我敢肯定,當我說我們評估用TypeScript重寫CesiumJS時,很多人都振作起來了。我絕對贊成延長時間。作為評估過程的一部分,我實際使用TypeScript編譯器構建了現有的JavaScript代碼庫,甚至將一些基本文件(如Cartesian3.js)移植到TypeScript,以了解如何進行TS/js混合開發,而不是“一次完成”遷移策略。很像ES6,移植代碼是最簡單的部分。預計很快就會出現GitHub issue,它將打破所有必須發生的事情,使CesiumJS的TypeScript版本成為現實;但目前還沒有承諾。
致謝
我只想再次感謝社區在過去幾年中幫助產生了關於TypeScript的想法和討論,特別向@thw0rted致謝,他是第一個改進初始TypeScript類型定義的外部貢獻者,在最初的pull request中提供了很多很好的反饋。最后,非常感謝我的伙伴和維護人員 Kevin Ring,他不僅提供了大量的專家知識和反饋,還讓自己投入到這項工作中,並最終對代碼進行了一系列改進。
原文鏈接:https://cesium.com/blog/2020/06/01/cesiumjs-tsd/
評語:TypeScript的引入,使得CesiumJS成為更加專業的庫,同時使其更易於維護。當然移植是一個痛苦的過程。
Cesium中文網交流QQ群:807482793
Cesium中文網:http://cesiumcn.org/ | 國內快速訪問:http://cesium.coinidea.com/


