TypeScript (twelve) modules

Table of contents

introduction

d.ts declaration file

declare keyword

global declaration

global declaration

Global declarations are generally used as

function declaration

Using declare in .ts

external module (file module)

module keyword module

declare module

Module declaration method

module wildcard

module export

module nesting

module scope

module alias

Internal modules (namespaces)

Namespace OR module?

global keyword

Summarize

reference article


introduction

This article is included in the series of TypeScript knowledge summary articles , corrections are welcome! 

Splitting a large program into multiple small, functionally independent modules is an indispensable part of development. One of the cores of developing complex programs is to make them uncomplicated. Modular development can improve code maintainability, reusability, scalability, and testability, thereby improving development efficiency and code quality. TypeScript follows the module concept of JS. In the previous article, I introduced two types of compatibility in the Node environment. By the way, I mentioned the commonly used module import and export methods: Commonjs and ES Module. These two methods are called external modules in TS. In addition, TS also includes internal modules and global modules. This article will introduce them one by one .

d.ts declaration file

At the same level of the compiled JS file, you can often see the declaration file with the .d.ts suffix, which is used to describe the type information that already exists in the code or provide a type declaration for it. For example, when using a third-party library, you may not be able to find the corresponding type information, so TS provides the concept of a declaration file, which enables developers to describe the library to achieve static type hints or TS checks. After the declaration file is compiled, no js code will be generated.

Generally, the declaration file does not contain executable statements, only type or variable declarations. During development, a file like global.d.ts (self-named) is usually created in the root directory of the project to describe global types, variables, functions, classes, etc.

declare keyword

declare is a mechanism for describing information outside of TS files. Its function is to tell TS that a certain type or variable already exists. We can use it to declare global variables, functions, classes, interfaces, type aliases, class attributes or methods, and modules and namespaces that will be introduced later.

global declaration

Usually, the declare keyword is used in the global.d.ts file for global declaration, so that all files in the directory can be directly accessed

global declaration

  • declare var name: variable
  • declare const / let name: ES6 variables
  • declare function name: method
  • declare class name: class
  • declare enum name: enumeration
  • declare module name: module
  • declare namespace name: namespace
  • declare interface name: interface
  • declare type name: type alias

Global declarations are generally used as

  • describe a global variable or type
  • Describe the type of third-party library
  • Describe the global module

For example, create a new global.d.ts in the root directory of the project for the global declaration of the variable type, then modify the configuration include in tsconfig to ["global.d.ts", "src"], and create a new index.ts in any directory of the project

// global.d.ts
declare interface IAnimal {
    name: string
    age?: number
}

declare let animal: IAnimal
// src/index.ts
animal = {
    name: "阿黄"
}

You can see that the type of animal in the index.ts file is the variable declared in global.d.ts, and the two are associated

 

tips : All types ( except type aliases and interfaces ) and variables in the declaration file (d.ts) must be defined using declare, or exported using export, otherwise the following error will be thrown: The top-level declaration in the .d.ts file must start with the "declare" or "export" modifier.

In addition, types defined using type aliases and interfaces can be directly accessed globally without declaration

declare type str = string
// 相当于type str = string

function declaration

Referring to the above definition method, we can declare a function in the declaration file, and then implement or overload the function before using it

// global.d.ts
declare function add(a: number, b: number): number;

// src/index.ts
function add(a: number, b: number) {
    return a + b
}
console.log(add(1, 2));// 3

// src/main.ts
function add(a: number, b: number, c: string) {
    return a + b + c
}
console.log(add(1, 2, "3"));// 33

Overloading function declarations

Using declare in .ts

When we introduced the attribute decorator , we used the declare keyword. At that time, we did not specify the reason for using it. Here we will analyze it in detail. First, we will post a similar code

class Animal {
    name?: string;
}

Properties defined in ES2022 and later classes will persist in the class after compilation, just like

class Animal {
    name;
}

However, using declare in a .ts file will only be regarded as a type or variable definition, and finally compiled in the declaration file .d.ts, not in a .js file, just like the following class

// index.ts
declare class Animal {
    name?: string;
}

// index.js
// 空文件

// index.d.ts
declare class Animal {
    name?: string;
}

Through the feature of declare, we can use the declare keyword to specify it as a variable of the declared type when defining an attribute or method in a class, which will not appear in .js, effectively solving the previous problem

// index.ts
class Animal {
   declare name?: string;
}

// index.js
class Animal {}

external module (file module)

In TS, a module can exist in the form of a single file, which is the same as JS. It is exported and imported through the two keywords of export and import. For the corresponding introduction, please refer to the ESM part of this article , or you can use the module keyword to define the module.

Slightly different from JS, TS contains interfaces and type aliases, and we can also export the corresponding type aliases through the export type type name, such as

// src/main.ts
export type IAnimal = {
    name: string
    color?: string
}

// src/index.ts
import { IAnimal } from './main'
const animal: IAnimal = {
    name: "阿黄",
};

tips : Using the export keyword in a .d.ts file will make this file a module ( this is very important , a declaration file (d.ts) is not a global declaration file (only use declare to declare the type) but a file module (use export and other keywords to export)), for example, we change the above global file and index into the following code

// global.d.ts
type IAnimal = {
    name: string
    color?: string
}
export {}

// index.ts
const animal: IAnimal = {
    name: "阿黄",
};

At this time, using the global IAnimal directly will throw an error

IAnimal must be exported using export and imported into the type in that module

module keyword module

declare module

In addition to the above usage, we can use the module keyword to define multiple modules in a file, such as

// global.d.ts
declare module 'global_type' {
    export type IAnimal = {
        name: string
    }
    export type ICat = {
        name: string
    }
}
declare module 'global_type1' {
    export type IDog = {
        name: string
    }
}

// index.ts
import type { IAnimal, ICat } from "global_type"
import type { IDog } from 'global_type1'
const animal: IAnimal = {
    name: "阿黄",
};
const dog: IDog = animal
const cat: ICat = animal

Every content defined using module is a module

Module declaration method

TS supports two module systems, CommonJS and ESM, so that there are two ways to declare modules, using strings and variable names respectively

The writing method of CommonJS follows the relative or absolute path of the matching file. Usually, the module name is used as a string literal. This method does not support exporting modules. It only allows the use of declare to define global modules, and it needs to be imported when using it.

// global.d.ts 
declare module "global_type" {
    export type IAnimal = {
        name: string
    }
}

// src/index.ts
import * as global_type from "global_type"
const myObject: global_type.IAnimal = {}

The writing method of ESM is the same as that of defining variables, using variable names to match identifiers to import modules. This method has the same effect as defining namespaces. Global modules defined using ESM can be used directly without importing.

// global.d.ts 
declare module global_type {
    export type IAnimal = {
        name?: string
    }
}

// src/index.ts
const myObject: global_type.IAnimal = {}

module wildcard

We may see code similar to the following in tools such as webpack or vite, which is unique to the CommonJS module system

declare module '*.type' {
    export type IDog = {
        name: string
    }
}

The *.type wildcard is used in this code, which matches all modules ending with .type. Importing a file of type .type will have the type IDog

import type { IDog } from 'global_type.type'
const animal: IDog = {
    name: "阿黄",
};
const dog: IDog = animal

module export

Modules defined using module follow global declarations, and can also be exported using export and used in other files. This method is unique to the ESM module system

// global.d.ts 
export module global_type {
    export type IAnimal = {
        name: string
    }
    export class Animal implements IAnimal {
        name: string
    }
}

// index.ts
import { global_type } from '../global'
const myObject: global_type.IAnimal = new global_type.Animal();

module nesting

Module nesting can cope with more complex structures, avoid naming conflicts, and global pollution. Writing modules in modules does not require the declare and export keywords, and the modules are exported by default.

// global.d.ts 
declare module global_type {
    export module IAnimalModule {
        export let animal: IAnimal
        export type IAnimal = {
            name: string
        }
    }
    module IDogModule {
        let dog: IDog
        type IDog = {
            name?: string
        }
    }
}
// src/index.ts
let animal: global_type.IAnimalModule.IAnimal = global_type.IAnimalModule.animal
let dog: global_type.IDogModule.IDog = global_type.IDogModule.dog

module scope

When modules are nested, we can refer to the scope in the curly brackets of module { } or namespace { } as the scope of the module. The module types in the scope can be accessed. At this time, if you use export {} in the module to export empty objects, the current module will be regarded as a file module (this is similar to the tips we mentioned above for converting global files to module files). You need to use export to export local types, variables, and modules. Otherwise, the export operation will not be performed by default and become a private module:

// global.d.ts 
declare module global_type {
    module IAnimalModule { // 局部模块,只能在global_type中使用
        let animal: IAnimal
        type IAnimal = {
            name?: string
        }
    }
    export { }
}

// src/index.ts
let animal: global_type.IAnimalModule.IAnimal// “global_type”没有已导出的成员“IAnimalModule”

At this point, you need to manually export the modules in the module, or add them to the export object

export module IAnimalModule {
    let animal: IAnimal
    type IAnimal = {
        name?: string
    }
}
// 或者
export { IAnimalModule }

module alias

The alias of a module is a way to simplify its access. You can use the import keyword to define an alias, and then use this alias to replace the original name, such as

// global.d.ts 
declare module global_type {
    export type IAnimal = {
        name?: string
    }
    export class Animal implements IAnimal { }
}

// src/index.ts
import Ani = global_type.Animal
import IAni = global_type.IAnimal
const ami: IAni = new Ani()

Internal modules (namespaces)

Because the namespace (namespace) before version 1.5 is the module concept proposed by TS, and the module mentioned above is the ES standard in JS, TypeScript makes a distinction between the two, so it is called an internal module. It is also a member of the modularization mechanism. Its function is to encapsulate global variables, functions, classes, etc. in a space to prevent naming pollution and conflicts. The official recommendation is to use namespace instead of the ESM module system writing method of module, so when using the variable writing method of the module, TS will convert it into namespace

 

Remember the export method of the ESM module system mentioned above? We replace the module keyword in the code with namespace, and you're done. The namespace has the characteristics of the variable writing method of the module.

namespace global_type {
    export type IAnimal = {
        name: string
    }
    export class Animal implements IAnimal {
        name: string
    }
}
const myObject: global_type.IAnimal = new global_type.Animal();

Let's put the code in a file and parse the compiled JS file

var global_type;
(function (global_type) {
    class Animal {
        name;
    }
    global_type.Animal = Animal;
})(global_type || (global_type = {}));
const myObject = new global_type.Animal();

It can be seen that the use of iife in the code generates a private scope and defines an empty object, and puts the variables exported by the namespace into the object.

Think about a question, must a namespace be defined by a code block?

The answer is no, similar to function overloading, the definition of namespace allows declaration merging, merging namespace objects with the same name (we will talk about it later in the article, leave a suspense)

Namespace OR module?

Modules are suitable for scenarios that require dynamic loading, packaging, and code reuse, such as Node.js applications, web applications, and npm packages. Modules can leverage module loaders (such as CommonJS/Require.js) or runtimes that support ES modules to manage dependencies and import and export. Modules are part of the ES6 standard and are the recommended way to organize modern code.

Namespaces are suitable for scenarios where variables, functions, classes, interfaces, etc. need to be defined globally, such as using the <script> tag in a web application to introduce all dependent HTML pages. Namespaces can avoid global variable naming conflicts, but it also increases the difficulty of component dependencies, especially in large applications.

global keyword

The declare global keyword in TS is used to add a type or variable declaration to the global scope

In my understanding, the use of global should be similar to that of module and namespace. Follow the gourd drawing, use global { ... } as a code block to represent the global scope, and the types and variables defined in it should be available anywhere

// global.d.ts 
declare global {
    type IDog = {
        name: string
    }
    let animal: IDog
}

// src/index.ts
animal = {
    name: "阿黄"
}

However, things are not so simple, it will prompt: the expansion of the global scope can only be directly nested in external modules or ambient module declarations.

In the official d.ts template , the last line of the code does an export operation

Why?

In fact, this is to solve a problem with the TS compiler. As we mentioned above, if the declaration file d.ts does not use export to export or does not use declare to define types and variables, then the compiler will report an error, and using declare global is different from using declare module, declare namespace, declare type, and declare interface. The definitions of these types or modules are export declarations, and no additional export is required . Therefore, when defining (it is more appropriate to say that it is more appropriate to expand) the global global, an empty object will be exported at the bottom of the code to declare that this file is exported

We mentioned above that using the export keyword will make the declaration file a module; if declare global and export { } are used, then our global variables and types do not need to use declare declarations, and can be directly written in the declare global code block

Here is a complete example

// global.d.ts 
declare global {
    type IDog = {
        name: string
    }
    let animal: IDog
}
export { }

// src/index.ts
animal = {
    name: "阿黄"
}

Summarize

The article ends here. This article briefly introduces the use of modules in TS, and introduces the declaration file, declare keyword, file module, namespace and global global keyword. At the same time, it puts forward its own views and problems encountered. I hope it can help you.

Thank you for reading to the end. If you think the article is not bad, please like, collect, follow and support the blogger. If you have any questions about the content of the article, please leave a message in the comment area or private message, thank you!

reference article

Namespaces and Modules - TypeScript Manual

TypeScript: Documentation - Global .d.ts

TypeScript: Documentation - Modules

Typescript already has a module system, why do we need a namespace? - Know almost

Guess you like

Origin blog.csdn.net/time_____/article/details/129443454