As many of you know, Dojo 2 is being built on TypeScript. Many of us involved in Dojo 2 believe that TypeScript brings several advantages to developing with web technologies these days. Features like structural typing and interfaces help us write code that is less prone to errors as well as being able to express to those consuming Dojo 2 what the intent of the code is.

If you have worked with Dojo 1 for any extended period of time, you will realise how feature rich and complex the Dojo Toolkit is. Because of the power and backwards compatibility of Dojo 1, it can often be daunting, even for an experienced user, to effectively use Dojo. If as a developer, you need to utilise a new part of Dojo, it can be confusing to understand what part of the API to use and how to use it. I know from personal experience, I would often review the test cases for a part of Dojo I wasn’t familiar with to try to figure out what the intent of the original author was.

As we have continued to work on Dojo 2, several of us realised that TypeScript could offer a lot to Dojo 1, potentially allowing people to start to migrate code to TypeScript and ES6+, making their current code even better, but giving them an easier path to the future. In order to be effective at using TypeScript with Dojo 1, we need to do a bit of enablement.

TypeScript Typings

Concurrent with the releases of Dojo 1.11, we first made available dojo-typings. While you could use TypeScript straight away with Dojo 1, it would not be very effective. When working with non-TypeScript code it is best to have a rich set of ambient declarations, which describe the code “around” them, but have no impact on the code itself. While we appreciate the community efforts at Definitely Typed, because Dojo 1 is so complex, we felt it needed a slightly different approach. We also needed typings for popular dependencies such as dgrid and dstore.

auto-complete Code completion and typing with Dojo 1 and TypeScript

Example Application

A very basic example application is provided within the dojo/typings repository. This example includings a package.json, tsconfig.json, and a main module to kickoff the application.

package.json simply includes the standard metadata for your project, the same as any typical Dojo application.

tsconfig.json contains the compiler settings for your application, as well a reference to the location of your application’s files. For example:

{
	"version": "2.2.1",
	"compilerOptions": {
		"allowSyntheticDefaultImports": true,
		"baseUrl": ".",
		"declaration": true,
		"experimentalDecorators": false,
		"module": "amd",
		"moduleResolution": "node",
		"noImplicitAny": true,
		"noImplicitThis": false,
		"noImplicitUseStrict": true,
		"noUnusedParameters": true,
		"noUnusedLocals": true,
		"outDir": "_build/app/src",
		"removeComments": false,
		"sourceMap": true,
		"strictNullChecks": false,
		"target": "es5"
	},
	"include": [
		"./app/src/**/*.ts"
	],
	"files": [
		"./node_modules/dojo-typings/dojo/1.11/index.d.ts",
		"./node_modules/dojo-typings/dojo/1.11/modules.d.ts",
		"./node_modules/dojo-typings/dijit/1.11/index.d.ts",
		"./node_modules/dojo-typings/dijit/1.11/modules.d.ts",
		"./node_modules/dojo-typings/dojox/1.11/index.d.ts",
		"./node_modules/dojo-typings/dojox/1.11/modules.d.ts",
		"./node_modules/dojo-typings/custom/dgrid/1.1/dgrid.d.ts",
		"./node_modules/dojo-typings/custom/dstore/1.1/dstore.d.ts"
	]
}

Read the full tsconfig.json documentation for more information on each setting.

And then we can load internationalization resource bundles:

// app/src/nls/main.ts
export const root = {
    // default i18n messages
    login: 'Log in',
    signUp: 'Create an account'
};

And then in our application we can import the internationalization resources:

// app/src/Application.ts

// various import statements
import * as _WidgetBase from 'dijit/_WidgetBase';
import * as messages from 'dojo/i18n!./nls/main';

// ...
// app/src/main.ts
import Application from './Application';

export const app = new Application();

app.start();

We can also leverage decorators to generate our declare constructor.

import * as DijitDialog from 'dijit/Dialog';
import * as messages from 'dojo/i18n!./nls/main';
import declare from './declareDecorator';

interface Dialog extends DijitDialog {}

@declare(DijitDialog)
class Dialog {
	title: string = messages.dialogTitle;
	content: string = messages.dialogContent;
}

export default Dialog;

We currently have some examples in the dojo/typings repo and in the SitePen Typescript examples repo. Note that we are currently working to update these examples for more recent TypeScript releases, but they are informative in showing more details and providing several examples.

Beyond authoring your application, the next steps are to setup a solid TypeScript workflow to transpile your application automatically, hook into tests with Intern or another test framework, and more!

Notes and Caveats

There are some caveats to note from our experience in putting this to practice in our work.

async/await

Using async/await on IE requires a Promise polyfill. You’ll need to decide early whether you want to use async/await and use a polyfill or abstain from polyfills and use the more verbose Promise#then chains.

declare typings

declare and DeclareConstructor#createSubclass typings are challenging to use. If you don’t match the types exactly, TypeScript will silently fall through to the bottom any case. You’re better off explicitly defining types and separately defining the mixin properties interface.

Deferred and Promise

dojo/Deferred and Promise types are not compatible with each another, but they are compatible with Thenable. Use Thenable instead of Promise as parameter types.

Potential typings conflicts

Intern’s typings pull in module declarations for Intern 3.x’s early version of Dojo 2. When working w/ Dojo 1.x you need to include Dojo 1’s ambient module declarations from dojo-typings. Instead, include Intern and leadfoot typings from dojo-typings (not Intern). Alternatively, --noResolve may also help.