CesiumJS新增官方TypeScript类型定义
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/