关于typescript:TypeScript-里的-module-解析过程-Module-Resolution

34次阅读

共计 6961 个字符,预计需要花费 18 分钟才能阅读完成。

Module Resolution

模块解析是编译器用来确定导入所指内容的过程。思考像 import {a} from “moduleA”; 这样的导入语句。为了查看 a 的任何应用,编译器须要确切地晓得它代表什么,并且须要查看它的定义 moduleA。

此时,编译器会问“moduleA 的形态是什么?”尽管这听起来很简略,但 moduleA 能够在您本人的 .ts/.tsx 文件之一中定义,或者在您的代码所依赖的 .d.ts 中定义。

首先,编译器会尝试定位一个代表导入模块的文件。为此,编译器遵循两种不同策略之一:classical 或 Node。这些策略通知编译器去哪里寻找 moduleA。

如果这不起作用并且模块名称是非相干的(在“moduleA”的状况下,它是),那么编译器将尝试定位一个环境模块申明。接下来咱们将介绍非绝对导入。

最初,如果编译器无奈解析模块,它将记录一个谬误。在这种状况下,谬误相似于谬误 TS2307:找不到模块 ‘moduleA’。

Relative vs. Non-relative module imports

依据模块援用是绝对的还是非绝对的,模块导入的解析形式不同。

绝对导入是以 /、./ 或 ../ 结尾的导入。一些例子包含:

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

任何其余导入都被认为是非相干的。一些例子包含:

  • import * as $ from “jquery”;
  • import {Component} from “@angular/core”;

绝对导入是绝对于导入文件解析的,无奈解析为环境模块申明。您应该对本人的模块应用绝对导入,以保障在运行时放弃其绝对地位。

Module Resolution Strategies

有两种可能的模块解析策略:Node 和 Classic。您能够应用 –moduleResolution 标记来指定模块解析策略。如果未指定,则 –module commonjs 默认为 Node,否则默认为 Classic(包含 –module 设置为 amd、system、umd、es2015、esnext 等时)。

留神:Node 模块解析是 TypeScript 社区中最罕用的,举荐用于大多数我的项目。如果您在 TypeScript 中遇到导入和导出的解析问题,请尝试设置 moduleResolution: “node” 以查看它是否解决了问题。

Classical 解析策略

这已经是 TypeScript 的默认解析策略。现在,这种策略次要是为了向后兼容。

绝对导入将绝对于导入文件进行解析。因而,在源文件 /root/src/folder/A.ts 中 import {b} from “./moduleB” 将导致以下查找:

  • /root/src/folder/moduleB.ts
  • /root/src/folder/moduleB.d.ts

然而,对于非相干模块导入,编译器从蕴含导入文件的目录开始沿着目录树向上走,试图找到匹配的定义文件。

例如:

在源文件 /root/src/folder/A.ts 中,对 moduleB 的非绝对导入,例如 import {b} from “moduleB”,将导致尝试应用以下地位来定位 “moduleB”:

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

Node 模式

这种解析策略试图在运行时模拟 Node.js 模块解析机制。Node.js 模块文档中概述了残缺的 Node.js 解析算法。

Node.js 如何解析模块?

要理解 TS 编译器将遵循哪些步骤,理解 Node.js 模块十分重要。传统上,Node.js 中的导入是通过调用名为 require 的函数来执行的。Node.js 采取的行为将依据 require 是相对路径还是非相对路径而有所不同。

相对路径相当简略。例如,让咱们思考一个位于 /root/src/moduleA.js 的文件,其中蕴含 import var x = require(“./moduleB”); Node.js 按以下程序解析该导入:

  • 询问名为 /root/src/moduleB.js 的文件是否存在。
  • 询问文件夹 /root/src/moduleB 是否蕴含指定“主”模块的名为 package.json 的文件。在咱们的示例中,如果 Node.js 发现文件 /root/src/moduleB/package.json 蕴含 {“main”: “lib/mainModule.js”},那么 Node.js 将援用 /root/src/moduleB/lib/mainModule.js。
  • 询问文件夹 /root/src/moduleB 是否蕴含名为 index.js 的文件。该文件被隐式视为该文件夹的“主”模块。

然而,对非相干模块名称的解析以不同的形式执行。Node 将在名为 node_modules 的非凡文件夹中查找您的模块。node_modules 文件夹能够与以后文件位于同一级别,也能够在目录链中的更高级别。Node 将沿着目录链向上遍历,查看每个 node_modules,直到找到您尝试加载的模块。

依照咱们下面的例子,思考 /root/src/moduleA.js 是否应用非相对路径并导入 var x = require(“moduleB”);。而后,Node 会尝试将 moduleB 解析为每个地位,直到一个地位失常工作。

(1) /root/src/node_modules/moduleB.js
(2) /root/src/node_modules/moduleB/package.json(如果它指定了“main”属性)
(3) /root/src/node_modules/moduleB/index.js

(4) /root/node_modules/moduleB.js
(5) /root/node_modules/moduleB/package.json(如果它指定了“main”属性)
(6) /root/node_modules/moduleB/index.js

(7) /node_modules/moduleB.js
(8) /node_modules/moduleB/package.json(如果它指定了“main”属性)
(9) /node_modules/moduleB/index.js

请留神,Node.js 在步骤 (4) 和 (7) 中跳转了一个目录。

您能够在 Node.js 文档中浏览无关从 node_modules 加载模块的更多信息。

How TypeScript resolves modules

TypeScript 将模拟 Node.js 运行时解析策略,以便在编译时定位模块的定义文件。为此,TypeScript 在 Node 的解析逻辑上笼罩了 TypeScript 源文件扩展名(.ts、.tsx 和 .d.ts)。TypeScript 还将应用 package.json 中名为“types”的字段来反映“main”的用处——编译器将应用它来查找要查阅的“main”定义文件。

例如,像 /root/src/moduleA.ts 中的 import {b} from “./moduleB” 这样的导入语句将导致尝试以下地位来定位 “./moduleB”:

(1)/root/src/moduleB.ts
(2)/root/src/moduleB.tsx
(3)/root/src/moduleB.d.ts
(4)/root/src/moduleB/package.json(如果它指定了“types”属性)
(5)/root/src/moduleB/index.ts
(6)/root/src/moduleB/index.tsx
(7)/root/src/moduleB/index.d.ts

回忆一下,Node.js 查找名为 moduleB.js 的文件,而后是实用的 package.json,而后是 index.js。

同样,非绝对导入将遵循 Node.js 解析逻辑,首先查找文件,而后查找实用的文件夹。因而,在源文件 /root/src/moduleA.ts 中 import {b} from “moduleB” 将导致以下查找:

/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json(如果它指定了“types”属性)/root/src/node_modules/@types/moduleB.d.ts
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts

/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json(如果它指定了“types”属性)/root/node_modules/@types/moduleB.d.ts
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts

/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json(如果它指定了“types”属性)/node_modules/@types/moduleB.d.ts
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts

不要被这里的步骤数吓倒——TypeScript 依然只在步骤 (9) 和 (17) 中两次跳转目录。这实际上并不比 Node.js 自身所做的更简单。

Additional module resolution flags

我的项目源布局有时与输入布局不匹配。通常一组构建步骤会生成最终输入。其中包含将 .ts 文件编译为 .js,以及将依赖项从不同的源地位复制到单个输入地位。最终后果是模块在运行时的名称可能与蕴含其定义的源文件的名称不同。或者最终输入中的模块门路可能在编译时与其对应的源文件门路不匹配。

TypeScript 编译器有一组额定的标记来告诉编译器预期产生在源上的转换以生成最终输入。

须要留神的是,编译器不会执行任何这些转换;它只是应用这些信息来领导将模块导入解析到其定义文件的过程。

Base Url

在应用 AMD 模块加载器的应用程序中,应用 baseUrl 是一种常见做法,其中模块在运行时“部署”到单个文件夹。这些模块的源代码能够位于不同的目录中,然而构建脚本会将它们放在一起。

设置 baseUrl 告诉编译器在哪里能够找到模块。假设所有具备非绝对名称的模块导入都与 baseUrl 相干。

baseUrl 的值确定为:

(1)baseUrl 命令行参数的值(如果给定的门路是绝对的,则依据当前目录计算)

(2)’tsconfig.json’ 中 baseUrl 属性的值(如果给定的门路是绝对的,则依据 ‘tsconfig.json’ 的地位计算)

请留神,绝对模块导入不会受到设置 baseUrl 的影响,因为它们总是绝对于它们的导入文件进行解析。

您能够在 RequireJS 和 SystemJS 文档中找到无关 baseUrl 的更多文档。

path mapping

有时模块并不间接位于 baseUrl 下。例如,对模块“jquery”的导入将在运行时转换为“node_modules/jquery/dist/jquery.slim.min.js”。加载器应用映射配置在运行时将模块名称映射到文件,请参阅 RequireJs 文档和 SystemJS 文档。

TypeScript 编译器反对应用 tsconfig.json 文件中的“paths”属性申明此类映射。以下是如何为 jquery 指定“paths”属性的示例。

{
  "compilerOptions": {
    "baseUrl": ".", // This must be specified if "paths" is.
    "paths": {"jquery": ["node_modules/jquery/dist/jquery"] // This mapping is relative to "baseUrl"
    }
  }
}

如何查看 TypeScript 模块解析过程

如前所述,编译器在解析模块时能够拜访以后文件夹之外的文件。在诊断模块未解析的起因或解析为不正确的定义时,这可能很艰难。应用 –traceResolution 启用编译器模块解析跟踪能够深刻理解模块解析过程中产生的状况。

假如咱们有一个应用 typescript 模块的示例应用程序。

app.ts has an import like import * as ts from “typescript”.


│   tsconfig.json
├───node_modules
│   └───typescript
│       └───lib
│               typescript.d.ts
└───src
        app.ts

应用如下命令行编译:

tsc –traceResolution

后果:

======== Resolving module 'typescript' from 'src/app.ts'. ========
Module resolution kind is not specified, using 'NodeJs'.
Loading module 'typescript' from 'node_modules' folder.
File 'src/node_modules/typescript.ts' does not exist.
File 'src/node_modules/typescript.tsx' does not exist.
File 'src/node_modules/typescript.d.ts' does not exist.
File 'src/node_modules/typescript/package.json' does not exist.
File 'node_modules/typescript.ts' does not exist.
File 'node_modules/typescript.tsx' does not exist.
File 'node_modules/typescript.d.ts' does not exist.
Found 'package.json' at 'node_modules/typescript/package.json'.
'package.json' has 'types' field './lib/typescript.d.ts' that references 'node_modules/typescript/lib/typescript.d.ts'.
File 'node_modules/typescript/lib/typescript.d.ts' exist - use it as a module resolution result.
======== Module name 'typescript' was successfully resolved to 'node_modules/typescript/lib/typescript.d.ts'. ========

触发模块解析的源代码地位:

======== Resolving module‘typescript’from‘src/app.ts’. ========

模块解析策略:

Module resolution kind is not specified, using‘NodeJs’.

Loading of types from npm packages:

‘package.json’has‘types’field‘./lib/typescript.d.ts’that references‘node_modules/typescript/lib/typescript.d.ts’.

最初胜利解析的输入:

======== Module name‘typescript’was successfully resolved to‘node_modules/typescript/lib/typescript.d.ts’. ========

正文完
 0