在ES2015出现之前,JavaScript还没有原生的模块系统(ES Module),只能借助第三方的RequireJS和SystemJS来使用模块功能。
模块本质上就是一个JS/TS文件,类似一个沙盒环境,使用import语句引入其他模块的API和值,export语句暴露自己的API和值。
TypeScript原生支持ES Module的语法,可以在.ts
文件中使用import和export语句。默认情况下,TypeScript仅支持引入.ts
和.d.ts
文件模块,可以通过修改allowJS
配置选项,使TypeScirpt支持.js
文件(这个在详解tsconfig.json文件文章中介绍过)。
// example.ts
import "../program";
import "package";
上面的代码,就是要介绍的两种导入模块的方式。
第一种方式比较简单,typescript将会根据我们指定的文件路径寻找文件(program.ts或者program.d.ts),上面的例子中,typescript会在当前文件的父级目录中优先寻找program.ts
,如果program.ts
没有找到,再寻找program.d.ts
,如果找到则导入找到的模块。
第二种方式,通过包名引入,情况比较复杂,接下来将重点介绍。
上面的第二个例子import "package";
,通过模块名导入模块。当typescirpt处理这种导入语句时,它会使用以下两个模块定位策略中的一个。
TypeScript的模块定位策略是可配置的,可以通过tsc
的--moduleResolution
选项配置,也可以通过moduleResolution
编译选项进行配置(tsc
与编译选项在详解tsconfig.json文件详细讨论过)。
使用Classic模块定位策略,TypeScirpt首先在当前文件的同级目录查找与模块名相同的.ts
文件或者.d.ts
文件,如果没有找到,则向上一级目录查找,直到找到或者抵达磁盘根目录也未找到,如果找到则引入找到的模块。
以上面的例子import "package";
为例。假设example.ts
文件路径为D:\program\src\example.ts
。首先在example.ts
文件的同级目录D:\program\src\
寻找package.ts
和package.ds
文件,如果没找到继续在父级目录D:\program\
中寻找package.ts
和package.ds
文件,如果没找到继续向上在D盘根目录D:\
中寻找,如果找到则引入模块。
Node模块定位策略(Node Module Resolution)是TypeScript的默认定位策略,也是最常用的模块定位策略。
只要是JS程序员,肯定知道项目都有一个node_modules
文件夹,里面存放了一些公共包,供项目使用。了解typeScript是如何利用Node模块定位策略的,先看一下Nodejs是如何定位模块的。
Node模块定位策略对于非Node.js程序员来说,可能有点复杂。与Classic模块定位策略相同,首先会向上遍历文件夹,寻找模块,但是也有一些不一样。
以下面的例子为例:
// D:\code\src\example.ts
import '../program';
import 'package';
第一个import
语句,使用相对路径导入模块,这种情况下,首先在父级目录寻找program.ts
和program.d.ts
文件,如果没有找到,则检查父级目录下有没有一个program
文件夹,如果D:\code\src\program\
存在,如果这个D:\code\src\program\
目录下有package.json
文件存在,然后它会导入package.json
中types
和typings
字段指定的路径的文件。
如果D:\code\src\program\
目录下不存在package.json
文件,或者它缺少types
字段,然后它会检查该目录下是否有index.ts
和index.d.ts
文件,如果找到则导入模块,否则报错。
整个过程如下。
1. ../program.ts
2. ../program.d.ts
3. ../program/package.json -> [[types]] -> file path
4. ../program/index.ts
5. ../program/index.d.ts
6. Error: Cannot find module
对于第二个import
语句,使用包名导入模块,情况变得更复杂了。首先,首先寻找当前目录下D:\code\src
的node_modules
文件夹,然后按照与路径导入模块方式相同的方式查找模块。
如果它没有找到合适的文件,它会向上(父级目录,向外遍历)使用相同的逻辑寻找。整个过程如下。
Import statement: import 'package';
Import Location: D:\code\src\example.ts
1. D:/code/src/node_modules/package.ts
2. D:/code/src/node_modules/package.d.ts
3. D:/code/src/node_modules/package/package.json -> [[types]] ->
4. D:/code/src/node_modules/package/index.ts
5. D:/code/src/node_modules/package/index.d.ts
6. D:/code/node_modules/package.ts
7. D:/code/node_modules/package.d.ts
8. D:/code/node_modules/package/package.json -> [[types]] ->
9. D:/code/node_modules/package/index.ts
10. D:/code/node_modules/package/index.d.ts
11. D:/node_modules/package.ts
12. D:/node_modules/package.d.ts
13. D:/node_modules/package/package.json -> [[types]] ->
14. D:/node_modules/package/index.ts
15. D:/node_modules/package/index.d.ts
16. Error: Cannot find module
TypeScirpt利用Node模块定位策略导入模块时,当引用相对路径的模块时,是完全一样的,区别在于使用包名引入模块时,有一点区别,它会检查node_modules/@types
文件夹,整个过程如下。
Import statement: import 'package';
Import Location: D:\code\src\example.ts
1. D:/code/src/node_modules/package.ts
2. D:/code/src/node_modules/package.d.ts
3. D:/code/src/node_modules/package/package.json -> [[types]] ->
4. D:/code/src/node_modules/@types/moduleB.d.ts
5. D:/code/src/node_modules/@types/moduleB/index.d.ts (或者package.json文件指定的入口文件)
6. D:/code/src/node_modules/package/index.ts
7. D:/code/src/node_modules/package/index.d.ts
8. D:/code/node_modules/package.ts
9. D:/code/node_modules/package.d.ts
10. D:/code/node_modules/package/package.json -> [[types]] ->
11. D:/code/node_modules/@types/moduleB.d.ts
12. D:/code/node_modules/@types/moduleB/index.d.ts (或者package.json文件指定的入口文件)
13. D:/code/node_modules/package/index.ts
14. D:/code/node_modules/package/index.d.ts
15. D:/node_modules/package.ts
16. D:/node_modules/package.d.ts
17. D:/node_modules/package/package.json -> [[types]] ->
18. D:/node_modules/@types/moduleB.d.ts
19. D:/node_modules/@types/moduleB/index.d.ts (或者package.json文件指定的入口文件)
20. D:/node_modules/package/index.ts
21. D:/node_modules/package/index.d.ts
22. Error: Cannot find module
(完)