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