How do Typescript “types” packages and files work? (*.d.ts, @types/x)

Oleks Gorpynich
Level Up Coding
Published in
3 min readApr 1, 2024
Chat GPT Generated Image

You’ve probably seen *.d.ts files before or at least installed packages named similarly to @types/lodash. What exactly are these, and how do they work?

Regular typescript files

Before jumping into *.d.ts files and subsequently “types” packages; let’s understand how regular typescript files work and how they differ. Generally, typescript files end with a .ts extension and contain both executable Javascript code and type information.

// Type information
interface someType {
someField: string;
}
// JS Code
console.log("Hey there!")

The Typescript compiler compiles these files into .js files, which can then be run normally (got an article here for that ;) ).

Declaration files

Now onto .d.ts files. The Typescript compiler is designed to look for these files in order to understand the types behind regular .js files. I think this can be better explained with an example.
Say you have a file called test.js inside of your Typescript project.

// test.js
function add(a, b) {
return a + b;
}

function subtract(a, b) {
return a - b;
}

module.exports = { add, subtract };

When the Typescript compiler encounters this file, it will automatically look for the equivalent test.d.ts which might look something like this.

// example.d.ts
declare module 'example' {
export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;
}

The add and substract functions declared in test.js are typed in test.d.ts, and in such a way, any file importing these functions from test.js gets their type information. The key thing to realize here is that the name “test” must match in test.d.ts and test.js for Typescript to realize that these are “connected.”

Another key thing to know is that when compiling to .js files, the Typescript compiler can simply ignore the declarations in .d.ts files. These are there purely for development purposes. They are the difference between this.

My own image

And this

My own image

Notice how, in the second screenshot, the add function is typed properly. This is because I defined a “test.d.ts” file.

Types packages

Finally, everything makes sense with @types/x packages. Whenever you install a package using npm (or some other package manager) that’s written in JS, Typescript has no clue what the types are for the exports of that package. However, the Typescript compiler is set up to look for matching @types/x packages. So when you install “lodash,” for instance (a popular TS package), you can use lodash functions in your ts application, but in your editor, these functions will have arbitrary types. If you, however, also install @types/lodash, Typescript will understand that the typings from lodash are defined inside of @types/lodash. As you’d expect, @types/lodash just has a bunch of *.d.tsfiles.

Conclusion

Hope it all makes sense now!
To conclude, .d.ts files are basically a bridge between your Typescript project and .js files. They give the compiler context as to the type definitions of regular Javascript files. @types/x packages have a bunch of these .d.ts files for their corresponding Javascript package and allow you to use Javascript packages with types.

--

--

Aspiring developer. I write about AI, ML, occasional Web Dev, and Philosophy :) Short story account - https://medium.com/@oleksandr.gorpynich