4.1 模块系统与解析


4.1 模块系统与解析

这节在地图里是工程化的地基。很多"明明文件在那却找不到模块"的报错,根子在 module 与 moduleResolution 这两个开关没配对。我们直接讲清它们怎么决定 import 的写法。

ESM 与 CommonJS:两种模块语法

TS 同时支持 import/export(ESM)和 require(CommonJS)。编译成哪种,由 module 决定。

// ESM 写法(推荐) import { foo } from "./foo"; export const bar = 1; // CommonJS 写法(老 Node 项目) import foo = require("./foo"); export = bar;

输入输出:配 module: "ESNext" 时输出原生 import;配 module: "CommonJS" 时输出 require。选错会导致 Node 跑不起来或浏览器不认。

moduleResolution:编译器怎么找文件

moduleResolution 告诉 TS 如何把 import "./foo" 映射到具体文件。常用三档:

// 经典 Node 解析(CommonJS 时代) { "module": "CommonJS", "moduleResolution": "node" } // 现代 Node ESM(Node 12+) { "module": "ESNext", "moduleResolution": "node16" } // 打包器场景(Vite/webpack,支持扩展名省略与别名) { "module": "ESNext", "moduleResolution": "bundler" }

易错点:moduleResolution: "node" 要求写 import "./foo" 时文件是 foo.ts,但运行时 Node ESM 要 ./foo.js。用 node16/bundler 能免去这层心智负担。

路径映射:干掉相对路径地狱

深层目录里 ../../../utils 既丑又易错。用 paths + baseUrl 起别名。

// tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@app/*": ["src/app/*"], "@shared/*": ["src/shared/*"] } } }
// 之前 import { calc } from "../../../shared/calc"; // 之后 import { calc } from "@shared/calc";

注意:paths 只帮 TS 找类型,运行时(Node/打包器)也要配对应别名,否则编译过、运行崩。这张图把"解析链条"画出来:

04-01-fig01-2

实战:双模块发布的坑

一个库想既被 ESM 又被 CommonJS 用户 import,需两种产物 + 对应 package.jsonexports 字段。TS 侧用 moduleResolution: "node16" 让它按文件扩展名(.mts/.cts)和 package.json 决定类型来源。

// package.json 关键片段 { "type": "module", "exports": { ".": { "import": "./dist/index.mjs", "require": "./dist/index.cjs" } } }

声明文件 .d.ts 与环境声明

模块解析不仅找实现,也找类型。没有类型的 JS 模块,用 declare module 补一份环境声明(ambient declaration),让 TS 认识它。

// legacy.d.ts:给无类型的老模块补契约 declare module "legacy-lib" { export function run(opts: { timeout: number }): void; export const version: string; } // 之后普通 import 即可获得类型 import { run, version } from "legacy-lib"; run({ timeout: 1000 }); // 有提示

背景:大量 npm 旧包没自带类型。操作:写 .d.tsdeclare module 描述导出形状。结果:项目内 import 获得完整检查。解读:.d.ts 是"只含类型、无运行时代码"的文件,编译后不产生任何 JS,是连接 TS 与 JS 生态的桥。

通配符模块与资源导入

前端常 import logo from "./logo.png" 这类非代码资源。编译器不认,需要一条"通配符模块声明"告诉它这种导入返回什么类型。

// assets.d.ts declare module "*.png" { const src: string; export default src; } declare module "*.svg" { const content: string; export default content; } // 之后 import logo from "./logo.png"; // logo 被推断为 string

背景:打包器(Vite/webpack)实际会把图片处理成 URL 字符串。操作:用 *.png 通配符 declare module 声明默认导出类型。结果:TS 不再报"找不到模块"。解读:这把"资源也是一种模块"纳入类型系统,避免到处 // @ts-ignore 破坏契约。

重导出与类型隔离

export * 能聚合多个模块,但类型与值会一起被转发。想只转发类型,用 export type

// 只转发类型,不引入运行时代码 export type { User, Order } from "./models"; // 值或类型都转发 export * from "./utils";

背景:库想按需暴露类型而不增加运行时体积。操作:export type 仅转发类型。结果:树摇更干净,消费方也不会因只引类型而被迫打包无关代码。解读:类型与值的边界在模块化里也重要——export type 让"只取形状"成为显式意图。

工程取舍:统一用 ESM 还是保留 CJS

新项目直接 ESM + moduleResolution: bundler(前端)或 node16(Node)。维护老 Node 库时保留 CommonJS 更稳,避免升级惊动所有调用方。解析策略跟着 module 走,别混搭。

实战:带类型的 side-effect import 与隔离

有时你 import 一个模块只为它的类型或它的副作用(如注册插件),TS 都支持清晰写法。

// 只取类型,编译后完全消失(不占产物) import type { Plugin } from "./plugin"; // 只跑副作用,不绑定任何名字 import "./polyfill"; // 混合:值 + 类型 分开写,tree-shaking 更友好 import { setup } from "./runtime"; import type { Config } from "./runtime"; const p: Plugin = { name: "x" }; // Plugin 仅编译期,运行时无此引用 setup(); // 运行期调用

输入输出:import typePlugin 在产物里被彻底抹去,不会因"只用了类型"而把整个模块打进包。这比 import { Plugin } 再也不用值,更利于打包器摇树。

诊断:解析失败的典型报错与含义

遇到"找不到模块",报错码能告诉你根因,别只盯着路径。

import { x } from "./missing"; // 常见报错: // TS2307: 找不到模块"./missing"或其相应的类型声明。 // → moduleResolution 配错,或文件真不存在,或扩展名/别名未配 // TS2829: 无法从"./x.json"导入,需设 resolveJsonModule // → 读 JSON 要开 resolveJsonModule

背景:新人常把 TS2307 当成"路径写错"。操作:先确认 moduleResolution 与文件扩展名是否匹配(如 node16.js)。结果:多数"找不到"其实是解析策略与运行时不一致。解读:解析是"类型世界"和"文件系统"的桥,桥的规格由 module/moduleResolution 共同决定。

本节要点回顾

  • module 决定输出语法,moduleResolution 决定如何找文件
  • node/node16/bundler 三档对应不同生态
  • paths 别名改善可读性,但要运行时同步配置
  • 双模块发布靠 exports 字段 + 对应解析策略

⚠️ 别以为 paths 配了就万事大吉。它只影响编译期类型查找,运行时 Node 不认 @shared/...,不配打包器或 tsconfig-paths 会运行期崩溃。

💡 新项目前端用 moduleResolution: bundler,能省略扩展名、支持别名,和 Vite/webpack 最契合;Node 库用 node16 才能正确处理 ESM 与 CJS 双形态。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U