// 软件版本
操作系统:Mac OS Monterey Version 12.2.1
TypeScript: 4.6.2
ts-node: 10.7.0
nodemon: 2.0.15
node: v16.13.0
使用 ts-node 时,可能会出现找不到 TypeScript 类型定义的问题,官方也给出了明确的回复如下:
ts-node does not use files
, include
or exclude
, by default. This is because a large majority projects do not use all of the files in a project directory (e.g. Gulpfile.ts
, runtime vs tests) and parsing every file for types slows startup time. Instead, ts-node starts with the script file (e.g. ts-node index.ts
) and TypeScript resolves dependencies based on imports and references.
简单翻译(真简(随)单(遍)翻译,手动狗头🐶)一下就是:ts-node 默认不使用 tsconfig.json 中的 files
, include
或者 exclude
配置,而是从入口文件开始,根据文件依赖路径去查找编译文件。那这和 TypeScript 类型声明找不到有什么关系呢?
关系就在于:一般情况下,开发者自定义的的 *.d.ts
不会在常规的 *.ts
文件中显式的引入,而是依赖 TypeScript 自动识别,也就是说,TypeScript 自动读取了 *.d.ts
中的类型声明,这样普通的 *.ts
文件无需任何配置即可使用 *.d.ts
中声明的类型。那么问题就来了,ts-node 默认不使 用 tsconfig.json 的 files
, include
or exclude
这几个配置,也就是说,存在 *.d.ts
没有被自动识别并报错类型定义找不到的问题。
既然知道了为什么,解决方案也就呼之欲出,下面几个解决方案以供参考。
这里先给出后续示例的一些公共配置
1. 项目目录
├── package.json
├── src
│ └── index.ts
├── tsconfig.json
├── typings
└── yarn.lock
2. package.json
{
"name": "ts-test",
"version": "1.0.0",
"main": "src/index.ts",
"license": "MIT",
"scripts": {
"start": "nodemon ./src/index.ts"
},
"dependencies": {
"axios": "^0.26.1",
"nodemon": "^2.0.15",
"ts-node": "^10.7.0"
},
"devDependencies": {
"typescript": "^4.6.2"
}
}
3. tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["ES2015", "dom"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "typings/**/*"]
}
方案1:使用 typeRoots 配置项
typeRoots
是 tsconfig.json 的一个配置项,用来指定声明文件的存储位置(官方链接),默认情况下,不需要手动设置,TypeScript 会自动读取相对路径为 ./node_modules/@types/
, ../node_modules/@types/
, ../../node_modules/@types/
等等的配置。
下面给出使用 typeRoots
的完整代码。
1. 项目目录
├── src
│ └── index.ts
├── tsconfig.json
├── typings
│ └── TestModule // 模块名是 TestModule
│ └── index.d.ts // 这里 declare module TestModule 的 TestModule 必须和模块名(目录名)一致
2. tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./typings"], // 增加这一条配置即可,其他同默认配置
},
}
3. typings/TestModule/index.d.ts
declare module TestModule {
interface TestModuleAnimal {
legs: number,
}
}
4. src/index.ts
const bird: TestModule.TestModuleAnimal = {
legs: 2,
}
console.log(bird)
关于使用 typeRoots
有几个点需要注意一下
- 类型声明文件必须以
index.d.ts
命名,且存储在对应模块目录下,如上示例必须放在typings/TestModule
目录下; - 声明的模块名必须和目录名一致,如上示例
declare module TestModule
这里的TestModule
必须和对应的目录名一致; - 模块必须放在
typeRoots
声明的文件夹下,如上示例TestModule
目录必须在typings
目录下;
以下是使用范式:
1. 项目结构
<project_root>/
-- tsconfig.json
-- typings/
-- <module_name>/
-- index.d.ts
2. 声明文件格式
declare module '<module_name>' {
// module definitions go here
}
方案2:使用 ts-node: { files }
配置
还有一种方式就是让 ts-node 识别 tsconfig.json 的 files
, include
or exclude
配置,这样 *.d.ts
的文件只要存放在 files
或者 include
指定的文件夹中即可,ts-node 会自动读取并编译。
1. 项目目录
src
│ └── index.ts
├── tsconfig.json
typings
│ └── API.d.ts
└── yarn.lock
2. tsconfig.json
{
"ts-node": {
"files": true // 增加这一条配置即可,其他同默认配置
},
}
3. typings/API.d.ts
declare namespace API {
interface Weirongjing {
a: number,
}
}
4. src/index.ts
const test: API.Weirongjing = {
a: 1,
}
console.log(test)
细心的同学会发现,和方案1不同的是,这里的类型声明文件 API.d.ts
是直接放在 typings
目录下的,且命名空间可任意命名。
方案3:使用 /// <Reference path="类型声明文件路径" />
TypeScript 提供了 /// <Reference path="类型声明文件路径" />
的 API
来显式的导入类型声明文件,完整代码如下:
1. 项目目录
src
│ └── index.ts
├── tsconfig.json
typings
│ └── API.d.ts
└── yarn.lock
2. typings/API.d.ts
declare namespace API {
interface Weirongjing {
a: number,
}
}
3. src/index.d.ts
/// <reference path="../typings/API.d.ts" /> // 增加这一行即可
const test: API.Weirongjing = {
a: 1,
}
console.log(test)
使用 reference
显式引入的话,必须使用 declare namespace
来声明类型,无需其他配置。
方案4:使用 ESModule
的方式导入
使用 reference
方案的前提是使用 TypeScript 提供的命名空间(namespace
),本质也是解决 JavaScript 的模块化问题,而 ES6 已经默认支持了模块化,那么则可以使用模块导入的方式,代码如下:
1. 项目目录
src
│ └── index.ts
├── tsconfig.json
typings
│ └── API.d.ts
└── yarn.lock
2. typings/API.d.ts
export interface API {
a: number,
}
3. src/index.d.ts
import { API } from '../typings/API'
const test: API = {
a: 2,
}
console.log(test)
小结
小结一下,可以使用4种方式来解决 ts-node 不识别 TypeScript 声明文件的问题
- 使用 tsconfig.json 的
typeRoots
配置项; - 使用 tsconfig.json 的
ts-node: { files: true }
配置项; - 使用 TypeScript 提供的
/// <reference path="类型声明文件路径" />
; - 使用 ESModule 导入;
最后补充一点,如果使用 tsc
来编译项目,只需要把对应的声明文件放在 tsconfig.json 的 include
或者 files
配置项声明的目录中即可。