There are three things that you learn about dojo:
- it is very powerful
- it can be tricky to learn
- it doesn’t play well with others
One technology that I have been seriously considering recently is TypeScript. Both of these technologies are designed to make large client-side applications easier to build and maintain: dojo brings a large amount of infrastructure support for building multi-layer browser applications and TypeScript brings strong type support that help prevent typos, refactor-induced bugs, etc.
In order to bring these two technologies, two things needed to be done:
- Type definitions had to be generated for the dojo API
- A method had to be developed to get the dojo and TypeScript module system to work together
Generating type definitions for dojo
Extracting the API
The dojo community has done a phenominal job creating extensive documentation. Like many other frameworks, the API for dojo is automatically generated from the source code leading to a consistent structure to the API’s HTML.
At the base of this structure is a file called tree.json that looks like this:
This file is used by the page to generate a tree control that is used to navigate the API. Conveniently, this file also describes dojo’s parent-child relationships and serves as the basic structure for the extraction.
The next step in the extraction process is to get the detailed information for each API entry. For a class, this would include the description, constructor arguments, properties, and methods; functions would have the description as well as the parameters and return values. To accomplish this, we need to get the same page fragments that the API site uses into our own page for examination. Due to cross-site scripting issues, this can’t be done directly. Instead the calls had to go up the the generator’s server which would retrieve the information from the dojo site and the return the result to the client. Along the way, it caches the data so that the application didn’t have to make the requests throughout the development process (there are almost 2500 entities that make up the dojo API, so network perfomance was pretty important).
When each item was loaded, then its “type” (as described by the entry in tree.json) would be used to determine what kind of document fragment would be expected. This allowed Function objects (e.g. dojo/request) to be extracted as a function while classes (e.g. dijit/form/Button) would be extracted as a class. As an example, consider dojo/request. It is a function that is used to make requests via XMLHttpRequest or other transport. The API page looks like this:
As you can see, the page shows the description, parameters (with type and description) and the return type of the function. You also see that dojo overloads the request function as an object too. The request function has a property “del” that is a shortcut for a delete request (there are also shortcuts for get, post, and put…). This gives us all of the information that we need to generate the API entry.
Processing the API
After the API entries have been read, they are sent to the server to have the type definitions generated. At this point, the data is very good (due to the high quality of the documentation), but there are occasional issues, such as misspelled types, that the server will begin to address. The server executes its work using the following steps:
- Merge entries that refer to the same path
- Cleanse the data
- Generate the type definition for the entry
Cleanse the data
As I have said before, the dojo documentation is very good, but any framework as large as this is bound so have some challenges maintaining consistency with the API. That, combined with TypeScript’s selection of primitives (e.g. all numbers are type number. No distinction exists for integers and floats) leads to a need to map and correct the API types to be valid TypeScript types.
Generate the type defintions
Originally, I set out to generate a single type definition file. However, this file ended up being something north of 500K lines of code. This caused Visual Studio (the IDE I was using) great consternation as it tried to process the massive file. In the end, the type definitions are written out as multiple script files consisting of dojo, dijit, doh, and each dojox sub-module in its own file. The generation of the files were pretty straightforward since the previous steps have already done the heavy-lifting.