Creating a TypeScript declaration file for a JavaScript library

Previously, I had the issue that the greek-utils library I was using didn’t offer TypeScript types. Today I’m going to build this declaration file.

Hello everyone, a couple of weeks ago, I wrote a post on migrating my bot project from JavaScript to TypeScript. In that post, I had the issue that the greek-utils library I was using didn’t offer TypeScript types. I also mentioned that maybe creating a declaration file for that library would be a good idea for a post. So here we are! Today I’m going to build this declaration file.

What is a declaration file?

At this moment, some of you may wonder, “What is a declaration file exactly?” you probably know it has something to do with types, but you might not be one hundred percent sure. Don’t worry, I’m about to shed some light on this matter, and hopefully, I will answer all your questions.

As you already know, TypeScript is a superset of JavaScript, which means that if you take a JavaScript file and rename it to .ts, it will be valid TypeScript code, and it will compile (with strict off) to JavaScript again.
Even though these two are highly compatible, they are still two different languages. To go from the TypeScript domain to the JavaScript domain, all we have to do is compile the TypeScript code. The JavaScript ecosystem is a very rich one, and it’s a massive advantage if we can leverage it in the TypeScript language. So the next question is, how can we go from the JavaScript domain to the TypeScript domain? That is why we need declaration files to enable this transformation from JavaScript to TypeScript.

You will find a declaration file as index.d.ts in the root folder of a JavaScript library, e.g., the Axios library. However, not all libraries include the declaration file in the library repository, e.g., the express library, and this is where DefinitelyTyped comes in. DefinitelyTyped is a monorepo that consists of a vast number of typings for different npm packages. The idea is that it is the central place where somebody can publish typings for a library without necessarily being the owner of that library. This way, you don’t need the maintainer’s approval to port a JavaScript library into TypeScript. If you ever installed a package like @types/express, then you used DefinitelyTyped. I will show you how to publish typings in DefinitelyTyped on the next post.

How can I create a declaration file?

At this point, you should understand the purpose of a declaration file, so now is the time to create one. I found out that the official TypeScript documentation provides some templates for you to create your declaration files. However, I understand that if this is the first time you create one, it might be overwhelming. So I will present how I go about it step by step.

In my previous post to stop the complaints of the TypeScript compiler, we created a fake declaration file regarding the greek-utils library that had just this line:

declare module 'greek-utils'

But let's see how this library looks. If we go to the library source, we observe that it's quite small, making it a perfect example to create a declaration file for it.

How to structure the declaration file

Below you can see the public interface of the library:

greekUtils = {
  toGreek: function (text, ignore) {},
  toGreeklish: function (text, ignore) {},
  toPhoneticLatin: function (text, ignore) {},
  toTransliteratedLatin: function (text, ignore) {},
  sanitizeDiacritics: function (text, ignore) {}
};

module.exports = greekUtils;

The declaration file needs to describe this public interface. As we can see, the library exports five functions. The structure is quite straightforward, and we don't have nested structures. Following the module template, this is how our declaration file looks like:

// In declaration file
export function toGreek(text: string, ignore?: string): string;
export function toGreeklish(text: string, ignore?: string): string;
export function toPhoneticLatin(text: string, ignore?: string): string;
export function toTransliteratedLatin(text: string, ignore?: string): string;
export function sanitizeDiacritics(text: string, ignore?: string): string;

Then to use it in our TypeScript application:

// In app
import * as greekUtils from 'greek-utils';

greekUtils.toGreeklish(text)

That's it! Now we can use it! However, before I show you how to use it, I think it's an excellent opportunity to show you some extra things.
Let's imagine for a while that the structure of the library looked a bit different:

greekUtils = {
  transformations: {
    toGreek: function (text, ignore) {},
    toGreeklish: function (text, ignore) {},
    toPhoneticLatin: function (text, ignore) {},
    toTransliteratedLatin: function (text, ignore) {},
    sanitizeDiacritics: function (text, ignore) {}
  },
};

module.exports = greekUtils;

How would you structure your index.d.ts now? I would suggest you use the module template and try to build it on your own.
So did you find it difficult? If your declaration file looks like this:

export module transformations {
  export function toGreek(text: string, ignore?: string): string;
  export function toGreeklish(text: string, ignore?: string): string;
  export function toPhoneticLatin(text: string, ignore?: string): string;
  export function toTransliteratedLatin(text: string, ignore?: string): string;
  export function sanitizeDiacritics(text: string, ignore?: string): string;
}

Then you got it right! If you used namespace instead of the module, you also got it right, but you might want to look at this part of the docs regarding the differences of modules and namespaces. If you have more questions, you can also check out the documentation on declaration files.

How to use the declaration file

So what's left to do is to configure our project to use the declaration file.
The easiest but also the sloppiest way to add our declaration file is to put it in the node_modules/@types directory under the module's name. In our case, the index.d.ts would be under node_modules/@types/greek-utils/index.d.ts.

However, as I already said, this way is not ideal, because you manually add things to your project dependencies. We can achieve the same result using the tsconfig.json of the project.

To do that, we need to create a types directory in the project root and put the declaration file under a greek-utils folder. In the end, the path to the declaration file should look like types/greek-utils/index.d.ts. Then to include the declaration file in our project, we have to use the baseUrl and paths compiler options. So the tsconfig.json will look like this:

{
  "compilerOptions": {
    "target": "es6",
    "baseUrl": "./",
    "paths": {
      "greek-utils" : ["types/greek-utils"]
    },
    "module": "commonjs",
    "allowJs": true,
    "outDir": "./dist",
    "sourceMap": true,
    "strict": false,
  },
  "include": ["./src/**/*"],
}

Note: Initially, I tried to use the typedRoots compiler option for the same purpose. However, this is not the right way to do it.

Summary

So that was it for today! By now, it should be clear to you what is the purpose of a declaration file in TypeScript, and you should be able to create one if necessary. In the next post, we will see how you can publish your declaration file to the DefinetelyTyped repository.

I hope you found this post informative! Stay tuned!

EDIT 05.07.2020:
While I was preparing the pull request, I found out about dts-gen, a tool that generates a declaration scaffold for a library or a file. Feel free to check it out if you don't have a declaration file yet.

Subscribe to Backend Definite

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe