Writing Declaration Files for @types

Daniel Rosenwasser

A while back we talked about how TypeScript 2.0 made it easier to grab declaration files for your favorite library. Declaration files, if you’re not familiar, are just files that describe the shape of an existing JavaScript codebase to TypeScript. By using declaration files (also called .d.ts files), you can avoid misusing libraries and get things like completions in your editor.

As a recap of that previous blog post, if you’re using an npm package named foo-bar and it doesn’t ship any .d.ts files, you can just run

npm install -S @types/foo-bar

and things will just work from there.

But you might have asked yourself things like “where do these ‘at-types’ packages come from?” or “how do I update the .d.ts files I get from it?”. We’re going to try to answer those very questions.

DefinitelyTyped

The simple answer to where our @types packages come from is DefinitelyTyped. DefinitelyTyped is just a simple repository on GitHub that hosts TypeScript declaration files for all your favorite packages. The project is community-driven, but supported by the TypeScript team as well. That means that anyone can help out or contribute new declarations at any time.

Authoring New Declarations

Let’s say that we want to create declaration files for our favorite library. First, we’ll need to fork DefinitelyTyped, clone your fork, and create a new branch.

git clone https://github.com/YOUR_USERNAME_HERE/DefinitelyTyped
cd DefinitelyTyped
git checkout -b my-favorite-library

Next, we can run an npm install and create a new package using the new-package npm script.

npm install
npm run new-package my-favorite-library

For whatever library you use, my-favorite-library should be replaced with the verbatim name that it was published with on npm. If for some reason the package doesn’t exist in npm, mention this in the pull request you send later on.

The new-package script should create a new folder named my-favorite-library with the following files:

  • index.d.ts
  • my-favorite-library-tests.ts
  • tsconfig.json
  • tslint.json

Finally we can get started writing our declaration files. First fix up the comments for index.d.ts by adding the library’s MAJOR.MINOR version, the project URL, and your username. Then, start describing your library. Here’s what my-favorite-library/index.d.ts might look like:

// Type definitions for my-favorite-library x.x
// Project: https://github.com/my-favorite-library-author/my-favorite-library
// Definitions by: Your Name Here <https://github.com/YOUR_GITHUB_NAME_HERE>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

export function getPerpetualEnergy(): any[];

export function endWorldHunger(n: boolean): void;

Notice we wrote this as a module – a file that contains explicit imports and exports. We’re intending to import this library through a module loader of some sort, using Node’s require() function, AMD’s define function, etc.

Now, this library might have been written using the UMD pattern, meaning that it could either be imported or used as a global. This is rare in libraries for Node, but common in front-end code where you might use your library by including a <script> tag. So in this example, if my-favorite-library is accessible as the global MyFavoriteLibrary, we can tell TypeScript that with this one-liner:

export as namespace MyFavoriteLibrary;

So the body of our declaration file should end up looking like this:

// Our exports:
export function getPerpetualEnergy(): any[];

export function endWorldHunger(n: boolean): void;

// Make this available as a global for non-module code.
export as namespace MyFavoriteLibrary;

Finally, we can add tests for this package in my-favorite-library/my-favorite-library-tests.ts:

import * as lib from "my-favorite-library";

const energy = lib.getPerpetualEnergy()[14];

lib.endWorldHunger(true);

And that’s it. We can then commit, push our changes to GitHub…

git add ./my-favorite-library
git commit -m "Added declarations for 'my-favorite-library'."
git push -u origin my-favorite-library

…and send a pull request to the master branch on DefinitelyTyped.

Once our change is pulled in by a maintainer, it should be automatically published to npm and available. The published version number will depend on the major/minor version numbers you specified in the header comments of index.d.ts.

Sending Fixes

Sometimes we might find ourselves wanting to update a declaration file as well. For instance, let’s say we want to fix up getPerpetualEnergy to return an array of booleans.

In that case, the process is pretty similar. We can simply fork & clone DefinitelyTyped as described above, check out the master branch, and create a branch from there.

git clone https://github.com/YOUR_USERNAME_HERE/DefinitelyTyped
git checkout -b fix-fav-library-return-type

Then we can fix up our library’s declaration.

- export function getPerpetualEnergy(): any[];
+ export function getPerpetualEnergy(): boolean[];

And fix up my-favorite-library‘s test file to make sure our change can be verified:

import * as lib from "my-favorite-library";

// Notice we added a type annotation to 'energy' so TypeScript could check it for us.
const energy: boolean = lib.getPerpetualEnergy()[14];

lib.endWorldHunger(true);

Dependency Management

Many packages in the @types repo will end up depending on other type declaration packages. For instance, the declarations for react-dom will import react. By default, writing a declaration file that imports any library in DefinitelyTyped will automatically create a dependency for the latest version of that library.

If you want to snap to some version, you can make an explicit package.json for the package you’re working in, and fill in the list of dependencies explicitly. For instance, the declarations for leaflet-draw depend on the the @types/leaflet package. Similarly, the Twix declarations package has a dependency on moment itself (since Moment 2.14.0 now ships with declaration files).

As a note, only the dependencies field package.json is necessary, as the DefinitelyTyped infrastructure will provide the rest.

Quicker Scaffolding with dts-gen

We realize that for some packages writing out every function in the API an be a pain. Thats why we wrote dts-gen, a neat tool that can quickly scaffold out declaration files fairly quickly. For APIs that are fairly straightforward, dts-gen can get the job done.

For instance, if we wanted to create declaration files for the array-uniq package, we could use dts-gen intsead of DefinitelyTyped’s new-package script. We can try this our by installing dts-gen:

npm install -g dts-gen

and then creating the package in our DefinitelyTyped clone:

cd ./DefinitelyTyped
npm install array-uniq

dts-gen -d -m array-uniq

The -d flag will create a folder structure like DefinitelyTyped’s new-package script. You can peek in and see that dts-gen figured out the basic structure on its own:

export = array_uniq;
declare function array_uniq(arr: any): any;

You can even try this out with something like TypeScript itself!

Keep in mind dts-gen doesn’t figure out everything – for example, it typically substitutes parameter and return values as any, and can’t figure out which parameters are optional. It’s up to you to make a quality declaration file, but we’re hoping dts-gen can help bootstrap that process a little better.

dts-gen is still in early experimental stages, but is on GitHub and we’re looking feedback and contributions!

A Note About Typings, tsd, and DefinitelyTyped Branches

If you’re not using tools like tsd or Typings, you can probably skip this section. If you’ve sent pull requests to DefinitelyTyped recently, you might have heard about a branch on DefinitelyTyped called types-2.0. The types-2.0 branch existed so that infrastructure for @types packages wouldn’t interfere with other tools.

However, this was a source of confusion for new contributors and so we’ve merged types-2.0 with master. The short story is that all new packages should be sent to the master branch, which now must be structured for for TypeScript 2.0+ libraries.

Tools like tsd and Typings will continue to install existing packages that are locked on specific revisions.

Next Steps

Our team wants to make it easier for our community to use TypeScript and help out on DefinitelyTyped. Currently we have our guide on Publishing, but going forward we’d like to cover more of this information on our website proper.

We’d also like to hear about resources you’d like to see improved, and information that isn’t obvious to you, so feel free to leave your feedback below.

Hope to see you on DefinitelyTyped. Happy hacking!

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Kristijan Trajkovski 0

    So… How should we go about testing out the definitions? I’d rather not open a PR unless i’m sure everything works as it should.

Feedback usabilla icon