Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

TypeScript module resolution


May 07, 2021 TypeScript


Table of contents


TypeScript module resolution

This section assumes that you already know some basics about modules Read the module documentation to learn more.

Module resolution refers to a process that the compiler uses to find out the specific values referenced by an import operation. S uppose you have an import import { a } from "moduleA" In order to check for any a the compiler needs to know exactly what it means, and it needs to check its moduleA

At this point, the compiler wonders, moduleA the shape of moduleA?" This sounds simple, and moduleA be in .ts / .tsx files you write or .d.ts

First, the compiler tries to locate the file that represents the import module. C ompilation follows one of two strategies: Classic or Node. These policies tell the compiler where to look for moduleA

If they fail and if the module name is non-relative "moduleA" the compiler attempts to locate an external module declaration. We'll talk about non-relative imports next.

Finally, if the compiler still can't parse the module, it logs an error. In this case, the error error TS2307: Cannot find module 'moduleA'.

Relative vs. Non-relative module import

Depending on whether the module reference is relative or non-relative, the module import resolves in different ways.

Relative imports are / , . ./ or ../ At the beginning. Here are some examples:

  • import Entry from "./components/Entry";
  • import { DefaultHeaders } from "../constants/http";
  • import "/mod";

All other forms of import are treated as non-relative. Here are some examples:

  • import * as $ from "jQuery";
  • import { Component } from "angular2/core";

Relative import resolution comes relative to the file that imported it, and cannot be resolved to an external module declaration. You should use relative imports for your own modules to ensure their relative position at runtime.

The module resolution policy

There are two module resolution strategies available: Node and Classic. Y ou can --moduleResolution to specify which one to use. The default is Node.

Classic

This policy was previously the default resolution policy for TypeScript. Now, it exists primarily for backward compatibility.

The relatively imported module is parsed relative to the file in which it was imported. So the import { b } from "./moduleB" in /root/src/folder/A.ts uses the following lookup process:

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts

For imports of non-relative modules, the compiler traverses the parent directory in turn from the directory that contains the imported files, trying to locate the matching claims file.

Like what:

There is a non-relative import import for moduleB import { b } from "moduleB" which /root/src/folder/A.ts and is "moduleB"

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts
  3. /root/src/moduleB.ts
  4. /root/src/moduleB.d.ts
  5. /root/moduleB.ts
  6. /root/moduleB.d.ts
  7. /moduleB.ts
  8. /moduleB.d.ts

Node

This parsing strategy attempts to mimic the Node .js module resolution mechanism at runtime. The complete Node .js algorithm can be found .js node module documentation.

Node .js how the module is resolved

In order to understand the parsing steps followed by TypeScript compilation, it is important to understand .js module first. T ypically, imports in .js are made require function call. Node .js behave differently require on whether the require is a relative path or a non-relative path.

The relative path is simple. F or example, suppose you have a file path /root/src/moduleA.js that contains an import var x s var x = require("./moduleB"); Node .js this import in the following order:

  1. Treat /root/src/moduleB.js to check for the existence.

  2. Treat /root/src/moduleB directory to check if it package.json file and it "main" module. In our case, if Node.js discovers a file /root/src/moduleB/package.json contains the { "main": "lib/mainModule.js" } then Node.js /root/src/moduleB/lib/mainModule.js

  3. Treat /root/src/moduleB a directory and check to see if it index.js file. This file is implicitly used as the "main" module under that folder.

You can read .js documentation for more details: file modules and folder modules.

However, the resolution of non-relative module names is a completely different process. N ode will find your module node_modules in a special folder. node_modules be in the same directory as the current file, or in the upper directory. Node traverses the parent directory, looking for node_modules it finds the module to load.

Or use the example above, but assume /root/src/moduleA.js is using a non-relative path to import var x s var x = require("moduleB"); Node resolves moduleB in the following order until there is a match.

  1. /root/src/node_modules/moduleB.js
  2. /root/src/node_modules/moduleB/package.json (if the "main"
  3. /root/src/node_modules/moduleB/index.js

  4. /root/node_modules/moduleB.js
  5. /root/node_modules/moduleB/package.json (if the "main"
  6. /root/node_modules/moduleB/index.js

  7. /node_modules/moduleB.js
  8. /node_modules/moduleB/package.json the "main"
  9. /node_modules/moduleB/index.js

Note that .js in steps (4) and (7) will jump up the level directory.

You can read .js Documentation for more details: loading modules node_modules

How TypeScript parses modules

TypeScript is an analysis strategy .js mode runtime to locate module definition files during the compilation phase. T herefore, TypeScript adds the extensions of the TypeScript source files .ts .tsx and .d.ts Node resolution logic. At the same time, TypeScript uses the field "typings" package.json to represent a meaning similar "main" - the compiler uses it to find the "main" definition file to use.

For example, there is an import import { b } from "./moduleB" /root/src/moduleA.ts is "./moduleB"

  1. /root/src/moduleB.ts
  2. /root/src/moduleB.tsx
  3. /root/src/moduleB.d.ts
  4. /root/src/moduleB/package.json the "typings"
  5. /root/src/moduleB/index.ts
  6. /root/src/moduleB/index.tsx
  7. /root/src/moduleB/index.d.ts

Recall that Node .js find moduleB.js file, then package.json and index.js

Similarly, a non-relative import follows the resolution logic .js Node, first finding the file and then the appropriate folder. /src/moduleA.ts import { b } from "moduleB" in the following search order:

  1. /root/src/node_modules/moduleB.ts
  2. /root/src/node_modules/moduleB.tsx
  3. /root/src/node_modules/moduleB.d.ts
  4. /root/src/node_modules/moduleB/package.json (if the "typings"
  5. /root/src/node_modules/moduleB/index.ts
  6. /root/src/node_modules/moduleB/index.tsx
  7. /root/src/node_modules/moduleB/index.d.ts

  8. /root/node_modules/moduleB.ts
  9. /root/node_modules/moduleB.tsx
  10. /root/node_modules/moduleB.d.ts
  11. /root/node_modules/moduleB/package.json (if the "typings"
  12. /root/node_modules/moduleB/index.ts
  13. /root/node_modules/moduleB/index.tsx
  14. /root/node_modules/moduleB/index.d.ts

  15. /node_modules/moduleB.ts
  16. /node_modules/moduleB.tsx
  17. /node_modules/moduleB.d.ts
  18. /node_modules/moduleB/package.json (if the "typings"
  19. /node_modules/moduleB/index.ts
  20. /node_modules/moduleB/index.tsx
  21. /node_modules/moduleB/index.d.ts

Don't be intimidated by the number of steps here - TypeScript just jumps up the catalog twice in steps (8) and (15). This is no more complicated .js in The Node.

Use --noResolve

Normally, the compiler resolves the module import before it starts compiling. Whenever it successfully parses import on import file, the file is added to a list of files for later processing by the compiler.

--noResolve tells the compiler not to add any files that are not uploaded on the command line to the compilation list. The compiler will still try to parse the module, but as long as the file is not specified, it will not be included.

Like what

app.ts

import * as A from "moduleA" // OK, moduleA passed on the command-line
import * as B from "moduleB" // Error TS2307: Cannot find module 'moduleB'.
tsc app.ts moduleA.ts --noResolve

Compile app.ts with --noResolve

  • ModuleA may have moduleA because it is specified on the command line.
  • ModuleB moduleB found because it was not passed on the command line.

Problems

Why are exclude in the exclude list still used by the compiler?

tsconfig.json the folder into an "engineering" if “exclude” “files” tsconfig.json and all subdirectdirecters are in the compilation list. If you want to exclude some files with “exclude” or even if you want to specify a list of all the files to compile, “files”

Some are tsconfig.json I t does not involve module resolution discussed above. If the compiler recognizes that a file is a module import target, it is added to the compilation list, regardless of whether it is excluded or not.

Therefore, to exclude a file from the compilation list, you need to exclude it from all files that import it or that use /// <reference path="..." /> instruction.