typescript-import-name
TS 项目 import 路径规则笔记
1. 裸模块说明符(bare specifier)vs 相对路径
import * as undici from "undici"; —— 为什么不用写具体文件路径 + 后缀?
- 相对路径(
./foo.js):Node ESM 解析器把它当字面文件路径处理,不做任何猜测,必须精确匹配磁盘上的文件。 - 裸说明符(
"undici"):不以./、../、/开头,Node 走「包解析算法」:
1. 从当前文件所在目录开始,逐级向上找 node_modules/undici(monorepo 场景会一直找到根 node_modules);
2. 找到包目录后,读它的 package.json;
3. 按优先级看 exports 字段(现代包)或退化到 main 字段,决定这个裸名字映射到哪个真实文件。
例:undici 的 package.json 里 "main": "index.js",所以 "undici" 实际被解析成 node_modules/undici/index.js。
import * as undici from "undici" 这种 namespace import 会把该包所有具名导出打包成一个对象,适合既要用值(类、函数)又要用类型(如 undici.Dispatcher)的场景,可以用同一个标识符访问,少写几条 import。
2. 路径别名(@/...)为什么能省略后缀
import { convertDateStr } from '@/common/utils/date-utils-v2.util'(常见于 NestJS 等用 CommonJS/ts-node/webpack 的项目):
1. @/ 是路径别名,不是真实路径:在 tsconfig.json 的 paths 里配置过,如 "@/": ["src/"]。这不是 Node 原生语法,必须靠额外工具(TS 编译器、tsconfig-paths、webpack/jest 的 moduleNameMapper、SWC 等)在编译期或运行期翻译成真实路径。
2. 解析走的是传统 CommonJS 风格的扩展名探测:moduleResolution 是 "node"(经典模式)或 "bundler",遇到不带后缀的路径时会依次尝试 .ts、.tsx、.js、/index.ts 等,直到找到存在的文件。
这跟 Node 原生 ESM(见第 3 节)是完全不同的两套规则,不能混用经验。
3. pi 项目:相对导入为什么必须写 .js,又能写 .ts
pi 项目 package.json 里 "type": "module",走 Node 原生 ESM 解析(moduleResolution: "NodeNext"),相对路径必须精确匹配,不会自动补后缀——所以常规写法下相对导入必须显式写 .js。
但在这个仓库的 tsconfig.base.json 里又开了两个选项,使得可以直接写 .ts:
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
allowImportingTsExtensions:允许在 import 说明符里写.ts后缀,TS 编译器不再报错。rewriteRelativeImportExtensions(TS 5.7+):编译产物(dist/下的.js)里会把.ts自动重写成.js。
效果:
- 源码写法:
import type { SessionManager } from "./session-manager.ts"; - 编译产物:
import type { SessionManager } from "./session-manager.js";
即开发时「所见即所得」写真实文件后缀(编辑器跳转、类型检查都对应真实的 .ts 文件),编译后产物仍符合 Node ESM 严格要求的 .js 后缀规则。
另外 import type 本身是纯类型导入,运行时会被完全擦除,不会生成任何 JS 代码。
小结对照表
| 写法 | 场景 | 解析机制 |
|---|---|---|
| import x from "pkg" | npm 包裸名 | Node 包解析算法,查 node_modules + package.json 的 exports/main |
| import x from "@/foo/bar.util" | 路径别名 + 省略后缀 | tsconfig paths 映射 + CommonJS 风格扩展名探测(node/bundler 解析) |
| import x from "./foo.js" | Node 原生 ESM 相对导入 | 字面路径匹配,必须写真实产物后缀 .js |
| import x from "./foo.ts" | 本仓库(allowImportingTsExtensions + rewriteRelativeImportExtensions) | 源码写 .ts,编译时自动重写为 .js |