TypeScript official manual translation plan [XIII]: module

  • Note: at present, there is no Chinese translation of the latest official documents of TypeScript on the Internet, so there is such a translation plan. Because I am also a beginner of TypeScript, I can't guarantee the 100% accuracy of translation. If there are errors, please point them out in the comment area;
  • Translation content: the tentative translation content is TypeScript Handbook , other parts of the translated documents will be supplemented when available;
  • Project address: TypeScript-Doc-Zh , if it helps you, you can click a star~

Official document address of this chapter: Modules

modular

There are many ways to deal with modular code, and JavaScript has a long history in this regard. TypeScript was born in 2012 and supports many modular schemes. However, over time, the community and JavaScript specifications have reached a consensus on a scheme called es module (or ES6 module). You may have heard of its import/export syntax.

The ES module was incorporated into the JavaScript specification in 2015. By 2020, it has been supported by most web browsers and JavaScript runtime.

This manual will focus on the ES module and the popular module before it Exports = CommonJS of syntax. In the "references" section modular In this section, you can learn more about other modular solutions.

How are JavaScript modules defined

Like ECMAScript 2015, TypeScript treats any file containing top-level import or export as a module.

Conversely, a file that does not contain a top-level import or export declaration is treated as a script, and its contents can be accessed in the global scope (so it is also visible to the module).

The module executes in its own scope, not in the global scope. This means that variables, functions and classes declared in a module are not visible outside the module unless they are explicitly exported using one of the export methods. Conversely, in order to use variables, functions, classes, etc. exported from a different module, you also need to import them using one of the import methods.

Non module

Before we start, it's important to understand what TypeScript sees as a module. The JavaScript specification states that any JavaScript file that does not contain export or top-level await should be treated as a script, not a module.

Variables and types declared in a script file will be in the shared global scope, and usually you will use outFile The compile option combines multiple input files into one output file, or uses multiple < script > tags in the HTML file (in the correct order!) Load file.

If your file does not currently have any import or export, but you want to treat it as a module, you can add the following line of code:

export {};

This converts the file into a module that does not export anything. This syntax works regardless of your module goal.

Modules in TypeScript

There are three main things to consider when writing module based code in TypeScript:

  • Syntax: what syntax do I want to use for import and export?
  • Module resolution: what is the relationship between the module name (or path) and the file on the disk?
  • Module output target: what should the generated JavaScript module look like?

ES module syntax

A file can declare a main export through export default:

// @filename: hello.ts
export default function helloWorld() {
  console.log("Hello, world!");
}

Then import through the following statement:

import hello from "./hello.js";
hello();

In addition to the default export, you can also omit default and export multiple variables and functions directly with export:

// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
 
export class RandomNumberGenerator {}
 
export function absolute(num: number) {
  if (num < 0) return num * -1;
  return num;
}

You can import in another file through import syntax:

import { pi, phi, absolute } from "./maths.js";
 
console.log(pi);
const absPhi = absolute(phi);
         ^
   // const absPhi: number

Other import syntax

You can rename an import in the form of import {old as new}:

import { pi as π } from "./maths.js";
 
console.log(π);
            ^
     //(alias) var π: number
    // import π

You can mix the above syntax into a single import:

// @filename: maths.ts
export const pi = 3.14;
export default class RandomNumberGenerator {}
 
// @filename: app.ts
import RNGen, { pi as π } from "./maths.js";
 
RNGen;
  ^
//(alias) class RNGen
//import RNGen
 
console.log(π);
            ^
     // (alias) const π: 3.14
    // import π

With * as name, you can accept all exported objects and put them into a single namespace:

// @filename: app.ts
import * as math from "./maths.js";
 
console.log(math.pi);
const positivePhi = math.absolute(math.phi);
           ^
    // const positivePhi: number

Using import "./file", you can import only files in the current module without importing any variables in the file:

// @filename: app.ts
import "./maths.js";
 
console.log("3.14");

In this case, import will not do anything. But, math All code in TS will be executed, which may trigger side effects and affect other objects.

TypeScript specific ES module syntax

You can export and import types using the same syntax as JavaScript values:

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
 
export interface Dog {
  breeds: string[];
  yearOfBirth: number;
}
 
// @filename: app.ts
import { Cat, Dog } from "./animal.js";
type Animals = Cat | Dog;

TypeScript extends the import syntax to two uses, allowing it to declare type imports:

import type

This import statement can only import types:

// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
// 'createCatName' cannot be used as a value because it was imported using 'import type'.
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";
 
// @filename: valid.ts
import type { Cat, Dog } from "./animal.js";
export type Animals = Cat | Dog;
 
// @filename: app.ts
import type { createCatName } from "./animal.js";
const name = createCatName();

Inline type import

TypeScript 4.5 also allows a single import to use a type prefix to indicate that the imported reference is a type:

// @filename: app.ts
import { createCatName, type Cat, type Dog } from "./animal.js";
 
export type Animals = Cat | Dog;
const name = createCatName();

All of this allows non TypeScript translation tools such as Babel, swc, or esbuild to know which imports can be safely removed.

ES module syntax with CommonJS behavior

The ES module syntax of TypeScript can be directly associated with the requirements of CommonJS and AMD. In most cases, the import using ES module is the same as using require in the same environment, but this syntax can ensure that there is a one-to-one match between your TypeScript file and CommonJS output:

import fs = require("fs");
const code = fs.readFileSync("hello.ts", "utf8");

You can modular Learn more about this syntax in this reference section.

CommonJS syntax

CommonJS is the modular solution adopted by most npm packages. Even if you use ES module syntax when writing code, a brief understanding of how common JS syntax works will help simplify your debugging process.

export

By setting the exports property on a global object named module, you can export identifiers:

function absolute(num: number) {
  if (num < 0) return num * -1;
  return num;
}
 
module.exports = {
  pi: 3.14,
  squareTwo: 1.41,
  phi: 1.61,
  absolute,
};

These files can then be imported through the require declaration:

const maths = require("maths");
maths.pi;
      ^
   // any

Or you can use JavaScript's deconstruction syntax to import only part of the content:

const { squareTwo } = require("maths");
squareTwo;
    ^
  // const squareTwo: any

Interoperability between CommonJS and ES modules

Due to the difference between the default import and module namespace object import, there is a functional mismatch between CommonJS and ES modules. TypeScript provides a compilation option esModuleInterop To reduce the conflict between these two different sets of constraints.

Module resolution options for TypeScript

Module parsing is a process that extracts a string from the import or require declaration and determines the file indicated by the string.

TypeScript uses two parsing strategies: Classic and Node. The Classic policy is used to achieve backward compatibility when compiling options module This policy is adopted by default when it is not commonjs. The Node policy reproduces the Node JS works in common JS mode and provides additional functions ts and d.ts check.

There are also many TSConfig options that affect the module policy in TypeScript, including: moduleResolution,baseUrl,paths,rootDirs.

For more information on how these policies work, read Module analysis.

Module output options for TypeScript

There are two options that affect the JavaScript of the final output:

  • target Determines which JS features will be degraded (converted and run in older JavaScript runtime) and which JS features will be retained
  • module Determines the code used to interact between modules

Which to use target , depending on the features available to the JavaScript runtime that you want to execute TypeScript code. Such runtime can be: the oldest browser you support and the lowest version of node you want to run JS, or consider from the unique constraint of runtime, such as Electron.

All communication between modules takes place through a module loader, with the compile option module Will decide which one to use. At runtime, the module loader is responsible for locating and executing all dependencies of the module before executing the module.

For example, this is a TypeScript file using ES module syntax:

import { valueOfPi } from "./constants.js";
 
export const twoPi = valueOfPi * 2;

Here's how to use different module Compilation results after option:

ES2020
import { valueOfPi } from "./constants.js";
export const twoPi = valueOfPi * 2;
CommonJS
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.twoPi = void 0;
const constants_js_1 = require("./constants.js");
exports.twoPi = constants_js_1.valueOfPi * 2;
UMD
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./constants.js"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.twoPi = void 0;
    const constants_js_1 = require("./constants.js");
    exports.twoPi = constants_js_1.valueOfPi * 2;
});

Note: ES2020 is actually the same as the original index TS is the same.

You can TSConfig In the "module" section of this reference chapter, you can learn about all the available options and the corresponding output JavaScript code.

TypeScript namespace

TypeScript has its own module format called "namespace", which appeared earlier than ES module standard. This syntax provides many useful features to create complex definition files and is still widely used in DefinitelyTyped Yes. Although this syntax has not been deprecated, since the ES module already has most of the features of the namespace, we recommend that you use the ES module to be consistent with JavaScript. stay Namespace You can learn more about it in the reference section of.

Keywords: Front-end TypeScript

Added by monloi on Fri, 10 Dec 2021 13:43:06 +0200