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