Photo by Elke Karin Lugert on Unsplash
Our front-end team at Hopjump has been working with TypeScript for a while now. When we’re working in TypeScript, everything is wonderful – we have quick editor feedback to see if we’re writing type errors, automatic IntelliSense to see possible properties and methods, and wonderful editor tools to help us write TypeScript quicker.
TypeScript works great for the application code we write – however most projects nowadays use at least some third-party libraries. How can we use libraries, while still keeping the great developer experience that TypeScript gives us?
Option 1: The third-party library is written in TypeScript
index.d.ts file and end up in the
node_modules directory for that project.
react-content-loader is written in TypeScript. Here’s a tree of the file structure:
The yellow outline shows the code as it will run in our apps, and the blue outline shows the types that the TypeScript compiler will use.
Great! This also ensures that the types are always in sync with the code, since the library wouldn’t compile if there were type errors. In practice, these types work great:
We can easily see all of the functions available in the library, as well as the types of the parameter and return values. You can also command click (control click on Windows) on the import in VSCode to see the type definitions for the entire module. This can be a very useful reference!
However, not all third-party libraries are written in TypeScript. As soon as we try to use a library that doesn’t have TypeScript types, many of the benefits of TypeScript get thrown away:
We could decide to use libraries solely based on if the library was written in TypeScript, but that’s generally not a good idea. We could be missing out on some great libraries, like React! So, what do we do if the library wasn’t written in TypeScript?
Option 2: Import @types/library-name
TypeScript provides another way to get types for third-party libraries, even if they aren’t written in TypeScript through a project called DefinitelyTyped.
These types are community maintained, which means that users of the library edit these types. It’s not exactly the wild west, though, because there are “type maintainers” that will review any changes to the types and merge the changes. Since these are community maintained, it’s very likely that these types are up to date, even if the official library doesn’t support types on their own.
In addition to being community maintained, DefinitelyTyped is officially “blessed” by the TypeScript team and Microsoft. This gives a few benefits:
- If type maintainers are particularly unresponsive, a stale PR to update types will be escalated to the TypeScript team and somebody there will review and merge the PR.
- The TypeScript team uses these library types as a “test suite” for the TypeScript language itself – this means that any breaking changes in the language are tested against existing types, and they will update the types if there are errors with newer versions of the language.
- The tooling around publishing types is very documented and streamlined, because it’s being maintained by a large company (Microsoft).
- DefinitelyTyped isn’t going to randomly disappear, because it’s core to the TypeScript language. It’s not some random user’s type definition monorepo.
Option 3: Define your own types
Creating our own types is pretty easy in TypeScript. By default, TypeScript will automatically pick up any type definitions that are inside the
@types directory in
node_modules, as well as any
index.d.ts files. This means you can create an
index.d.ts file wherever you want in your project, and TypeScript will use it. At Hopjump, we organize the types inside the
types directory in our application:
index.d.ts file, you can declare all the types you want for that package. Make sure to declare the module or namespace that you want the types to be defined inside. Read about this more in the TypeScript documentation. Here’s an example where we added types to
Because this code is wrapped in the
window-or-global namespace, whenever we import something from
window-or-global it will use these types.
Bonus: Monkeypatching types
Sometimes types in DefinitelyTyped are wrong. In those cases, we might want to fix those types by applying type monkeypatches. In general this is a bad idea – it’s much better to submit a PR to the DefinitelyTyped repo to fix the types for everyone, but sometimes this is useful.
The start is the same -- create an
index.d.ts file for the library you’re trying to patch. Inside that file you will declare the module as you did before, and stick the new types inside that module. However, since you want to “merge” these new types with the existing types, you should also import the original types inside that type file. Here’s an example for
Now, when you import
@testing-library/react-hooks it will include the original types, plus the new function defined.
Hopefully this clears up why third-party types are important, as well as clarify the various ways these types can make it into our application. Availability of robust type definitions may also be a criteria for evaluating a new third-party library, since good accurate types can help you develop faster. If a library doesn’t have types, TypeScript also gives you the flexibility to write them yourself and contribute them back to the community.