Page Content

Tutorials

What is Declaration Files in TypeScript?

Ambient Declarations and Declaration Files in TypeScript

Based on the design objective of enabling the safe and simple consumption of pre-existing JavaScript libraries within a TypeScript project, the use of Ambient Declarations and Declaration Files (.d.ts) is a core component of TypeScript (TS). Developers can use this method to gradually switch JavaScript, CoffeeScript, and other compile-to-JS language projects to TypeScript.

To put it simply, ambient definitions give the TypeScript compiler the information that the actual code for a variable, function, class, or module already exists somewhere else. This code is usually written in plain JavaScript or supplied by the runtime environment (such as the browser or Node.js).

Declaration Files

The .d.ts file extension designates a declaration file. This extension shows that there is no runtime executable code in the file; it is just used as a type declaration. They are comparable to the header files in C/C++.

A declaration file’s primary function is to supply types without implementation. They are entirely contained within the TypeScript type system and do not affect JavaScript runtime. The TypeScript compiler does not produce any JavaScript code when it processes a .d.ts file.

Only types, interfaces, modules, and general type shapes are described in declaration files. Implementations for variables, classes, or functions, as well as default values for arguments, are prohibited.

This includes ambient declarations for basic JavaScript structures used in runtimes and the DOM (Document Object Model), such as the definition file lib.d.ts that comes with every TypeScript installation. With the help of these declarations, you may use native features like toString() on numbers defined in lib.d.ts to build type-checked code.

Defining Ambients using the Keyword

Declare is a crucial keyword for ambient element definition. “I swear that my JavaScript exports a class of this type” is what this declaration tells the compiler. TypeScript can do type verification and give tooling support (such as intellisense and refactoring) without producing any matching JavaScript code because of this promise to the compiler that the defined object will exist at runtime.

Declare must come before any top-level declaration of a runtime construct, such as a variable, function, or class, in a .d.ts file. This makes it clear to the compiler and the file creator that no implementation is needed, and as a result, no JavaScript will be released.

Comparing an ambient declaration with a conventional assignment, for instance:

Code in a .ts file (without declare):Code in a .d.ts file (with declare):
foo = 123; // Error: \foo` is not defined`declare var foo: any;
foo = 123; // allowed

In this second instance, TypeScript uses the ambient declaration to infer that foo exists globally at runtime, allowing for usage without compilation issues.

Ambient Declarations in Practice

Ambient declarations enable a number of constructs that are essential for interacting with external JavaScript code, especially those seen in contemporary NPM modules that do not have native TypeScript definitions or in vintage global libraries.

Ambient Variable Declarations (Globals)

Ambient variable declarations are used for traditional JavaScript libraries (such as the legacy jQuery $) that expose their API via global variables. With the help of these declarations, TypeScript is informed about a JavaScript global variable that can be used in any project file without the need for explicit imports.

While establishing a specific interface is normally advised for increased safety, declaring the global variable with any type is a straightforward, low-friction method of overcoming friction when integrating third-party JavaScript.

Declaring the jQuery Global Variable

An error will occur if you attempt to utilise jQuery’s $ globally without informing TypeScript that it exists since TypeScript will assume you have declared the variable someplace.

Friction (Code in index.ts without declaration file):

// index.ts
$('.awesome').show(); // Error: cannot find name `$` 

Ambient Variable Declaration (Code in jquery-globals.d.ts):

To quickly fix this, you tell TypeScript that $ exists:

// jquery-globals.d.ts
declare var $: any; 

Alternatively, defining an interface allows for easier future updates and provides type safety:

// jquery-globals.d.ts
declare type JQuery = any;
declare var $: JQuery; // Tells TS that '$' exists globally and has type JQuery 

Although it doesn’t generate any JavaScript output, the aforementioned declaration guarantees the presence of $ at runtime. This makes it possible for the TypeScript code to compile correctly.

Ambient Module Declarations (NPM Packages)

Ambient module declarations are required for third-party NPM packages imported using import or need if the package does not have its own type definitions.

Declare module'somePath‘ allows ambient modules to be defined globally for the project. This influences the type system of the module’s structure.

Shorthand Ambient Module for Lodash

If a module, such as lodash, is attempted to be imported without a declaration file, TypeScript will encounter an error as it is unable to locate the module.

Ambient Module Declaration

Although a shorter ambient module declaration notifies TypeScript of the module’s existence and permits imports, it does not offer useful type verification until internal types are declared.

// vendor.d.ts
declare module "lodash"; // Declares that a module named "lodash" exists 

Using the Declared Module

This module now allows the TypeScript file to be imported, and if no exports are specifically defined in the ambient module, the imported elements will be handled as type any.

// main.ts
import { flatten } from "lodash"; 
import * as _ from "lodash"; // Allowed due to ambient declaration 

_.someUntypedMethod(42); // This code is allowed but remains untyped (any) 

With the understanding that the imported components would be of type any, this short workaround is a low-friction method to continue migrating when definitions are absent. The module’s structure must be specified inside the ambient declaration if you want improved type safety.

Using Ambient Declaration for a JavaScript Library

Examine CalcLibrary.js, a fictitious JavaScript library that exposes the class TutorialPoint.Calculate using the doSum(limit) function.

Xisting JavaScript Library (CalcLibrary.js – Runtime Implementation)

This file is written in JavaScript and contains the real runtime functionality.

// CalcLibrary.js 
var TutorialPoint;
(function (TutorialPoint) {
    var Calc = (function () {
        function Calc() {
        }
        Calc.prototype.doSum = function (limit) {
            // Implementation logic is here (runtime code)
            var sum = 0;
            for (var i = 0; i <= limit; i++) {
                sum += i;
            }
            return sum;
        };
        return Calc;
    })();
    TutorialPoint.Calc = Calc;
})(TutorialPoint || (TutorialPoint = {}));
var test = new TutorialPoint.Calc();
// ... (omitted implementation details)

Ambient Declaration File (Calc.d.ts – Type Definition)

Without mentioning implementation specifics, a TypeScript programmer generates this file by defining the types that the external library exposes using the declare keyword. The code logic for doSum is not included in the file.

// Calc.d.ts
// This file contains TYPE DEFINITIONS ONLY — no JavaScript emitted.

declare module TutorialPoint {
    export class Calc {
        // Declaration: Promises 'doSum' exists, and specifies its signature
        doSum(limit: number): number; 
        // No function body provided, upholding the 'types without implementation' rule.
    }
}

In this declaration file, we affirm that the TutorialPoint module and the Calc class exist and that doSum expects a number input and returns a number.

Client TypeScript Code (CalcTest.ts)

The TypeScript file uses a triple-slash reference directive, which is a standard pattern for declarations in script mode, to consume the library’s types.

// CalcTest.ts
/// <reference path="Calc.d.ts" /> 

var obj = new TutorialPoint.Calc();

// Case A: Correct usage, matches definition
console.log(obj.doSum(10)); 

// Case B: Incorrect usage, parameter is a string, not a number 
obj.doSum("Hello");

Compiler Output and Results

The TypeScript compiler uses the data in Calc.d.ts to perform type verification while assembling CalcTest.ts.

Compilation Errors (Output of tsc CalcTest.ts):

Even though JavaScript is used for the function implementation, type safety is maintained by the compiler by providing an error for Case B, in which a string is supplied in place of the expected integer.

// Output (Conceptual Error):
// CalcTest.ts(8,11): error TS2345: Argument of type '"Hello"' is not 
// assignable to parameter of type 'number'.

Generated JavaScript (CalcTest.js):

Crucially, the resulting JavaScript file only contains the executable code from CalcTest.ts and not the definitions from Calc.d.ts.

// Generated by typescript 1.8.10
/// <reference path="Calc.d.ts" /> 
var obj = new TutorialPoint.Calc();
// obj.doSum("Hello"); // Commented out due to error
console.log(obj.doSum(10));

This shows that the TypeScript compiler provided type safety without adding any implementation code when this CalcTest.js is run alongside the actual CalcLibrary.js (for example, on an HTML page using script tags). The external JavaScript asset’s rigorous type verification was successfully provided via the ambient declaration.

Index