There were a few areas in Source where a function was assumed to be executed synchronously when a function was resolved. Native Promises however, by spec, will resolve at the end of a frame. There were also some strange handling of promise rejection in imagery providers that I cleaned up a bit in order to make them testable.
- In LabelCollection and EntityCluster, order of execution adjustment where made.
- Added TextureAtlas.addImageSync to support the order of execution fix in LabelCollection
- ArcGisMapServerImageryProvider.readyPromise will not reject if there is a failure unless the request cannot be retried.
- SingleTileImageryProvider.readyPromise will not reject if there is a failure unless the request cannot be retried.
The majority of the changes lie in the Specs, where unresolved promises weren’t being awaited before finishing executions, and where resolved promises are assumed to be synchronous all over the place and needed a good amount of fixes. Another issue which came up was calling Promise.reject in the body of a spec can cause node to halt execution when running via the command line.
Locked to version 6.7.0 since that is the version we are already using and
updating it requires additinal work because of the use of util.lazyResolve
in the GoogleEarthEnterpriceMetadata root parser code.
Rather than submit libraries to Source/ThirdParty, which always end up
being modified for our build system and quickly go out of date, this
change starts to use libraries via npm instead. Currently Autolinker,
earcut, when, tween.js, rbush, kdbush, quickselect, and topojson are
ported.
The main hurdle that prevented us from doing this sooner was the fact that
Cesium has a long history of not requiring a build step after every code
change and has a goal of writing valid JS code, unlike many libraries today
that mandate a bundler to turn invalid JS code into valid code.
Rather than mandate a bundler during development, this initial step adds a
"buildThirdyParty" function to the current "build" step. This function runs
third party libraries (defined in the ThirdParty/npm/ folder) through
RollUp and creates an equivalent file in `Source/ThirdPartyNpm`.
The change to end users will be non-existent, especially since the combined
Cesium.js will still re-export any third party modules as part of the
private API just like it used to.
This doesn't prevent code duplication for users using some of the same
third party libraries as Cesium. This is just an improvement as to how
depend on third party libraries internally.
I think Cesium's days of being "bundler free" are probably limited long
term, performance is the main hurdle and newer tools like esbuild may make
that no longer a problem. But that's outside the scope of this initial
goal.
1. No code change required on our end at all. These were mostly dropping
support for Node 8, which is end-of-life.
2. I did not update to TypeScript 4.x, because I'm actually not sure what
that would mean for the definition files and something we should probably
look into separately.
Many tools attempt to require.resolve package.json, and in the latest
versions of node that is not possible if the package specifies exports.
The solution is to simply add package.json to exports.
Resolves#8992
1. Only code change was a harmless bug in Expression.js that new eslint
detected.
2. rollup-plugin-uglify and rollup-plugin-external-globals complain they
want rollup 1.x, but they work fine with 2 so it's a harmless warning.
It's been a long requested feature for us to have official TypeScript type
definitions. While the community has done a yeoman's job of manually
supporting various efforts, the most recent incarnation of which is
`@types/cesium`, the sheer scale and ever-evolving nature of Cesium's code
base makes manual maintenance a Sisyphean task.
Thankfully, our decision to maintain meticulous JSDoc API documentation
continues to pay dividends and is what makes automatically generating
TypeScript definitions possible. Using the excellent
https://github.com/englercj/tsd-jsdoc project we can now automatically
generate and even partially validate official definitions as part of the
build process. (Thanks to @bampakoa who contributed some early PRs to both
CesiumJS and tsd-jsdoc over a year ago and is how I learned about
tsd-jsdoc)
While tsd-jsdoc output is mostly where we need it to be, we do
post-processing on it as well. This lets us clean up the output and also
make sure these definitions work whether users include cesium via module,
i.e. `import { Cartesian3 } from 'cesium'`, or individual files, i.e.
`'import Cartesian3 from 'cesium/Source/Core/Cartesian3'`. There were also
some quirks of tsd-jsdoc output we fixed that may eventually turn into a PR
into that project from us. The post-processing is part typescript compiler
API, part string manipulation. It works and is straightforward but we might
want to go full TS api in the future if we decide we need to do more
complicated tasks. The output of tsd-jsdoc is currently a little noisy
because of some incorrect error reporting, but I'm talking with the
maintainer in https://github.com/englercj/tsd-jsdoc/issues/133 to get them
fixed. No need to hold up this PR for it though.
The definition is generated as a single `Cesium.d.ts` file in Source, so it
lives alongside Cesium.js. It is ignored by git but generated by a separate
`build-ts` task as part of CI and makeZipFile. This task also validates the
file by compiling it with TypeScript, so if a developer does anything too
egregious, the build will fail. Definitions are automatically included in
our npm packages and release zips and will be automatically used by IDEs
thanks to the `types` setting in package.json. This means that IDEs such as
VS Code will prefer these types over the existing `@types/cesium` version
by default.
I didn't want to slow the `build` step down, so I made this a separate
step, but in the future we could consider running it by default and we
could also unignore this file in Git so that PR reviewers can see the
impact, if any, our code changes have on the generated definitions. This
might be a good idea as an additional sanity check and should only actually
change when the public API itself changes. But the issue would be
remembering to run it before submitting the code (or we could use git hooks
I suppose?) I just don't want to slow down devs so I'm hesitant to do
anything like this out of the gate. We can definitely revisit in the
future.
A particular exciting thing about this approach is that it exposed a ton of
badness in our current JSDoc markup, which is now fixed. Previously, our
only concern was "does the doc look right" and we didn't pay attention to
whether the meta information generated by JSDoc correctly captured type
information (because up until it didn't matter). We leaned particular hard
on `@exports` which acted as a catch-all but has now been completely
removed from the codebase. All this means is that our documentation as a
whole has now improved greatly and will continue to be maintained at this
new higher level thanks to incorporating TS definition creation into our
pipeline!
One minor caveat here is that obviously we changed our JSDoc usage to both
make it correct and also accommodate TypeScript. The main drawback to these
fixes is that enums are now generated as globals in the doc, rather than
modules. This means they no longer have their own dedicated page and are
instead on the globals page, but I changed the code to ensure they are
still in the table of contents that we generate. I think this trade-off is
perfectly fine, but I wanted to mention it since it does change the doc
some. We can certainly look into whether we can generate enums on their own
page if we think that makes sense. (I actually like this approach a little
better personally).
Last major piece, the actual code. 99% of the changes in this PR only
affect the JSDoc. There are two exceptions:
A few of our enums also have private functions tacked onto them. I had to
move these functions to be outside the initializer but otherwise they are
unchanged. This ensures that a valid TS enum is generated from our code, since you can't have functions globbed onto enums in the TS world. If we were writing TS code by hand, we could use declaration merging with a namespace, but the toolchain we are using doesn't have a way to express that right now. There were two cases where these extra functions weren't private, `ComponentDataType` and `IndexDataType`. That means that as far as the TS definitions goes, the helper methods don't exist. I consder this an edge case and we can write up issues to investigate later. I'm actually not even sure if these functions are public on purposes, @lilleyse can you confirm?
We had a few places where we had method signatures with optional parameters
that came _before_ required parameters, which is silly. This is invalid
TypeScript (and not good API design no matter the language). In 99% of
cases this was `equalsEpsilon` style functions where the lhs/rhs were
optional but the epsilon was not. I remember the discussion around this
when we first did it because we were paranoid about defaulting to 0, but
it's an edge case and it's silly so I just changed the epsilon functions
to default to zero now, problem solved.
Here's a high level summary of the JS changes:
* Use proper `@enum` notation instead of `@exports` for enums.
* Use proper `@namespace` instead of `@exports` for static classes.
* Use proper `@function` instead of `@exports` for standalone functions.
* Fix `Promise` markup to actually include the type in all cases, i.e.
`Promise` => `Promise<void>` or `Promise<Cartesian3[]>`.
* Fix bad markup that referenced types that do not exist (or no longer
exist) at the spec level, `Image` => `HTMLImageElement`,
`Canvas` => `HTMLCanvasElement`, etc.. `TypedArray` in particular does not
exist and much be expressed as a lsit of all applicable types,
`Int8Array|Uint8Array|Int16Array|Uint16Array...`.
* Use dot notation instead of tilde in callbacks, to better support
TypeScript, i.e. `EasingFunction~Callback` becomes
`EasingFunction.Callback`. The only exception is when we had a callback
type that was i.e. `exportKml~ModelCallback` becomes
`exportKmlModelCallback` (a module global type rather than a type on
exportKml). This is because it's not possible to have exportKml be both a
function and a namespace in this current toolchain. Not a big deal either
way since these are meta-types used for defining callbacks but I wanted to
mention it.
* There were some edge cases where private types that were referenced in
the public API but don't exist in the JSDoc. These were trivial to fix by
either tweaking the JSDoc to avoid leaking the type or in some cases, just
as `PixelDataType`, simply exposing the private type as public. I also
found a few cases where things were accidentally public, I marked these as
private (these were extreme edge cases so I'm not concerned about breaking
changes). Appearances took an optional `RenderState` in their options, I
just changed the type to `Object` which we can clean up further later if
we need to.
* Lots of other little misc JSDoc issues that became obvious once we
started to generate definitions (duplicate parameters for example).
Thanks again to the community for helping generate ideas and discussions
around TS definitions over the last few years and a big thanks to @javagl
for helping behind the scenes on this specific effort by evaluating a few
different approaches and workaround before we settled on this one (I'm
working on a blog with all of the gory details for those interested).
Finally, while I'm thrilled with how this turned out (all ~41000 lines
and 1.9MB of it), I can guarantee we will uncover some issues with the
type definitions as more people use it. The good news is that improving it
is now just a matter of fixing the JSDoc, which will benefit the community
as a whole and not just TS users.
Fixes#2730Fixes#5717
1. Add prettier and configuration files, but do not format code yet.
2. Configure it for all html|css|js|md code.
3. Fix a bug it found in `CZML Custom Properties.html`
4. Add a pre-commit hook via husky and pretty-quick which will
automatically format changed files when they are staged on the client.
5. Run `prettier-check` during CI and fail the build of code is not
formatted properly.
6. Install eslint config for prettier. This does not enable prettier
checking in eslint (it's unusably slow) but instead just makes sure that
no eslint rules conflict with prettier formatting.
7. Update .editorconfig to match (since prettier respects it)
8. Tweak Sandcastle to handle prettier formated examples