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
└───srcapp.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’. ========

TypeScript 里的 module 解析过程 - Module Resolution相关推荐

  1. TypeScript 里的 module 概念

    Modules JavaScript 在处理模块化代码方面有着悠久的历史. TypeScript 自 2012 年问世以来,已经实现了对许多这些格式的支持,但随着时间的推移,社区和 JavaScrip ...

  2. 0基础快速入门WebPack(3)——图解详述plugins(插件)的安装及sourceMap的使用及WebpackDevServer正向代理和模块热更新等(附详细案例源码解析过程及版本迭代过程)

    文章目录 1. 重点提炼 2. 配置环境 3. Plugins(插件) 3.1 HtmlWebpackPlugin 3.1.1 example01 3.1.1.1 example01-1 3.1.1. ...

  3. DNS解析过程详解【转】

    转自:http://blog.chinaunix.net/uid-28216282-id-3757849.html 先说一下DNS的几个基本概念: 一. 根域 就是所谓的".",其 ...

  4. oracle 测试sql执行时间_通过错误的SQL来测试推理SQL的解析过程

    这是学习笔记的第 1977 篇文章 如果抛出一个问题,你是如何理解MySQL解析器的,它和Oracle解析器有什么差别?相信大多数同学都会比较迷茫,因为这个问题很难验证,要不是看源码,要不就是查看书上 ...

  5. Android init.rc文件解析过程详解(一)

        Android init.rc文件解析过程详解(一) 一.init.rc文件结构介绍 init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字 ...

  6. dns解析过程_DNS原理总结及其解析过程

    点击上方蓝色字体,关注我们 前言 域名系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务.它作为将域名和IP地址相互映射的一个分布式数据库,使人更方便地访问互联网.DN ...

  7. DNS之三 辅助区域和名称解析过程

    主要区域上次已经提到并详讲到,如果有一个规模比较大的公司只有一台DNS服务器,如果这台服务器出现了故障的话那么一切依靠域名的平台都不能解析,这是非常麻烦的,为了防止这种事情的发生就需要在局域网中再部署 ...

  8. 我眼中的 Nginx(六):深入 Nginx/Openresty 服务里的 DNS 解析

    转自:https://www.upyun.com DNS 解析在 Nginx/OpenResty 的服务里是不可分割的一个功能,本文主要来介绍下 Nginx 和 OpenResty 服务里的一些不同的 ...

  9. wordpress url index.php,WordPress对URL的路由解析过程详解

    本文说明WP 对URL rewrite并生成当前请求的过程. 实际内容并不复杂, 说的比较啰嗦啦- 关于Query Vars 这是Wordpress全部代码中最重要的变量,所谓的query vars是 ...

最新文章

  1. php配置mongodb扩展、安装mongodb服务教程
  2. ASP.NET页生命周期概述
  3. Android camera开发总结
  4. gitblit如何迁移入gitlab合并迁移_github仓库迁移到gitlab以及gitlab仓库迁移到另一个gitlab服务器...
  5. colab从CPU切换到GPU以及配置查看
  6. Stateless 3.0——.NET Core上的状态机库
  7. 奇妙的安全旅行之加密算法概述
  8. 数据表 高水位 mysql_Oracle中的高水位(HWM)
  9. 增强内容表现的jQuery插件
  10. 大数据python之简单的网络爬虫代码实现(单一与循环代码进行网络爬虫)
  11. 软考中级数据库系统工程师复习资料
  12. 学Java编程可以做什么?发展方向有哪些?
  13. 网络安全/渗透测试工具AWVS14.7下载
  14. 双机热备系统的方案与软件浅析
  15. ArcMap制作疫情地图
  16. opencv 将Mat转为图片数据
  17. Linux vmstat 命令详解
  18. ET服务器框架学习笔记
  19. 低学历逆袭难,真的只是不够努力吗?
  20. 数据库~如何快速、准确选取候选码(候选键)?

热门文章

  1. 小菜鸡进阶之路_Second week之元组、列表、集合、字典对比.
  2. Linux操作系统下实现远程开机
  3. Apache整合Tomcat
  4. 20155301《信息安全系统设计基础》第六周学习总结
  5. Java/Android引用类型及其使用分析
  6. SQL Server 查询数据库中所有的表名及行数
  7. CentOS6网卡静态IP设置
  8. 数据中心两种常用流量模型运用mininet的实现
  9. Git 常用命令和教程
  10. Cocos2d-x编程中CCRemoveSelf与CCCallFunc系列函数不能一起使用