SoFunction
Updated on 2025-03-01

Detailed explanation of the syntax and scenario of TypeScript declaration file

Introduction

The declaration file is a file with . as the suffix. The developer writes a type declaration in the declaration file. TypeScript performs type checking based on the content of the declaration file. (Note that it is best not to have .ts files with the same name in the same directory. For example, and the module system cannot load the module only according to the file name)

Why do you need a declaration file? We know TypeScript does type checking based on type declarations, but there are some cases where there may be no type declaration:

  • Third-party packages, because the third-party packages are all JavaScript syntax, not TypeScript, and have no types.
  • Host environment extensions, such as some hybrid environments, have some bridge interfaces under window variables, and these interfaces have no type declaration.

If there is no type declaration, you cannot pass TypeScript type checking when using variables, calling functions, or instantiating classes.

The declaration file is for these situations. The developer writes a type declaration for third-party modules/a type declaration for host environment in the declaration file. Let TypeScript perform type checking normally.

In addition, declaration files can also be imported using the type definitions exposed therein.

In short, there are two uses for declaration files:

  • Imported through import, using the type definition and variable declarations exposed there.
  • Associate with related modules and make type declarations for modules.

For the second usage, how do declaration files be associated with related modules?

For example, if there is a third-party package name "foo", TypeScript will look for the declaration file in node_modules/foo based on its type and typing fields, and the declaration file found is used as the declaration file for the module; TypeScript will also look for the declaration file in the node_modules/@types/foo/ directory, and if it can be found, it will be used as the declaration file for the foo module; TypeScript will also look for the file for the foo module in our project. If the declaration module 'foo' statement is encountered, the declaration is used as the declaration of the foo module.

To sum up, TypeScript will read the specified declaration file in a specific directory.

  • In an internal project, TypeScript will read the file collection in it, and the declaration files in it will be processed.
  • Read the types or typing-specified files of each third-party package in node_modules.
  • Read the declaration file of the package with the same name in the @types directory.

The code in the declaration file will not appear in the final compilation result. After compilation, the converted JavaScript code will be output to the directory specified by the "outDir" option, and the declaration of the value used in the .ts module will be output to the directory specified by the "declarationDir".

The declaration statement in the .ts file will be removed after compilation, such as

declare let a: number;

export default a;

Will be compiled to

"use strict";
exports.__esModule = true;
exports["default"] = a;

The TypeScript compilation process not only translates the TypeScript syntax to ES6/ES5, but also outputs the types of values ​​used in the .ts file in the code to the specified declaration file. If you need to implement a library project, this feature is useful because the project using your library can use these declaration files directly without you writing a declaration file for your library.

grammar

content

A declaration in TypeScript creates one of three entities: namespace, type, or value.

The namespace is eventually compiled into a global variable, so we can also think that the declaration file actually creates two entities: type and value. That is, define a type or declare a value.

// Type Interfaceinterface Person {name: string;}

// Type Type aliastype Fruit = {size: number};

// Value Variabledeclare let a: number;

// Value Functiondeclare function log(message: string): void;

// Value Classdeclare class Person {name: string;}

// Value Enumdeclare enum Color {Red, Green}

// Value Namespacedeclare namespace person {let name: string;}

We noticed that the type can be defined directly, but the declaration of the value needs to be used with the declare keyword, because if the declaration keyword is not used, the declaration and initialization of the value are together, such as

let a: number;

// Compile asvar a;

However, the compilation result is that all declaration statements will be removed, and the initialization part will be retained, and the content in the declaration file only plays a declaration role, so it needs to be identified through declaration. This is just a declaration statement, which can be removed directly during compilation.

TypeScript also restricts that declare a value in the declaration file must be declared, otherwise it will be considered that there is initialized content, thereby reporting an error.

// 
let a: number = 1; // error TS1039: Initializers are not allowed in ambient contexts.

declare also allows it to appear in .ts files, but it is not generally done. You can declare and initialize a variable by using let/const/function/class in the .ts file. And after the .ts file is compiled, declare statements will also be removed, so declare statements are not needed.

Note that declare multiple variables with the same name will conflict

declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'.

declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'.

In addition to using declare to declare a value, declare can also be used to declare a module and global plug-in. Both of these usages are used to declare third-party packages in specific scenarios.

Declare module is used to make type declarations for a third-party module, such as a third-party package foo without type declarations. We can implement a declaration file in our project to allow TypeScript to identify module types:

// 
declare module 'foo' {
    export let size: number;
}

Then we can use:

import foo from 'foo';

();

In addition to being used to declare a module, declare module can also be used to implement declarations of module plug-ins. This will be introduced in the following sections.

declare global is used to declare third-party packages that extend globally, as described in the following sections.

Modular

Module Syntax

The modular syntax of the declaration file is similar to that of the .ts module, and is slightly different in some details. .ts exports modules (typescript will judge the type based on the exported module), and .exports the definition and declared values ​​of the type.

Declaration file can export types or declarations of values

// 

// Export value declarationexport let a: number;

// Export typeexport interface Person {
    name: string;
};

Declaration files can introduce other declaration files, or even other .ts files (because .ts files may also export types)

// 
export default interface Person {name: string}

// 
import Person from './person';

export let p: Person;

If the declaration file is not exported, the default is globally accessible

// 
interface Person {name: string}
declare let p: Person;

// 
let p1: Person = {name: 'Sam'};
(p);

If you use module export syntax (ESM/CommJS/UMD), it will not resolve to global (of course UMD can still be accessed globally).

// ESM

interface Person {name: string}

export let p: Person;

export default Person;
// CommonJS
interface Person {name: string}

declare let p: Person;

export = p;
// UMD
interface Person {name: string}

declare let p: Person;

export = p;
export as namespace p;

Note: The UMD package export as namespace syntax can only appear in the declaration file.

Three-slash command

The three-slash instruction in the declaration file is used to control the compilation process.

The triple slash directive can only be placed at the top of the file containing it.

If the --noResove compilation option is specified, the precompilation process ignores the three-slash instruction.

reference

The reference directive is used to indicate the dependency of the declaration file.

/// <reference path="..." />Other declaration files used to tell the compiler to depend on. During preprocessing of the compiler, the path-specified declaration file will be added. The path is relative to the file itself. Referring to non-existent files or references themselves will result in an error.

/// <reference types="node" /> is used to tell the compiler that it depends on node_modules/@types/node/. If your project relies on some declaration files in @types, this directive will be automatically added to the compiled declaration file to indicate that the declaration files in your project relies on the relevant declaration files in @types.

/// <reference no-default-lib="true"/>,

This involves two compilation options, --noLib. After setting this compilation option, the compiler will ignore the default library. The default library is automatically introduced when TypeScript is installed. This file contains various common environment declarations in the JavaScript runtime (such as window) and the existence of various common environment declarations in the DOM. But if your project runtime environment is very different from the standard browser runtime environment, you may need to exclude the default library. Once you exclude the default file, you can include a file named similarly in the compilation context, which TypeScript will extract for type checking.

Another compilation option is --skipDefaultLibCheck. This option will cause the compiler to ignore the declaration file containing the /// <reference no-default-lib="true"/> directive. You will notice that there will be this three-slash instruction at the top of the default library, so if the --skipDefaultLibCheck compilation option is used, the default library will also be ignored.

amd-module

The amd-module-related instructions are used to control the compilation process of packaging to the amd module.

///<amd-module name='NamedModule'/> This directive is used to tell the compiler to pass in the module name to the module packaged as AMD (the default is anonymous)

///<amd-module name='NamedModule'/>
export class C {
}

The compile result is

define("NamedModule", ["require", "exports"], function (require, exports) {
    var C = (function () {
        function C() {
        }
        return C;
    })();
     = C;
});

Scene

Here we call our project code "internal projects", the third-party modules introduced, including those introduced by npm and those introduced by script, called "external modules".

1. Write a declaration file to the internal project in the internal project

In your own project, write a declaration file to your module, such as the type shared by multiple modules, and you can write a declaration file. This scenario is usually unnecessary, generally a certain .ts file export declaration and other module reference declaration.

2. Write a statement file to a third-party package

Writing a declaration file to a third-party package is divided into writing a declaration file to a third-party package in an internal project and writing a declaration file to an external module in an external module.

Write a declaration file for the third-party package in the internal project: If the third-party package does not have a TS declaration file, in order to ensure that the third-party package can pass type checks and to safely use the third-party package, the declaration file for the third-party package needs to be written in the internal project.

Write a declaration file to the external module in an external module: If you are the author of a third-party library, whether you use the TypeScript development library or not, you should provide a declaration file so that projects developed with TypeScript can better use your library, then you need to write your declaration file.

The syntax of the declaration file in these two cases is similar, and there is only difference between individual declaration syntax and file processing:

  • When an internal project writes a declaration file to a third-party package, it can be named after ., and then configures it to include the files in the files and include. The declaration file of the external module needs to be packaged into the output directory, and the declaration file location is specified in the type field in it; or upload it to @types/<moduleName>, and the user installs the declaration file through npm install @types/<moduleName>. redux specifies declarationDir as ./types in it. TypeScript will package the declaration of the project into this directory. The directory structure is the same as the source code. Then all modules are exported at the redux source code entrance. Therefore, there is also an entry declaration file in the type directory, and it contains all export module declarations. Redux specifies the type field (or typings field) as the entry declaration file: ./types/. This realizes the automatic generation of the interface declaration file.
  • When an internal project writes a declaration file to a third party, if it is introduced through the npm module, such as import moduleName from 'path'; it needs to declare the module through the declare module '<moduleName>' syntax. The declaration files of external modules are all normal type export syntax (such as export default export =, etc.). If the declaration file is in @types, the declaration file with the same name as the module will be declared as the module type; if the declaration file is in a third-party package, the TypeScript module will declare it as the module module of this third-party package module. When the user imports and uses this module, TypeScript will perform type prompts and type checks based on the corresponding declaration file.

According to the type of third-party package, it can be divided into several types

Third-party library of global variables

We know that if the module export syntax is not used, the default declaration of the declaration file is global.

declare namespace person {
    let name: string
}

or

interface Person {
    name: string;
}

declare let person: Person;

use:

();

Statement of third-party library of modules that modify global variables

If a third-party package modifies a global module (this third-party package is a plug-in for this global module), the declaration file of this third-party package has different declaration methods according to the declaration of the global module.

If the global module uses namespace declaration

declare namespace person {
    let name: string
}

According to the declaration and merge principle of namespace, plug-in modules can be declared in this way

declare namespace person {
  	// Extended age attribute    let age: number;
}

If the global module uses global variable declaration

interface Person {
    name: string;
}

declare let person: Person;

According to the principle of declaration and merging of interfaces, plug-in modules can be declared in this way

interface Person {
  	// Extended age attribute    age: number;
}

The declaration method of the plug-in module of the above global module can be applied to the following scenarios:

  • The internal project uses plug-ins, but the plug-in does not have a declaration file. We can implement the declaration file ourselves in the internal project.
  • Write a declaration file to the plug-in module and publish it to @types.

If you are the author of a plug-in module, you want to reference the global module in the project and output the extended type to the declaration file for use by other projects. This can be done

// plugin/

// Note that this declaration will allow TypeScript to output the type declaration filedeclare global {
  	// Assuming that the global module uses global variables to declare    interface Person {
        age: number
    }
}

();

export {};

Note that it is also possible to write declare global in the declaration file, but you must add export {} or other module export statements at the end, otherwise an error will be reported. In addition, if declare global is written in the declaration file, it will not be output to the declaration file after compilation.

Modify window

The type of window is interface Window {...}, declared in the default library. If you want to extend window variables (such as some hybrid environments), you can do this.

// 

// Declare mergeinterface Window {
		bridge: {log(): void} 
}

// ordeclare global {
    interface Window {
        bridge: {log(): void} 
    }
}

or

// 

declare global {
    interface Window {
        bridge: {log(): void} 
    }
}

 = {log() {}}

export {};

ESM and CommonJS

Write a declaration file to the third-party ESM or CommonJS module, and use ESM export or CommonJS module syntax export, regardless of the module form of the third-party package.

See the following example

interface Person {
    name: string;
}

declare let person: Person;
 
export = person;
// Can also be usedexport default person;
import person from 'person';

();

The above declaration file is placed in node_modules/@types/person/, or in the position specified in the type or typings field of node_modules/person/.

If declared in your own project, you should use declare module implementation

declare module 'person' {
    export let name: string;
}

UMD

For UMD module, add the export as namespace ModuleName; statement based on the CommonJS declaration.

See the following ESM example

// node_modules/@types/person/
interface Person {
    name: string;
}

declare let person: Person;

export default person;

export as namespace person;

It can be accessed through import import

// src/
import person from 'person';

();

It can also be accessed globally

// src/

// Note that if you export using ESM, first access the defalut property when using globally.();

Here is a CommonJS example

// node_modules/@types/person/

interface Person {
    name: string;
}

declare let person: Person;

export default person;

export as namespace person;

Access can be imported through import

// src/

import person from 'person';

();

Also available for global access

// src/

();

Module plug-in

As we mentioned above, declare module can not only be used to declare a type to a third-party module, but also to declare a type to a plug-in module of a third-party module.

// types/moment-plugin/

// If the moment is defined as UMD, it does not need to be introduced and can be used directlyimport * as moment from 'moment';

declare module 'moment' {
    export function foo(): ;
}

// src/

import * as moment from 'moment';
import 'moment-plugin';

();

For example, the redux-thunk declaration file as a plug-in for redux is declared like this

// node_modules/redux-thunk/

declare module 'redux' {
		// declaration code......
}

Summarize

This is the article about the detailed explanation of the syntax and scenarios of TypeScript declaration files. For more related TS declaration files, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!