Welcome to wechat Public account: Front Reading Room

The module

A quick note on terminology: It’s important to note that terms have changed in TypeScript 1.5. “Internal modules” are now called “namespaces.” “External modules” are now referred to simply as “modules” to be consistent with ECMAScript 2015 terminology (that is, module X {is equivalent to the now recommended namespace X {).

introduce

Starting with ECMAScript 2015, JavaScript introduced the concept of modules. TypeScript follows this concept as well.

Modules execute in their own scope, not in the global scope; This means that variables, functions, classes, etc., defined in a module are not visible outside the module unless you explicitly export them using one of the forms export. Conversely, if you want to use variables, functions, classes, interfaces, etc. exported by other modules, you must import them, using either of the import forms.

Modules are self-declared; The relationship between two modules is established by using imports and exports at the file level.

Modules use module loaders to import other modules. At run time, the function of the module loader is to find and execute all dependencies of a module before executing the module code. The most well-known JavaScript module loaders are CommonJS for Node.js and require.js for Web applications.

TypeScript, like ECMAScript 2015, treats any file that contains a top-level import or export as a module. Conversely, if a file does not have a top-level import or export declaration, its contents are treated as globally visible (and therefore visible to the module).

export

Export declaration

Any declaration (such as a variable, function, class, type alias, or interface) can be exported by adding the export keyword.

Validation.ts

export interface StringValidator {
    isAcceptable(s: string) :boolean;
}
Copy the code

ZipCodeValidator.ts

export const numberRegexp = / ^ [0-9] + $/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5&& numberRegexp.test(s); }}Copy the code

Export statement

Export statements are handy because we may need to rename the part of the export, so the above example can be rewritten like this:

class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5&& numberRegexp.test(s); }}export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };
Copy the code

To export

We often extend other modules and export only parts of that module. The re-export function does not import that module or define a new local variable in the current module.

ParseIntBasedZipCodeValidator.ts

export class ParseIntBasedZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && parseInt(s).toString() === s; }}// Export the original validator but rename it
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
Copy the code

Or a module can wrap multiple modules and syndicate their exports using the syntax: export * from “module”.

AllValidators.ts

export * from "./StringValidator"; // exports interface StringValidator
export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
export * from "./ZipCodeValidator";  // exports class ZipCodeValidator
Copy the code

The import

Importing a module is as simple as exporting it. You can import an export from another module using one of the following import forms.

Import an export from a module

import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();
Copy the code

You can rename the imported content

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
Copy the code

Import the entire module into a variable and access the export part of the module through it

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
Copy the code

Imported modules with side effects

Although this is not recommended, some modules set some global state for other modules to use. These modules may not have any exports or the user may not care about its exports at all. To import such modules, use the following method:

import "./my-module.js";
Copy the code

The default is derived

Each module can have a default export. The default export is marked with the default keyword. And a module can only have one default export. A special import format is required to import the Default export.

Default export is very convenient. For example, a library like JQuery might have a default export of JQuery or, and we’ll probably use the same name JQuery or, and we’ll probably use the same name JQuery or, and we’ll probably export JQuery.

JQuery.d.ts

declare let $: JQuery;
export default $;
Copy the code

App.ts

import $ from "JQuery";

$("button.continue").html( "Next Step..." );
Copy the code

Class and function declarations can be directly marked as default exports. The names of classes and functions marked as default exports can be omitted.

ZipCodeValidator.ts

export default class ZipCodeValidator {
    static numberRegexp = / ^ [0-9] + $/;
    isAcceptable(s: string) {
        return s.length === 5&& ZipCodeValidator.numberRegexp.test(s); }}Copy the code

Test.ts

import validator from "./ZipCodeValidator";

let myValidator = new validator();
Copy the code

or

StaticZipCodeValidator.ts

const numberRegexp = / ^ [0-9] + $/;

export default function (s: string) {
    return s.length === 5 && numberRegexp.test(s);
}
Copy the code

Test.ts

import validate from "./StaticZipCodeValidator";

let strings = ["Hello"."98052"."101"];

// Use function validate
strings.forEach(s= > {
  console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});
Copy the code

The default export can also be a value

OneTwoThree.ts

export default "123";
Copy the code

Log.ts

import num from "./OneTwoThree";

console.log(num); / / "123"
Copy the code

Export = and import = require()

Both CommonJS and AMD environments have an exports variable that contains all exports of a module.

Both CommonJS and AMD exports can be assigned to an object, in which case it acts like the default export syntax in ES6 (export Default). Export Default syntax is not compatible with CommonJS and AMD exports, though it has similar effects.

To support CommonJS and AMD exports, TypeScript provides an export = syntax.

The export = syntax defines an export object for a module. The term object here refers to a class, interface, namespace, function, or enumeration.

To export a module using export =, you must use TypeScript’s specific syntax import module = require(“module”) to import the module.

ZipCodeValidator.ts

let numberRegexp = / ^ [0-9] + $/;
class ZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5&& numberRegexp.test(s); }}export = ZipCodeValidator;
Copy the code

Test.ts

import zip = require("./ZipCodeValidator");

// Some samples to try
let strings = ["Hello"."98052"."101"];

// Validators to use
let validator = new zip();

// Show whether each string passed each validator
strings.forEach(s= > {
  console.log(`"${ s }"-${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
});
Copy the code

Generating module code

For node.js (CommonJS), require.js (AMD), UMD, The SystemJS or ECMAScript 2015 Native Modules (ES6) module loads the code used by the system. For define, require, and register in generated code, refer to the documentation for the corresponding module loader.

The following example shows how names used in import and export statements are translated into the corresponding module loader code.

SimpleModule.ts

import m = require("mod");
export let t = m.something + 1;
Copy the code

AMD / RequireJS SimpleModule.js

define(["require"."exports"."./mod"].function (require.exports, mod_1) {
    exports.t = mod_1.something + 1;
});
Copy the code

CommonJS / Node SimpleModule.js

let mod_1 = require("./mod");
exports.t = mod_1.something + 1;
Copy the code

UMD SimpleModule.js

(function (factory) {
    if (typeof module= = ="object" && typeof module.exports === "object") {
        let v = factory(require.exports); if(v ! = =undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require"."exports"."./mod"], factory);
    }
})(function (require.exports) {
    let mod_1 = require("./mod");
    exports.t = mod_1.something + 1;
});
Copy the code

System SimpleModule.js

System.register(["./mod"].function(exports_1) {
    let mod_1;
    let t;
    return {
        setters: [function (mod_1_1) { mod_1 = mod_1_1; }].execute: function() {
            exports_1("t", t = mod_1.something + 1); }}});Copy the code

Native ECMAScript 2015 modules SimpleModule.js

import { something } from "./mod";
export let t = something + 1;
Copy the code

A simple example

To clean up the previous validator implementation, each module has only one named export.

To compile, we must specify a module target on the command line. For Node.js, use — Module commonjs; For require.js, use –module AMD. Such as:

tsc --module commonjs Test.ts
Copy the code

When compiled, each module generates a separate.js file. For example, with the reference tag, the compiler compiles the file based on the import statement.

Validation.ts

export interface StringValidator {
    isAcceptable(s: string) :boolean;
}
Copy the code

LettersOnlyValidator.ts

import { StringValidator } from "./Validation";

const lettersRegexp = /^[A-Za-z]+$/;

export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
        returnlettersRegexp.test(s); }}Copy the code

ZipCodeValidator.ts

import { StringValidator } from "./Validation";

const numberRegexp = / ^ [0-9] + $/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5&& numberRegexp.test(s); }}Copy the code

Test.ts

import { StringValidator } from "./Validation";
import { ZipCodeValidator } from "./ZipCodeValidator";
import { LettersOnlyValidator } from "./LettersOnlyValidator";

// Some samples to try
let strings = ["Hello"."98052"."101"];

// Validators to use
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();

// Show whether each string passed each validator
strings.forEach(s= > {
    for (let name in validators) {
        console.log(`"${ s }"-${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`); }});Copy the code

Optional module loading and other advanced loading scenarios

Sometimes you only want to load a module under certain conditions. To implement this and other advanced load scenarios in TypeScript, we can call the module loader directly and keep the type complete.

The compiler checks to see if each module is used in the generated JavaScript. If a module identifier is used only in the type annotation section and not at all in the expression, no code is generated for require this module. Omitting unused references is good for performance and also provides the ability to selectively load modules.

The core of this pattern is import id = require(“…”) The) statement gives us access to the exported type of the module. The module loader is invoked dynamically (via require), as in the if code block below. It takes advantage of the optimization of omitting references, so modules are only loaded when they are needed. For this module to work, it’s important to note that the identifier defined by import can only be used where the type is represented (not where it would be converted to JavaScript).

To ensure type safety, we can use the Typeof keyword. The typeof keyword, when used where a type is represented, results in a type value, in this case the typeof the module.

Example: Dynamic module loading in node.js

declare function require(moduleName: string) :any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
    let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
    let validator = new ZipCodeValidator();
    if (validator.isAcceptable("...")) { / *... * /}}Copy the code

Example: dynamic module loading in require.js

declare function require(moduleNames: string[], onLoad: (... args:any[]) = >void) :void;

import * as Zip from "./ZipCodeValidator";

if (needZipValidation) {
    require(["./ZipCodeValidator"].(ZipCodeValidator: typeof Zip) = > {
        let validator = new ZipCodeValidator.ZipCodeValidator();
        if (validator.isAcceptable("...")) { / *... * /}}); }Copy the code

Example: dynamic module loading in System.js

declare const System: any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
    System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) = > {
        var x = new ZipCodeValidator();
        if (x.isAcceptable("...")) { / *... * /}}); }Copy the code

Use other JavaScript libraries

To describe the types of libraries not written in TypeScript, we need to declare the apis that the libraries expose.

We call it a declaration because it’s not a concrete implementation of an “external program.” They are usually defined in.d.ts files. If you are familiar with C/C++, you can think of them as.h files. Let’s look at some examples.

An external module

Most of the work in Node.js is done by loading one or more modules. We can use the top-level export declaration to define a.d.ts file for each module, but it is better to write it in a large.d.ts file. We use a similar method to construct an external namespace, but here we use the Module keyword and enclose the name in quotes to facilitate import later. Such as:

node.d.ts (simplified excerpt)

declare module "url" {
    export interfaceUrl { protocol? :string; hostname? :string; pathname? :string;
    }

    export function parse(urlStr: string, parseQueryString? , slashesDenoteHost?) :Url;
}

declare module "path" {
    export function normalize(p: string) :string;
    export function join(. paths:any[]) :string;
    export let sep: string;
}
Copy the code

Now we can ///

node.d.ts and use import url = require(“url”); Or import * as URL from “URL” to load the module.

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
Copy the code

Short for external module

If you don’t want to spend time writing a declaration before using a new module, you can use a short form of the declaration so that you can use it quickly.

declarations.d.ts

declare module "hot-new-module";
Copy the code

All exports in the shorthand module will be any.

import x, {y} from "hot-new-module";
x(y);
Copy the code

Modules declare wildcards

Some module loaders such as SystemJS and AMD support importing non-javascript content. They usually use a prefix or suffix to indicate a particular load syntax. Module declaration wildcards can be used to represent these cases.

declare module "*! text" {
    const content: string;
    export default content;
}
// Some do it the other way around.
declare module "json! *" {
    const value: any;
    export default value;
}
Copy the code

Now you can just import a match! “” The text “or” json!” The content of.

import fileContent from "./xyz.txt! text";
import data from "json! http://example.com/data.json";
console.log(data, fileContent);
Copy the code

UMD module

Some modules are designed to be compatible with multiple module loaders, or do not use module loaders (global variables). They are represented by the UMD module. These libraries can be accessed either as imports or as global variables. Such as:

math-lib.d.ts

export function isPrime(x: number) :boolean;
export as namespace mathLib;
Copy the code

The library can then be used in a module by import:

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // Error: cannot use global definitions within a module.
Copy the code

It can also be used as a global variable, but only in a script (that is, a script file with no module imports or exports).

mathLib.isPrime(2);
Copy the code

Guide to creating a module structure

Export as much as possible at the top level

Users should make it easier to use the content your module exports. Too many layers of nesting can become unmanageable, so think carefully about how you organize your code.

Exporting a namespace from your module is an example of adding nesting. While namespaces sometimes have their uses, they add an extra layer when using modules. This is inconvenient and often unnecessary to the user.

Static methods of exported classes have the same problem – the class itself adds an extra layer of nesting. Unless it is easy to express or to use clearly, consider exporting a helper method directly.

If only a single class or function is exported, use export Default

Just as “export at the top” helps make it easier for users to use, so does a default export. If a module is designed to export specific content, you should consider using a default export. This makes importing and using modules a little easier. Such as:

MyClass.ts

export default class SomeType {
  constructor(){... }}Copy the code

MyFunc.ts

export default function getThing() { return 'thing'; }
Copy the code

Consumer.ts

import t from "./MyClass";
import f from "./MyFunc";
let x = new t();
console.log(f());
Copy the code

This is ideal for the user. They can name the imported module whatever type they want (t in this case) and do not need superfluous (.) To find related objects.

If you want to export multiple objects, export them at the top level

MyThings.ts

export class SomeType { / *... * / }
export function someFunc() { / *... * / }
Copy the code

Explicitly list the name of the import

Consumer.ts

import { SomeType, someFunc } from "./MyThings";
let x = new SomeType();
let y = someFunc();
Copy the code

Use the namespace import schema when you want to export a lot of content

MyLargeModule.ts

export class Dog {... }export class Cat {... }export class Tree {... }export class Flower {... }Copy the code

Consumer.ts

import * as myLargeModule from "./MyLargeModule.ts";
let x = new myLargeModule.Dog();
Copy the code

Expand with re-export

You may often need to extend the functionality of a module. A common pattern in JS is to extend the original object as JQuery does. As we mentioned earlier, modules are not merged like global namespace objects. The recommended solution is not to change the original object, but to export a new entity to provide new functionality.

Suppose a simple Calculator implementation is defined in the calculator. ts module. This module also provides a helper function to test the calculator’s functionality by passing in a series of input strings and giving the result at the end.

Calculator.ts

export class Calculator {
    private current = 0;
    private memory = 0;
    private operator: string;

    protected processDigit(digit: string, currentValue: number) {
        if (digit >= "0" && digit <= "9") {
            return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0)); }}protected processOperator(operator: string) {
        if (["+"."-"."*"."/"].indexOf(operator) >= 0) {
            returnoperator; }}protected evaluateOperator(operator: string.left: number.right: number) :number {
        switch (this.operator) {
            case "+": return left + right;
            case "-": return left - right;
            case "*": return left * right;
            case "/": returnleft / right; }}private evaluate() {
        if (this.operator) {
            this.memory = this.evaluateOperator(this.operator, this.memory, this.current);
        }
        else {
            this.memory = this.current;
        }
        this.current = 0;
    }

    public handleChar(char: string) {
        if (char === "=") {
            this.evaluate();
            return;
        }
        else {
            let value = this.processDigit(char, this.current);
            if(value ! = =undefined) {
                this.current = value;
                return;
            }
            else {
                let value = this.processOperator(char);
                if(value ! = =undefined) {
                    this.evaluate();
                    this.operator = value;
                    return; }}}throw new Error(`Unsupported input: '${char}'`);
    }

    public getResult() {
        return this.memory; }}export function test(c: Calculator, input: string) {
    for (let i = 0; i < input.length; i++) {
        c.handleChar(input[i]);
    }

    console.log(`result of '${input}' is '${c.getResult()}'`);
}
Copy the code

Now use the exported test function to test the calculator.

import { Calculator, test } from "./Calculator";


let c = new Calculator();
test(c, "1 + 2 * 33/11 ="); // prints 9
Copy the code

Now extend it to add support for input to other bases (other than decimal), let’s create Programmercalculer.ts.

ProgrammerCalculator.ts

import { Calculator } from "./Calculator";

class ProgrammerCalculator extends Calculator {
    static digits = ["0"."1"."2"."3"."4"."5"."6"."Seven"."8"."9"."A"."B"."C"."D"."E"."F"];

    constructor(public base: number) {
        super(a);const maxBase = ProgrammerCalculator.digits.length;
        if (base <= 0 || base > maxBase) {
            throw new Error(`base has to be within 0 to ${maxBase} inclusive.`); }}protected processDigit(digit: string, currentValue: number) {
        if (ProgrammerCalculator.digits.indexOf(digit) >= 0) {
            return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit); }}}// Export the new extended calculator as Calculator
export { ProgrammerCalculator as Calculator };

// Also, export the helper function
export { test } from "./Calculator";
Copy the code

The new ProgrammerCalculator module exports a similar API to the original Calculator module, but without changing the objects in the original module. Here is the code to test the ProgrammerCalculator class:

TestProgrammerCalculator.ts

import { Calculator, test } from "./ProgrammerCalculator";

let c = new Calculator(2);
test(c, "001 + 010 ="); // prints 3
Copy the code

Do not use namespaces in modules

The first time you move into a mode-based development mode, you may always be tempted to wrap your exports in a namespace. A module has its own scope, and only exported declarations are visible outside the module. With this in mind, namespaces are of little value when working with modules.

In terms of organization, namespaces are convenient for grouping logically related objects and types in a global scope. For example, in C#, you would find all collection types from system.collections. By organizing types hierarchically in namespaces, users can easily find and use those types. However, the module itself already exists in the file system, which is necessary. We have to find them by path and file name, which already provides a logical organization. We can create the /collections/generic/ folder and put the modules in there.

Namespaces are important for resolving name conflicts in the global scope. For example, you can have a My. Application. Customer. AddForm and My. Application. Order. AddForm – two types of the same name, but different namespaces. However, this is not a problem for modules. There is no reason for two objects in a module to have the same name. From a module usage point of view, consumers pick out the name they use to refer to the module, so there’s no reason to have the same name.

For more information about modules and namespaces, see Namespaces and Modules

Danger signals

The following are the danger signals on the module structure. Re-check to make sure you are not using namespaces for modules:

  • The top-level declaration of the file is export namespace Foo {… } (delete Foo and move everything up one layer)
  • File has only one export class or export function (consider using export Default)
  • Multiple files have the same export namespace Foo {at the top (don’t think these will be merged into one Foo!).

Welcome to wechat Public account: Front Reading Room