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.