附加的模块解析标记
有时工程源码结构与输出结构不同。 通常是要经过一系统的构建步骤最后生成输出。 它们包括将.ts编译成.js,将不同位置的依赖拷贝至一个输出位置。 最终结果就是运行时的模块名与包含它们声明的源文件里的模块名不同。 或者最终输出文件里的模块路径与编译时的源文件路径不同了。

TypeScript编译器有一些额外的标记用来通知编译器在源码编译成最终输出的过程中都发生了哪个转换。

有一点要特别注意的是编译器不会进行这些转换操作; 它只是利用这些信息来指导模块的导入。

Base URL
在利用AMD模块加载器的应用里使用baseUrl是常见做法,它要求在运行时模块都被放到了一个文件夹里。 这些模块的源码可以在不同的目录下,但是构建脚本会将它们集中到一起。

设置baseUrl来告诉编译器到哪里去查找模块。 所有非相对模块导入都会被当做相对于baseUrl。

baseUrl的值由以下两者之一决定:

命令行中baseUrl的值(如果给定的路径是相对的,那么将相对于当前路径进行计算)
‘tsconfig.json’里的baseUrl属性(如果给定的路径是相对的,那么将相对于‘tsconfig.json’路径进行计算)
注意相对模块的导入不会被设置的baseUrl所影响,因为它们总是相对于导入它们的文件。

阅读更多关于baseUrl的信息RequireJS和SystemJS。

路径映射
有时模块不是直接放在baseUrl下面。 比如,充分"jquery"模块地导入,在运行时可能被解释为"node_modules/jquery/dist/jquery.slim.min.js"。 加载器使用映射配置来将模块名映射到运行时的文件,查看RequireJs documentation和SystemJS documentation。

TypeScript编译器通过使用tsconfig.json文件里的"paths"来支持这样的声明映射。 下面是一个如何指定jquery的"paths"的例子。

{
  "compilerOptions": {
    "baseUrl": ".", // This must be specified if "paths" is.
    "paths": {
      "jquery": ["node_modules/jquery/dist/jquery"] // 此处映射是相对于"baseUrl"
    }
  }
}

请注意"paths"是相对于"baseUrl"进行解析。 如果"baseUrl"被设置成了除".“外的其它值,比如tsconfig.json所在的目录,那么映射必须要做相应的改变。 如果你在上例中设置了"baseUrl”: “./src”,那么jquery应该映射到"…/node_modules/jquery/dist/jquery"。

通过"paths"我们还可以指定复杂的映射,包括指定多个回退位置。 假设在一个工程配置里,有一些模块位于一处,而其它的则在另个的位置。 构建过程会将它们集中至一处。 工程结构可能如下:

projectRoot
├── folder1
│   ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
│   └── file2.ts
├── generated
│   ├── folder1
│   └── folder2
│       └── file3.ts
└── tsconfig.json

相应的tsconfig.json文件如下:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "*": [
        "*",
        "generated/*"
      ]
    }
  }
}

它告诉编译器所有匹配"*"(所有的值)模式的模块导入会在以下两个位置查找:

"": 表示名字不发生改变,所以映射为 => /
"generated/
"表示模块名添加了“generated”前缀,所以映射为 => /generated/
按照这个逻辑,编译器将会如下尝试解析这两个导入:

导入’folder1/file2’
匹配’‘模式且通配符捕获到整个名字。
尝试列表里的第一个替换:’
’ -> folder1/file2。
替换结果为非相对名 - 与baseUrl合并 -> projectRoot/folder1/file2.ts。
文件存在。完成。
导入’folder2/file3’
匹配’‘模式且通配符捕获到整个名字。
尝试列表里的第一个替换:’
’ -> folder2/file3。
替换结果为非相对名 - 与baseUrl合并 -> projectRoot/folder2/file3.ts。
文件不存在,跳到第二个替换。
第二个替换:’generated/*’ -> generated/folder2/file3。
替换结果为非相对名 - 与baseUrl合并 -> projectRoot/generated/folder2/file3.ts。
文件存在。完成。

利用rootDirs指定虚拟目录
有时多个目录下的工程源文件在编译时会进行合并放在某个输出目录下。 这可以看做一些源目录创建了一个“虚拟”目录。

利用rootDirs,可以告诉编译器生成这个虚拟目录的roots; 因此编译器可以在“虚拟”目录下解析相对模块导入,就好像它们被合并在了一起一样。

比如,有下面的工程结构:

src
 └── views
     └── view1.ts (imports './template1')
     └── view2.ts

 generated
 └── templates
         └── views
             └── template1.ts (imports './view2')

src/views里的文件是用于控制UI的用户代码。 generated/templates是UI模版,在构建时通过模版生成器自动生成。 构建中的一步会将/src/views和/generated/templates/views的输出拷贝到同一个目录下。 在运行时,视图可以假设它的模版与它同在一个目录下,因此可以使用相对导入"./template"。

可以使用"rootDirs"来告诉编译器。 "rootDirs"指定了一个roots列表,列表里的内容会在运行时被合并。 因此,针对这个例子,tsconfig.json如下:

{
  "compilerOptions": {
    "rootDirs": [
      "src/views",
      "generated/templates/views"
    ]
  }
}

每当编译器在某一rootDirs的子目录下发现了相对模块导入,它就会尝试从每一个rootDirs中导入。

rootDirs的灵活性不仅仅局限于其指定了要在逻辑上合并的物理目录列表。它提供的数组可以包含任意数量的任何名字的目录,不论它们是否存在。这允许编译器以类型安全的方式处理复杂捆绑(bundles)和运行时的特性,比如条件引入和工程特定的加载器插件。

设想这样一个国际化的场景,构建工具自动插入特定的路径记号来生成针对不同区域的捆绑,比如将#{locale}做为相对模块路径./#{locale}/messages的一部分。在这个假定的设置下,工具会枚举支持的区域,将抽像的路径映射成./zh/messages,./de/messages等。

假设每个模块都会导出一个字符串的数组。比如./zh/messages可能包含:

export default [
    "您好吗",
    "很高兴认识你"
];

利用rootDirs我们可以让编译器了解这个映射关系,从而也允许编译器能够安全地解析./#{locale}/messages,就算这个目录永远都不存在。比如,使用下面的tsconfig.json:

{
  "compilerOptions": {
    "rootDirs": [
      "src/zh",
      "src/de",
      "src/#{locale}"
    ]
  }
}

编译器现在可以将import messages from './#{locale}/messages’解析为import messages from './zh/messages’用做工具支持的目的,并允许在开发时不必了解区域信息。