How I Made My First Contribution to DefinitelyTyped, an Open Source TypeScript Repo

As a full-stack developer who has worked extensively with TypeScript, I can attest to the immense benefits it provides in terms of type safety, code intelligibility, and developer productivity. TypeScript recently surpassed 5 million weekly npm downloads, and for good reason – it has become an indispensable tool for many JavaScript developers, including myself.

A huge part of what makes the TypeScript experience so great is the availability of high-quality type definitions for popular libraries. Instead of having to manually create type definitions yourself, you can simply install the official typings from the @types organization on npm.

Behind these helpful type definitions is a massive open source project called DefinitelyTyped. DefinitelyTyped is a community-driven repository that provides type definitions for over 6,000 JavaScript libraries, representing the combined effort of over 23,000 contributors.

Recently, I had my first opportunity to make a small contribution to this incredible project. In this article, I‘ll share my experience and offer a step-by-step guide to help other developers get involved in DefinitelyTyped.

The @types Namespace and Versioning

Before we dive in, let‘s clarify what exactly the @types namespace is and how versioning works. When you install a type definition package from @types, you‘re getting a snapshot of the type definitions from DefinitelyTyped at a specific point in time, versioned separately from the underlying JavaScript library.

For example, suppose you are using version 4.17.1 of the lodash library in your project. To get the corresponding type definitions, you would install @types/lodash:

npm install --save-dev @types/lodash

However, the version of @types/lodash does not necessarily match the version of lodash. The @types packages follow Semantic Versioning based on the DefinitelyTyped revision history. So you might have lodash 4.17.1 and @types/lodash 4.14.0 installed, and that‘s perfectly fine. The type definitions are backwards compatible and designed to work with a range of library versions.

One nuance is that the @types version can actually be ahead of the library version. For example, if a new version of the lodash library is released, the @types/lodash definitions may be updated to reflect the new APIs before you‘ve updated the actual lodash dependency in your project. This is usually not a problem, but occasionally you may need to roll back to an older version of the type definitions if there are incompatibilities.

The Benefits of TypeScript and Type Definitions

If you‘re reading this article, chances are you already see the value in TypeScript and type definitions. But allow me to share a few personal anecdotes that highlight just how beneficial they can be.

In one of my projects, we had a function that accepted a configuration object with dozens of properties, many of them optional. Without TypeScript, calling this function was always a little nerve-wracking. You had to refer back to the documentation constantly to remember what properties were available, which were required vs optional, and what types they expected. Accidentally passing in an invalid value could lead to runtime errors that might not surface until much later.

After we migrated the codebase to TypeScript and added type definitions for this config object, the entire experience was transformed. Now, when calling the function, my code editor would give me inline auto-completion and type-checking for the config object. I could see immediately which properties were required and what types they needed. If I made a mistake, the TypeScript compiler would catch it for me before I even ran the code. It was a huge boost to productivity and confidence.

On another occasion, TypeScript helped catch a subtle bug that could have easily slipped through in regular JavaScript. We were using a third-party library to make some API calls, and there was a function that accepted a callback. The function expected the callback to return a Promise, but we were accidentally returning a regular value. In TypeScript, this was caught immediately as a type error. In JavaScript, the code would have appeared to work fine, but the API calls would have silently failed due to the incorrect return value. TypeScript saved us from a frustrating debugging session.

These are just a couple small examples, but they demonstrate the real-world benefits that strong typing can provide. By catching errors early, providing better auto-completion and documentation, and giving you more confidence in your code, TypeScript and quality type definitions can be a major boon to any JavaScript project.

Finding a Library to Contribute To

So, how does one go about contributing to DefinitelyTyped? The first step is finding a library that needs type definitions. As I mentioned in the intro, my first contribution opportunity came about organically when I tried to use a library that didn‘t have types available. But there are also more proactive ways to find libraries in need.

One great place to start is the DefinitelyTyped issue tracker. Here, you‘ll find a list of open requests for type definitions. These are submitted by users who tried to npm install @types/some-library and found that no type definitions existed yet. Browsing through these requests, you may find a library you‘re familiar with or interested in providing types for.

Another approach is to simply search for libraries you use frequently and check if they have type definitions available. Go to the npm page for the library and look for the "Types" or "Typings" field in the sidebar. If it says "none", that means no type definitions have been published to @types yet. You can also check the library‘s GitHub repo to see if they provide their own type definitions. If not, they may be a good candidate for a DefinitelyTyped contribution.

Finally, keep an eye out for type definition related issues or pull requests in the libraries you use. Many library authors are open to accepting type definition contributions directly in their main repo. And even if a library does have published @types definitions, there may be open pull requests with fixes or improvements that you can help review and test.

Creating a New Type Definition Package

Once you‘ve found a library to contribute types for, the next step is to fork the DefinitelyTyped repo and create a new type definition package. Detailed instructions can be found in the DefinitelyTyped README, but here‘s a condensed version:

  1. Fork the DefinitelyTyped repo on GitHub.
  2. Clone your fork to your local machine.
  3. Run npm install to install the dev dependencies.
  4. Run npx dts-gen --dt --name <library> --template module to generate a new type definition package. Replace <library> with the name of the npm package you‘re creating types for.
  5. The previous step will have created a new directory with index.d.ts, <library>-tests.ts, tsconfig.json, and tslint.json files. Modify these as necessary for your library, following the contribution guidelines.
  6. Run npm test <library> to run the linter and tests on your package.
  7. Once your types are ready, commit your changes and open a pull request to DefinitelyTyped.

Let‘s walk through an example of this process for a hypothetical library called example-lib.

First, generate the package scaffolding:

npx dts-gen --dt --name example-lib --template module

This will create a new directory called example-lib with the following files:

example-lib
├── index.d.ts
├── example-lib-tests.ts 
├── tsconfig.json
└── tslint.json

Now, open up index.d.ts and start filling in the type definitions based on the library‘s API. Here‘s a contrived example:

// Type definitions for example-lib 1.2
// Project: https://github.com/example-org/example-lib
// Definitions by: Your Name <https://github.com/your-github-username>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

export function exampleFunction(arg1: string, arg2: number): boolean;

export interface ExampleInterface {
    prop1: string;
    prop2: number;
    method(): void;
}

export class ExampleClass {
    constructor(arg: string);
    exampleMethod(): string;
}

export as namespace exampleLib;

In this example, we‘re exporting a function, an interface, and a class, which might represent the public API of the example-lib library. The specific types and syntax will depend on the actual library you‘re working with.

Next, open up example-lib-tests.ts and write some test cases:

import { exampleFunction, ExampleInterface, ExampleClass } from "example-lib";

const result = exampleFunction("hello", 42);
const exampleObj: ExampleInterface = {
    prop1: "world",
    prop2: 13,
    method() {
        // ...
    }
};
const exampleInstance = new ExampleClass("test");
exampleInstance.exampleMethod();

These tests serve as a way to validate your type definitions and make sure they match up with how the library is actually used.

Finally, run the tests and linter:

npm test example-lib

If everything passes, you‘re ready to submit your PR! If there are any failures, go back and adjust your types until the tests pass.

Tips for Creating High-Quality Type Definitions

Creating good type definitions is an art as much as a science. Here are a few tips I‘ve learned from my experience and from reviewing other people‘s contributions:

  1. Follow the library‘s naming conventions. If the library uses camelCase, don‘t use PascalCase in your type definitions. Consistency is key.

  2. Don‘t export types that aren‘t part of the public API. It can be tempting to export every type and interface you see in the library‘s source code, but resist that urge. Only export the types that are actually intended to be used by consumers of the library.

  3. Use union types for polymorphic parameters and return values. If a function accepts multiple types for a parameter or can return multiple types, use a union type to represent that. For example: function stringify(value: string | number): string.

  4. Use generics for type parameters. If the library has functions or classes that work with generic types, make sure to define those as generics in your type definitions. Example: function identity<T>(arg: T): T.

  5. Write tests for edge cases. Don‘t just test the happy path in your test file. Think about unusual ways the library might be used and make sure your types handle those cases correctly.

  6. Document complex types and behaviors. If there‘s something non-obvious or tricky about how a particular type or function works, add a comment explaining it. Future maintainers will thank you.

  7. Keep an eye out for type bugs and submit fixes. Even if a library already has type definitions, they may not be perfect. If you notice a bug or inconsistency while using the types, don‘t hesitate to submit a fix to DefinitelyTyped.

The Importance of Maintained Type Definitions

One thing to keep in mind is that publishing type definitions is not a one-and-done affair. As libraries evolve and add new features, their type definitions need to be kept up to date. An outdated type definition can be almost as frustrating as having no types at all.

This is where the maintainers of DefinitelyTyped come in. They play a crucial role in reviewing new type definition packages and merging updates to existing packages. However, they rely on the community to actually submit those updates and bring issues to their attention.

As a contributor, you can help by keeping an eye on the libraries you‘ve provided types for. Watch for new releases and check if any of the changes require updates to the type definitions. If you don‘t have time to make the updates yourself, at least open an issue on DefinitelyTyped to let others know that the types are out of date.

Maintainers of popular libraries can also aid in this effort by publishing their own type definitions to DefinitelyTyped or bundling them directly with their library. This helps ensure that the types stay in sync with the library and reduces the burden on the DefinitelyTyped maintainers.

Some libraries, like RxJS, have even started using TypeScript themselves and generating their type definitions automatically from their source code. This is the ideal scenario, as it guarantees that the types are always up to date and accurate.

The Financial Aspect of Open Source

It‘s worth noting that maintainers of open source projects, including DefinitelyTyped, often do this work on a voluntary basis. They pour countless hours into reviewing contributions, triaging issues, and keeping the project running smoothly, usually without any financial compensation.

If you or your company benefits from the work of DefinitelyTyped maintainers (and if you use TypeScript, you almost certainly do), consider supporting them financially. DefinitelyTyped has a sponsorship page on GitHub where you can make a one-time or recurring donation.

Remember, financial support helps ensure that critical infrastructure projects like DefinitelyTyped can continue to operate and improve. It‘s a way of giving back to the community and recognizing the immense value that maintainers provide.

Of course, not everyone has the means to contribute financially, and that‘s okay! Contributing your time and expertise by submitting type definitions, reviewing PRs, or helping with issue triage is just as valuable. The point is to find a way to contribute that works for you.

Conclusion

Contributing to DefinitelyTyped is a fantastic way to get involved in open source and improve the TypeScript ecosystem for everyone. By providing high-quality type definitions, you can help make TypeScript development more productive, enjoyable, and error-free.

In this article, we‘ve covered the basics of how to contribute, from finding a library to submitting a pull request. We‘ve also discussed some best practices for writing good type definitions and the importance of keeping them up to date over time.

If you‘re new to open source or to TypeScript, don‘t let that stop you from contributing! The DefinitelyTyped community is welcoming and supportive, and there are plenty of small, beginner-friendly issues to tackle. You don‘t need to be an expert in TypeScript to make a meaningful contribution.

So what are you waiting for? Go find a library that needs types and submit a PR today! Your contribution, no matter how small, can make a real difference in the lives of TypeScript developers around the world.

Happy typing!

Similar Posts