一、TS 文件的加载策略
TS 中的加载策略分为两种方式,分别为相对路径和绝对路径两种方式。
1、相对路径
TypeScript 将 TypeScript 源文件扩展名(.ts
、.tsx
和.d.ts
)覆盖在 Node 的解析逻辑上。同时TypeScript 还将使用 package.json
named 中的一个字段 types
来镜像目的"main"
,编译器将使用它来查找“主”定义文件以进行查阅。
// 假设当前执行路径为 /root/src/modulea
import { b } from './moduleb'
此时,TS 对于 ./moduleb
的加载方式其实是和 node 的模块加载机制比较类似:
- 首先寻找
/root/src/moduleb.ts
是否存在,如果存在使用该文件。 - 其次寻找
/root/src/moduleb.tsx
是否存在,如果存在使用该文件。 - 其次寻找
/root/src/moduleb.d.ts
是否存在,如果存在使用该文件。 - 其次寻找
/root/src/moduleB/package.json
,如果 package.json 中指定了一个types
属性的话那么会返回该文件。 - 如果上述仍然没有找到,之后会查找
/root/src/moduleB/index.ts
。 - 如果上述仍然没有找到,之后会查找
/root/src/moduleB/index.tsx
。 - 如果上述仍然没有找到,之后会查找
/root/src/moduleB/index.d.ts
。
可以看到 TS 中针对于相对路径查找的规范是和 nodejs 比较相似的。
Ts 在寻找文件路径时,在某些条件下是会按照目录去查找 .d.ts
的。
2、绝对路径
// 假设当前文件所在路径为 /root/src/modulea
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
typescript 针对于非相对导入的 moduleb 会按照以上路径去当前路径的 node_modules 中去查找,如果上述仍然未找到。
此时,TS 仍然会按照 node 的模块解析规则,继续向上进行目录查找,比如又会进入上层目录 /root/node_modules/moduleb.ts ...
进行查找,直到查找到顶层 node_modules 也就是最后一个查找的路径为 /node_modules/moduleB/index.d.ts
如果未找到则会抛出异常 can't find module 'moduleb'
。
上述查找规则是基于 tsconfig.json 中指定的moduleResolution:node
,当然还有classic
不过classic
规则是 TS 为了兼容老旧版本,现代代码中基本可以忽略这个模块查找规则。
3、解析 *.d.ts
声明
日常开发中,有这样一种场景,在 TS 项目中我们需要引入一些后缀为 png 的图片资源,那么此时 TS 是无法识别此模块的。
解决方法也非常简单,通常我们会在项目的根目录中也就是和 tsconfig.json 平级的任意目录中添加对应的声明文件 image.d.ts
:
// image.d.ts
declare module '*.png' {
const src: String;
export default src;
}
import logo from './assets/logo.png'
可以看到,通过定义声明文件的方式解决了我们的问题。
3.1 typescript模块加载机制是怎么查找到定义在项目目录中的 image.d.ts
呢?
本质上我们引入任何模块时,加载机制无非就是我们上边提到的两种加载方式。
不过,这里有一个细小的点即是 ts 编译器会处理 tsconfig.json 的 file、include、exclude
对应目录下的所有 .d.ts 文件:
简单来说,ts 编译器首先会根据 tsconfig.json 中的上述三个字段来加载项目内的 d.ts
全局模块声明文件,自然由于 '.png' 文件会命中全局加载的 image.d.ts
中的 声明的 module
所以会找到对应的文件。
include 在未指定 file 配置下默认为**
,表示 tsc 解析的目录为当前 tsconfig.json 所在的项目文件夹。
关于 file、include、exclude 三者本质上都是针对于 TSC 编译器处理的范围。
// tsconfig.json
{
"compilerOptions": {
"outDir": "build/dist",
"module": "esnext",
"target": "esnext",
"lib": ["esnext", "dom"],
"sourceMap": true,
"baseUrl": ".",
"jsx": "react-jsx",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"allowJs": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"strict": true,
"paths": {
"@/*": ["./src/*"],
"@@/*": ["./src/.umi/*"]
}
},
"include": [
"mock/**/*",
"src/**/*",
"playwright.config.ts",
"tests/**/*",
"test/**/*",
"__test__/**/*",
"typings/**/*",
"config/**/*",
".eslintrc.js",
".stylelintrc.js",
".prettierrc.js",
"jest.config.js",
"mock/*"
],
"exclude": ["node_modules", "build", "dist", "scripts", "src/.umi/*", "webpack", "jest"],
"files": []
}