Progressively Adopting TypeScript in a Dojo Toolkit Application

TypeScript has become a mainstay of modern web development libraries. Consuming functions and widgets written by a third party can be error-prone without some type of guidance. Introducing static typing to the interfaces doesn’t just reduce misuse, it has added benefits including intelligence code completion.

Dojo Toolkit is one of the earliest libraries to facilitate the building of large, dynamic, interconnected single-page applications. Above and beyond the tools for loading and storing data, managing events, and working with the DOM, it provides a diverse set of widgets through Dijit.

Is TypeScript something the Dojo Toolkit can benefit from even though it was written before TypeScript existed? Luckily for us, the designers of TypeScript have made this possible through tools we’ll be looking at in this post.

Introducing TypeScript to Your Project

We will need just two things to start integrating TypeScript into our project -the TypeScript compiler, and a configuration file.

Installing the TypeScript Compiler

Installing the TypeScript compiler is easy, we simply install typescript as a devDependency.

The TypeScript Configuration File

You can read about the full extent of the tsconfig.json file options, but the interesting ones for this post are allowJs, checkJs, and esModuleInterop.

  • allowJs - Allows JavaScript files to be compiled. We need this right now because our entire app is written in JavaScript.
  • checkJs - Reports errors in JavaScript files. We can have TypeScript perform type checking on our original JavaScript files, possibly surfacing some application bugs before we even make any code changes!
  • esModuleInterop - Enables compatibility with the dojo loader.

With these two pieces in place, we can now try to compile our project using the TypeScript compiler! From your project root, run the compiler.

Uh oh! Looks like we have a few errors.

TypeScript doesn’t know anything about the Dojo Toolkit, so it’s not sure how to proceed here.

Ambient Declarations

Then, include the files in your tsconfig.json file by adding them to the include key.

Note that we’ve included three different ambient declarations (the .d.ts files):

  • dojo/1.11/modules.d.ts - Ambient declarations for Dojo 1.11
  • diijt/1.11/modules.d.ts - Ambient declarations for Dijit 1.11
  • dojo/1.11/loader.d.ts - Ambient declarations for the loader to give us access to require and define.

Running the TypeScript compiler now should yield better results.

Success! We’ve now got some basic TypeScript type checking for the Dojo Toolkit implemented, but soon we’ll really see the power of type checking when we transition our project to TypeScript.

Transitioning Loaders

Take a look at the top of Hello.js:

Using ES module syntax, that would look like:

How can this be compatible with Dojo Toolkit you wonder? Go ahead and compile the project (you’ll see some errors, but we’ll deal with those in a bit) and open up build/src/demo/widgets/Hello.js.

You’ll notice right away that our imports were turned into a call to define that looks a lot like our old define call! TypeScript knows we want to use AMD modules (from our tsconfig.json file) and automatically converted our imports into an appropriate define call. That's some powerful stuff right there.

Type Safety

Because TypeScript knows that our widget extends from _WidgetBase, it knows what properties are available and which ones are not. If we try to use a property that isn't part of _WidgetBase or our extension, we'll see an error about the property not existing - just like what we are seeing with nameInput. To fix this, we simply need to define the nameInput property and tell TypeScript (via JsDoc) what type it is. Add this above the buildRendering property:

With our build errors taken care of, let’s take a quick inventory of the type safety we’ve got in Hello.js right now.

  • Accidentally using a variable that doesn’t exist will create a compile-time error. So if we type this.nmeInput by mistake, we’ll get an error right away.
  • We’ve got autocomplete (most IDEs support this)! Now that TypeScript knows what types things should be, typing domConstruct or this. will provide you with an autocomplete list that is specific to that type.
  • We’ve got additional type safety on Dijit constructor parameters. Since TypeScript knows what TextBox({ ... }) should accept, if you try to type in a property that hasn’t been defined in the types, you’ll get a compile-time error.

These are all huge wins, but can we actually migrate our JavaScript files to full-on TypeScript files for an even bigger payoff?

.js to .ts

Rename Your Files

Update Your Types

Here we are declaring that nameInput is undefined, and we expect it to contain either nothing (undefined) or an instance of TextBox. This is a simple example, but TypeScript offers an incredibly wide and advanced set of types that are now at your disposal.

Your Project: What’s First?

Critical Business Logic

Modules with few dependencies

Potential Issues

Mapped Libraries

Next Steps

TypeScript and ESLint



Originally published at on June 3, 2020.

Modernizing Apps, Tools & Teams | | Twitter: @sitepen