'About "*.d.ts" in TypeScript

I am curious about .d.ts declaration files because I am new to the TypeScript programming language. I was told by someone that .d.ts files are are similar to .h header files in the C & C++ programming languages, however, the .d.ts files don't seem to work quite the same. Currently, I am failing to understand how to properly use the .d.ts files. It would appear that I cant add my .js or .ts files to the .d.ts files, so the only way my project will work is if it contains all three file types. That seems like a lot of files. To help me better understand how the .d.ts files are related to JavaScript & TypeScript, I have some questions I would like to ask.


  1. What is the relationship between the three files? the relationship between them?

  2. How can I use the *.d.ts file? Does it mean I can delete the *.ts file permanently?

  3. If so, how can the *.d.ts file know which JS file is mapping to itself?


It would be very nice if someone can give me an example.



Solution 1:[1]

The "d.ts" file is used to provide typescript type information about an API that's written in JavaScript. The idea is that you're using something like jQuery or underscore, an existing javascript library. You want to consume those from your typescript code.

Rather than rewriting jquery or underscore or whatever in typescript, you can instead write the d.ts file, which contains only the type annotations. Then from your typescript code you get the typescript benefits of static type checking while still using a pure JS library.

This works thanks to TypeScript's constraint of not letting you add the ".ts" extension at the end of the import statement. Because of that, when you reference some file, let's say, my-module.js, if there is a my-module.d.ts next to it, then TypeScript will include its content:

src/
  my-module.js
  my-module.d.ts
  index.ts

my-module.js

const thing = 42;

module.exports = { thing };

my-module.d.ts

export declare const thing: number;

index.ts

import { thing } from "./my-module"; // <- no extension

// runtime implementation of `thing` is taken from ".js"
console.log(thing); // 42

// type declaration of `thing` is taken from ".d.ts"
type TypeOfThing = typeof thing; // number

Solution 2:[2]

d stands for Declaration Files:

When a TypeScript script gets compiled there is an option to generate a declaration file (with the extension .d.ts) that functions as an interface to the components in the compiled JavaScript. In the process the compiler strips away all function and method bodies and preserves only the signatures of the types that are exported. The resulting declaration file can then be used to describe the exported virtual TypeScript types of a JavaScript library or module when a third-party developer consumes it from TypeScript.

The concept of declaration files is analogous to the concept of header file found in C/C++.

declare module arithmetics {
    add(left: number, right: number): number;
    subtract(left: number, right: number): number;
    multiply(left: number, right: number): number;
    divide(left: number, right: number): number;
}

Type declaration files can be written by hand for existing JavaScript libraries, as has been done for jQuery and Node.js.

Large collections of declaration files for popular JavaScript libraries are hosted on GitHub in DefinitelyTyped and the Typings Registry. A command-line utility called typings is provided to help search and install declaration files from the repositories.

Solution 3:[3]

I learned the following from having to painstakingly map a JavaScript project using .d.ts files.

Using a .d.ts file to map JavaScript requires that you name your .d.ts files the same as you name your .js files. Each .js file needs to be kept inline (kept in the same directory) with the .d.ts file that has the same name. Point the JS/TS code that needs the types from the .d.ts files to the .d.ts files.

eg: test.js and test.d.ts are in the testdir/ folder, then you import it like this in a react component:

import * as Test from "./testdir/test";

The .d.ts file was exported as a namespace like this:

export as namespace Test;
    
export interface TestInterface1{}
export class TestClass1{}

Solution 4:[4]

Worked example for a specific case:

Let's say you have my-module that you're sharing via npm.

You install it with npm install my-module

You use it thus:

import * as lol from 'my-module';

const a = lol('abc', 'def');

The module's logic is all in index.js:

module.exports = function(firstString, secondString) {

  // your code

  return result
}

To add typings, create a file index.d.ts:

declare module 'my-module' {
  export default function anyName(arg1: string, arg2: string): MyResponse;
}

interface MyResponse {
  something: number;
  anything: number;
}

Solution 5:[5]

Like @takeshin said .d stands for declaration file for typescript (.ts).

Few points to be clarified before proceeding to answer this post -

  1. Typescript is syntactic superset of javascript.
  2. Typescript doesn't run on its own, it needs to be transpiled into javascript (typescript to javascript conversion)
  3. "Type definition" and "Type checking" are major add-on functionalities that typescript provides over javascript. (check difference between type script and javascript)

If you are thinking if typescript is just syntactic superset, what benefits does it offer - https://basarat.gitbooks.io/typescript/docs/why-typescript.html#the-typescript-type-system

To Answer this post -

As we discussed, typescript is superset of javascript and needs to be transpiled into javascript. So if a library or third party code is written in typescript, it eventually gets converted to javascript which can be used by javascript project but vice versa does not hold true.

For ex -

If you install javascript library -

npm install --save mylib

and try importing it in typescript code -

import * from "mylib";

you will get error.

"Cannot find module 'mylib'."

As mentioned by @Chris, many libraries like underscore, Jquery are already written in javascript. Rather than re-writing those libraries for typescript projects, an alternate solution was needed.

In order to do this, you can provide type declaration file in javascript library named as *.d.ts, like in above case mylib.d.ts. Declaration file only provides type declarations of functions and variables defined in respective javascript file.

Now when you try -

import * from "mylib";

mylib.d.ts gets imported which acts as an interface between javascript library code and typescript project.

Solution 6:[6]

This answer assumes you have some JavaScript that you don't want to convert to TypeScript, but you want to benefit from type checking with minimal changes to your .js. A .d.ts file is very much like a C or C++ header file. Its purpose is to define an interface. Here is an example:

mashString.d.ts

/** Makes a string harder to read. */
declare function mashString(
    /** The string to obscure */
    str: string
):string;
export = mashString;

mashString.js

// @ts-check
/** @type {import("./mashString")} */
module.exports = (str) => [...str].reverse().join("");

main.js

// @ts-check
const mashString = require("./mashString");
console.log(mashString("12345"));

The relationship here is: mashString.d.ts defines an interface, mashString.js implements the interface and main.js uses the interface.

To get the type checking to work you add // @ts-check to your .js files. But this only checks that main.js uses the interface correctly. To also ensure that mashString.js implements it correctly we add /** @type {import("./mashString")} */ before the export.

You can create your initial .d.ts files using tsc -allowJs main.js -d then edit them as required manually to improve the type checking and documentation.

In most cases the implementation and interface have the same name, here mashString. But you can have alternative implementations. For example we could rename mashString.js to reverse.js and have an alternative encryptString.js.

Solution 7:[7]

I guess I could add my 2 cents here

// somefile.d.ts
export type SomeItem = {
  weight: number
}

export type ItemStorage = {
  name: string
  items: SomeItem[]
}
// simefile.js
// @ts-check
/** @typedef { import('./somefile.d.ts').SomeItem } SomeItem */
/** @typedef { import('./somefile.d.ts').ItemStorage } ItemStorage */

/**
 * @param { StorageItem } item
 */
function doSomething(item) {
  item. // intellisense
  // your code here
}

The neat thing about this is that one can gradually incorporate types in an existing javascript project.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dima Parzhitsky
Solution 2 iliketocode
Solution 3 j D3V
Solution 4 Boris Yakubchik
Solution 5 Michal
Solution 6
Solution 7 redeemefy