In-depth understanding of TS module —-TypeScriptSeries Of Advanced chapter (9)

A module has its own scope, and its variables, functions, classes, and so on are not visible to the outside world unless it is exported in some form. Correspondingly, if you want to use its exported members outside of a module, you need to import them accordingly. Mutual import of modules requires the use of module loaders, which locate and execute the module’s dependencies before executing the module. The CommonJs module loader of Node.js and the RequireJs module loader of AMD module in Web applications are mainly used in JS. TS follows the ES2015 modularity scheme, where any file containing a top-level import or export statement is a module; Conversely, scripts that do not contain these statements at the top level are scripts whose contents are visible in the global scope.

[toc]

A, export

1. Export a declaration

Any declaration (variable, function, class, type, and so on) can be exported using the export keyword.

export let a = 1;
export const b = {a:2};
export function cc(){};
export class CC {};
export type A = string | number;
export interface Z {
    name: string
}
// Export the namespace
export namespace N {
    let a = 2
    export const b = {a: "namespace"}}// Export enumeration
export enum E {
    a,
    b,
    c,
}
Copy the code

2. Export statements

Note the difference between exporting a statement and exporting a declaration. An export declaration is when a value, type, or namespace is exported, and an export statement is exported after it is declared. The export statement can be renamed using AS for ease of use.

interface Person {
  name: string.age: number
}
const cc: Person = {
  name: "cc".age: 18,}export {cc};
export {Person as P};
Copy the code

3. The heavy export

A module can be re-exported in another module file. Modules often extend other modules and expose some of their features. Reexport is not used to import other modules locally, nor to introduce local variables.

// moduleA.ts
export class A {
  name: "cc"
}
export const age = 18

// Take moduleb. ts in the same directory as an example:
export { A } from "./moduleA"	// Re-export A in moduleA in moduleB
export * from "./moduleA"	// Re-export all members of moduleA in moduleB
export * as B from "./moduleA"	// Re-export all members of moduleA from moduleB and rename the module to N

// Show how to import app.ts
// Can be imported from moduleA
import {A} from "./moduleA"
// You can import the module from the moduleB and use the previous re-export methods
import {A} from "./moduleB"	// Only A can be imported because only A is re-exported
import {A, age} from "./moduleB"	// All members of moduleA can be imported from moduleB because * is exported
import {N} from "./moduleB"	// When reexporting, rename it to N, so only N can be imported and accessed through N.A and n.age
Copy the code

You can also use the syntax above to re-export multiple modules in one module, in a sense, to integrate the contents of multiple modules into one module.

// moduleC.ts
export {A} from "./moduleA"
export * as B from "./moduleB"
export * from "./moduleD"
export {D as C} from "./moduleD"
Copy the code

4. Export by default

Each module can optionally export a default item, which can be a value, type, or namespace, but only one default export item can be exported per module. The default export is marked by the default keyword and, accordingly, a different import form is used.

// Module.ts
export default 123;

// app.ts
import number from "Module"
number;	/ / 123
Copy the code

Functions and classes can be exported directly as defaults when declared.

// Func.ts
export default function getName(){
  // ...
}

// Class.ts
export default class Person {
  name: "cc"
}

// Import in app.ts accordingly
// You can choose any name when importing
import getName from "Func"
import P from "Class"
getName()
const cc = new P()
Copy the code

It is also possible to export values without the name:

export default "cc"
Copy the code

Second, import,

1. Import a single member exported from a module

It’s very simple:

import {N} from "./moduleB"
Copy the code

You can rename it using as:

export {D as C} from "./moduleD"
Copy the code

2. Import the entire module, store it in a variable, and access it through the variable

Use the import * as M from “Module” syntax to import the entire Module and store it in the variable M, through which you can then access the contents of the Module.

import * as m from "Module"
// Access the setName function exported from Module
m.setName("cc")
Copy the code

3. Import the side effects module

Side effects modules, usually set some global variables, without exporting, can be used by other modules. Its export members are not normally used, so export {} is an empty object. The code in the side effects module is executed where it was imported. In general, the side effects module is not recommended.

import "side-effect-module"
Copy the code

4. Import the type

Prior to TS 3.8, types could only be imported by import. But after that, you can specify the import type by importing Type.

import {PropType} from "Types"
import type {PersonType, FoodType} from "Interfaces"
Copy the code

You can also mix imports with values or namespaces.

import {names, type Names} from "moduleName"
Copy the code

5. export = xxxandimport xx = require()

Both CommonJs and AMD have an Exports object, which contains all exports of a module and also supports replacing exported objects with a custom single object. The default export is intended to replace this behavior. Unfortunately, CommonJs and AMD are incompatible in this regard. TS supports the use of export = **, which exports a single object from a module. It can be a class, function, namespace, enumeration, interface, etc. Instead, import xx = require().

// Person.ts
class Person {
	name: "cc"
}
export = Person

// app.ts
const cc = new Person()
cc.name	// "cc"
Copy the code

Module code generation

At compile time, the TS compiler will generate ES6, CommonJs, SystemJs, AMD, UMD, etc corresponding module code according to the type of module identified. The following is an official example of the TS module’s import and export code when compiled into various modular scenarios. To learn more about functions like DEFINE, require, and Register, consult the documentation for each modularity solution.

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

var 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") {
    var 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) {
  var mod_1 = require("./mod");
  exports.t = mod_1.something + 1;
});
Copy the code

System SimpleModule.js

System.register(["./mod"].function (exports_1) {
  var mod_1;
  var 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 var t = something + 1;
Copy the code

Optional module loading and advanced module loading scenarios

In some cases, we only want to load certain modules under certain conditions, namely dynamic module loading, which is also a good performance optimization. We can maintain type safety by using the typeof operator. In the type context, typeof gets the typeof a value, in this case the typeof the module.

CommonJs dynamic module loading:

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

AMD dynamic module loading:

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

SystemJs dynamic module loading:

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

Five, in otherJSUse modules in libraries

Some libraries are not written in TS, and we need to declare their exposed apis to describe the shape of the library. A declaration that does not define an implementation is called an ambient and is usually written in a.d.ts file.

1. Environment module

In Node.js, many tasks are done by modules, and we can define each module in its own.d.ts file using a top-level declaration everywhere, rather than writing them all in a larger.d.ts file. Therefore, we can use the construction of the environment namespace, but with the module keyword and the quoted module name, which is used for subsequent imports.

A simplifiednode.d.ts

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 var sep: string;
}
Copy the code

///
and import URL = require(“url”); Or import * as URL from “URL” to load the corresponding module.

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

2. Environment module shorthand

We can use shorthand declarations (not recommended) when we don’t want to spend time writing module declarations before using a new module, and the members imported from that module are of type ANY.

// decalarations.d.ts
declare module "hot-new-module";

// app.ts
import {A} from "hot-new-module";	// The type of A is any
Copy the code

3. A wildcard*Module declaration

SystemJs and AMD, etc., allow non-javascript content to be imported. This content is often prefixed or suffixed to indicate associated semantics. Therefore, using the wildcard module declaration saves you a lot of convenience in covering these situations.

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

The chestnuts above used *! Where * is the wildcard character, indicating any character content! Used here to separate semantics. After that, we can import any match *! The text or json. *.

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

4. UMDThe module

Some libraries are designed with multiple module loaders in mind, or as global variables to be used when no module loaders exist. These are known as UMD modules. These libraries can be used either through some form of import or directly through their exposed global variables.

The following uses export as Namespace mathLib(see declare.d.ts and declare for syntax) to expose a global variable mathLib through which module members can be accessed in scripts (note, not modules).

// mathLib.d.ts
// Export a function member
export function isPrime(x: number) :boolean;
// Define a global variable mathLib
export as namespace mathLib;
  
// app1.ts
import {isPrime} from "math-lib";
isPrime(3);
  
// app2.ts
// No import, use the global variable mathLib directly
mathLib.isPrime(3);
Copy the code

Guide to structured modules

1. Export content as close to the top as possible

When organizing the structure of your modules, consider the user experience. Too much nesting can make modules bulky. For example, export namespaces allow modules to be nested at multiple levels. The same is true of exporting a class with static methods. Unless you can significantly enhance the class’s expressiveness, you should consider simply exporting a helper function.

  • If only a single class or function is exported, export default is used by default

  • If you export multiple members, try to export them at the top level

export class SomeType {
  / *... * /
}
export function someFunc() {
  / *... * /
}
Copy the code
  • When a small number of members are imported, the names of the imports should be listed explicitly
import { SomeType, someFunc } from "./MyThings";
let x = new SomeType();
let y = someFunc();
Copy the code
  • When importing a large number of members, it is best to use the namespace import patternimport * as Name from "Module"
// largeModule.ts
export class Dog {... }export class Cat {... }export class Tree {... }export class Flower {... }// app.ts
import * as LM from "largeModule"
const Wangcai = new LM.Dog()
const Kitty = new LM.Cat()
const rose = new Tree()
Copy the code

2. Use re-export to expand the module

Sometimes we need to extend functionality on modules. Namespaces of the same name can be merged, but modules cannot. Therefore, it is common practice not to modify the original contents of the module, but to re-export an entity with new functionality, which can be renamed to the original module name using AS.

A Caculator. Ts calculator module, a Caculator class, and a test helper function.

export class Calculator {
  private current = 0;
  private memory = 0;
  private operator: string;
  // Reads a number in an input string
  protected processDigit(digit: string, currentValue: number) {
    if (digit >= "0" && digit <= "9") {
      return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0)); }}// Reads an operator in the input string
  protected processOperator(operator: string) {
    if (["+"."-"."*"."/"].indexOf(operator) >= 0) {
      returnoperator; }}// Calculate the operation
  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; }}/ / calculate
  private evaluate() {
    if (this.operator) {
      this.memory = this.evaluateOperator(
        this.operator,
        this.memory,
        this.current
      );
    } else {
      this.memory = this.current;
    }
    this.current = 0;
  }
  // Process a single character in the input string
  public handleChar(char: string) {
    // =
    if (char === "=") {
      this.evaluate();
      return;
    } else {
      // Otherwise, it is treated as a numeric character
      let value = this.processDigit(char, this.current);
      if(value ! = =undefined) {
        this.current = value;
        return;
      } else {
        // Not a numeric character, treated as an operator
        let value = this.processOperator(char);
        if(value ! = =undefined) {
          this.evaluate();
          this.operator = value;
          return; }}}// Also not an operator, throws an error
    throw new Error(`Unsupported input: '${char}'`);
  }
  // get the result
  public getResult() {
    return this.memory; }}// Auxiliary functions are used for testing
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

Here’s another example of a Caculator in use:

import { Calculator, test } from "./Calculator";
let c = new Calculator();
test(c, "1 + 2 * 33/11 ="); // Console prints 9
Copy the code

As you can see, the Caculator is ready to use. Now, it is extended at programmerCalculator. ts to support bases other than decimal:

// ProgrammerCalculator.ts

// Enter the Caculator
import { Calculator } from "./Calculator";
// Inheritance is implemented through the extends keyword
class ProgrammerCalculator extends Calculator {
  // Base numbers
  static digits = [
    "0"."1"."2"."3"."4"."5"."6"."Seven"."8"."9"."A"."B"."C"."D"."E"."F",];// public case is the parameter attribute
  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.`); }}// Override the parent class's method for handling numeric characters
  protected processDigit(digit: string, currentValue: number) {
    if (ProgrammerCalculator.digits.indexOf(digit) >= 0) {
      return (
        currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit) ); }}}// Export the inherited entity class and rename it Caculator
export { ProgrammerCalculator as Calculator };
// Re-export the auxiliary function from the original Caculator module
export { test } from "./Calculator";
Copy the code

Let’s test out the extended Caculator:

import { Calculator, test } from "./ProgrammerCalculator";
/ / binary
let c = new Calculator(2);
test(c, "001 + 010 ="); // Console prints 3
Copy the code

3. Do not use namespaces in modules

Modules have their own scope, and only exported members are visible. We should avoid using namespaces in modules as much as possible. In particular, we should avoid the following two red lines:

  • The only top-level export of a module is the namespace: export namespace Foo { ... }(Should be removedFooAnd move all its members up one level);
  • Export multiple files to a namespace with the same name:export namespace Foo { ... }These namespacesFooWill not merge between).

7. Module analysis

After studying modules for so long, you might wonder, like me, how the compiler parses modules.

(1) Relative import VS non-relative import of modules

When we import modules, we can divide them into relative import and non-relative import according to the written form of module path. Relative import, as the name implies, uses a relative path to import modules, including./,.. / and other characters representing relative paths, such as

  • import { getName } from "./Person";
  • import Person from "./Person";
  • import "./mod";

Non-relative import does not contain./,.. / and other characters that represent paths, such as:

  • import { getName } from "Person";
  • import Person from "Person";
  • import "mod";

Relative imports are resolved relative to the current file and cannot be resolved to environment module declarations. For our own modules, we should use relative imports. Non-relative imports are resolved based on baseUrl or path-mapping and can be resolved as environment module declarations. When importing external dependencies, use non-relative imports.

(II) Module parsing strategy:Classic vs Node

There are two types of module resolution strategies: Classic and Node. You can specify which policy to use by setting the moduleResolution field in tsconfig.json. The Node policy is used by default; Or specify –module commonjs on the command line, which is the Node policy, if module is set to something else (amd, ES2015, System, UMD, etc., Classic policy). The Node policy is the most commonly used and recommended.

1. Classicstrategy

At this stage, the Node policy is dominant, while the Classic policy exists mainly for backward compatibility. Let’s take a look at how the Classic policy resolves relative and absolute imports.

  • Relative to the import

    Relative imports are resolved by the imported file (not the imported file). For example, if you import: import {getName} from “./Person” in /root/src/folder/app.ts, ts will look for the following files as module Person:

    • /root/src/folder/Person.ts;
    • /root/src/folder/Person.d.ts;
  • Nonrelative import

    For non-relative imports, the compiler starts at the directory containing the import file and looks in the directory tree. For example, if you import: import {getName} from “Person” in /root/src/folder/app.ts, ts will look for the following files as the location of the module Person:

    • /root/src/folder/Person.ts;
    • /root/src/folder/Person.d.ts;
    • /root/src/Person.ts;
    • /root/src/Person.d.ts;
    • /root/Person.ts;
    • /root/Person.d.ts;
    • /Person.ts;
    • /Person.d.ts;

If any of the listed files exist, the compiler can continue compiling normally; otherwise, an error message will be displayed indicating that the related module could not be found.

2. Nodestrategy

Node policy is called Node policy because it mimics the module resolution mechanism of Node.js at runtime. In general, importing a module in Node.js is represented by calling the require function. Passing a relative or non-relative path to the require function determines whether an import is relative or non-relative. So, before we look at Node policy, let’s look at how ** node.js performs module resolution **, and then look at how Node policy is resolved in TS.

  • Relative path resolution

    The relative path is particularly simple. For example, if you import /root/ SRC /app.js const p = require(“./Person”), node.js will look for the following files as module Person:

    1. Check whether /root/src/person. js exists. If not, go to the next step.

    2. Look for package.json in the /root/src/Person directory. If so, look at the module corresponding to the main field in package.json. In this case, If in the/root/SRC/Person/package. The json file contains {” main “: “Lib/mainModule. Js”}, Node. Js will Person point to/root/SRC/Person/lib/mainModule js. Otherwise, proceed to Step 3;

    3. Find/root/SRC/Person, whether to have the index directory. The js, if there is/root/SRC/Person/index, js, the file will be implicitly by the corresponding module as the main field.

  • Nonrelative path resolution

    Non-relative paths are completely different. For non-relative path imports, Node.js looks it up in a special folder called node_modules. The folder can be at the same directory level as the imported file, or it can exist in a higher directory chain. For example, if you import /root/ SRC /app.js const p = require(“Person”), node.js will look for the following files as module Person:

    1. /root/src/node_modules/Person.js;
    2. /root/src/node_modules/Person/package.jsonIn themainFile corresponding to the field,{"main": "/xx/xxx.js"}And at this point,node.jswillPersonModule to/root/src/node_modules/Person/xx/xxx.js;
    3. /root/src/node_modules/Person/index.js;
    4. /root/node_modules/Person.js.(This step changes the search directory);
    5. /root/node_modules/Person/package.jsonIn themainFile corresponding to the field,{"main": "/xx/xxx.js"}And at this point,node.jswillPersonModule to/root/node_modules/Person/xx/xxx.js;
    6. /root/node_modules/Person/index.js;
    7. /node_modules/Person.js.(This step changes the search directory);
    8. /node_modules/Person/package.jsonIn themainFile corresponding to the field,{"main": "/xx/xxx.js"}And at this point,node.jswillPersonModule to/node_modules/Person/xx/xxx.js;
    9. /node_modules/Person/index.js;

These are the simplified steps for node.js module parsing. Let’s take a look at how the Node policy of TS mimics Node.js parses modules. The Node policy mimics node.js logic to locate TS modules at compile time. Note that when TS uses the Node policy for module resolution, the supported file extensions are. TS,.tsx,.d.ts. In addition, TS uses the types or Typings field in package.json instead of the main field in Package. json in Node.js to specify the location of module files.

  • Relative to the import

    For example, if you import Person from “./Person” in /root/src/app.ts, the ts will try to find the following files as the Person module:

    1. /root/src/Person.ts;
    2. /root/src/Person.tsx;
    3. /root/src/Person.d.ts;
    4. To view/root/src/Person/package.jsonIn thetypesProperty corresponding to the module file;
    5. /root/src/Person/index.ts;
    6. /root/src/Person/index.tsx;
    7. /root/src/Person/index.d.ts;

Note the order. According to Node.js logic, we look for Person.js first, then Person/package.json, and then Person/index.js.

  • Nonrelative import

    Similarly, non-relative imports follow node.js import logic. If you do a non-relative import in /root/src/app.ts: import {getName} from “person”, the compiler will look for the following files as the person module:

    • /root/src/node_modules/person.ts;
    • /root/src/node_modules/person.tsx;
    • /root/src/node_modules/person.d.ts;
    • /root/src/node_modules/person/package.jsonIn thetypesFile corresponding to field;
    • /root/src/node_modules/@types/person.d.ts; Pay attention to is@typesdirectory;
    • /root/src/node_modules/person/index.ts;
    • /root/src/node_modules/person/index.d.ts;
    • /root/node_modules/person.ts;
    • /root/node_modules/person.tsx;
    • /root/node_modules/person.d.ts;
    • /root/node_modules/person/package.jsonIn thetypesFile corresponding to field;
    • /root/src/node_modules/@types/person.d.ts; Pay attention to is@typesdirectory;
    • /root/node_modules/person/index.ts;
    • /root/node_modules/person/index.d.ts;
    • /node_modules/person.ts;
    • /node_modules/person.tsx;
    • /node_modules/person.d.ts;
    • /node_modules/person/package.jsonIn thetypesFile corresponding to field;
    • /root/src/node_modules/@types/person.d.ts; Pay attention to is@typesdirectory;
    • /node_modules/person/index.ts;
    • /node_modules/person/index.d.ts;

Scared by the numbers listed here? In fact, they are very regular! Also, node_modules/@types/xxx.d.ts is only listed above, but that’s not all. The compiler will not only find node_modules / @ types folder XXX. Which s file, if you cannot find the file, will still find node_modules / @ types/person/package. The json corresponding declaration file types in the field, And node_modules / @ types/person/index, which s.

(3) Additional module resolution mark

The project source layout sometimes does not match the output layout. The final output is often generated by a number of packaging steps, such as compiling.ts code into.js code and copying dependencies from different source locations into the same single output location. This often results in modules having different names at run time than in the files in which they were defined, and modules’ paths at compile time likely do not match their source paths.

Fortunately, TS has a series of additional flags that inform the compiler of the transformation that took place on the source and the final output. Of course, the compiler does not perform these transformations, but uses the information as a guide to parse the module into its source file.

1. baseUrl

Set baseUrl to tell the compiler where to find the module. All modules that are not relative imports are considered baseUrl related. The value of baseUrl can be specified from the command line. Of course, the more common and convenient setting is in tsconfig.json. If the value of baseUrl set in tsconfig.json is a relative path, then the relative path is the position relative to tsconfig.json. Note that relative imported modules are not affected by baseUrl.

2. pathsRoute map

Sometimes modules are not directly under baseUrl. At this point, you can use the Paths property to set the path mapping for these modules. Jquery is used as an example:

{
  "compilerOptions": {
    "baseUrl": ".".// When there is a Paths attribute, baseUrl must be specified
    "paths": {
      "jquery": ["node_modules/jquery/dist/jquery"] // Paths relative to baseUrl}}}Copy the code

The actual path of the paths property map is relative to baseUrl, that is, the actual path changes with baseUrl. In the above in chestnut, baseUrl is set to the current directory. That is the path of the jquery for. / node_modules/jquery/dist/jquery. Note that the value of each entry in ** Paths can be an array **, so you can configure multiple path mappings with the wildcard *.

{
  "compilerOptions": {
    "baseUrl": ".".// When there is a Paths attribute, baseUrl must be specified
    "paths": {
      "*": ["*"."customModules/*"]}}}Copy the code

There are two paths configured for *, and the high-speed compiler can find modules in different paths:

  • The path*Indicates that the original path is used without any changes/baseUrlNext look up the module;
  • The pathcustomModules/*Said incustomModulesPrefix in the directory, that is/baseUrl/customModulesNext look for modules.

3. rootDirsVirtual directory

Sometimes, compilations from multiple project sources are combined to produce a single output directory. You can think of this as a set of directory sources generating a virtual directory. Use rootDirs to specify a set of directories and place them in a single virtual directory at run time so that the compiler can parse the modules in this virtual directory as if they were actually merged into one directory. For example, for the following directory structure:

– the SRC

– views

– view1.ts (import “./template1”)

– view2. Ts

– generated

– templates

– views

— template1.ts (import “./view2”)

In the above directory structure, you can see that view1.ts and template1.ts import the same template1.ts and view2.ts in relative import mode, but there are no corresponding files in the same directory. In this case, rootDirs needs to be configured to make this path relative relationship valid.

{
  "compilerOptions": {
    "rootDirs": ["src/views"."generated/templates/views"]}}Copy the code

Through the above configuration, the multiple directories SRC/views and generated/templates/views will be combined into the same virtual directory at run time, thus its mutual import need to use the directory relative path. At the same level /. Whenever the compiler sees a relative module import in a subfolder in rootDirs, it looks for the imported module one by one in all subfolders under rootDirs. In addition, rootDirs can contain any number of any directory names, whether or not the directory exists.

4. traceResolutionTrace module parsing

As mentioned earlier, the compiler can access files outside the current folder when parsing a module. This makes it difficult to tell why a module failed to resolve or failed to resolve correctly. Here comes the traceResolution. Enabling traceResolution enables the compiler to track the resolution process. This can be enabled by configuring {“compilerOptions”: {“traceResolution”: true}} in tsconfig.json. The compiler stone console then prints out a series of compilations.

5. UsenoResolve

This flag is not normally used. Once enabled, the module to be parsed must be specified, for example on the command line:

npx tsc app.ts moduleA.ts --noResolve
Copy the code

The compiler will parse moduleA, but if other modules are imported in app.ts, such as import Person from “modulePerson”, an error will be raised because modulePerson.ts is not explicitly specified on the command line.

About TS module, share here, so far my TS advanced series has entered the end, I expect to write the final compilation principle, and then start to learn other knowledge. Learning time is always too short, so, if you have a chance, see you next article.