TypeScript 模块解析(moduleResolution)详解

moduleResolution 决定 TS 如何定位 import 的模块。本文讲清 node10/node16/nodenext/bundler 四种策略的差异与选择。

最佳实践
类型结构与组件组合插画

2026 年的选择只有两种:打包器项目(Vite/webpack)用 "bundler",Node 直跑项目用 "NodeNext"。NodeNext 严格遵循 Node 的 ESM/CJS 规则(相对导入要带 .js 扩展名),bundler 更宽松。老配置(node10)只存在于存量项目。

模块解析是什么

import { x } from './utils' —— TS 需要找到 utils 到底是 ./utils.ts 还是 ./utils/index.ts。解析策略决定查找规则与对裸导入(npm 包)的处理。

四种策略

策略 适用
node10(旧 node) 存量 CJS 项目,不推荐新用
node16 / nodenext Node 直跑(ESM/CJS 按 Node 规则)
bundler 打包器项目(Vite/webpack/tsx)
// Node 项目
{ "module": "NodeNext", "moduleResolution": "NodeNext" }

// 打包器项目
{ "module": "ESNext", "moduleResolution": "bundler" }

NodeNext 的严格性

// ESM 模式下相对导入必须带扩展名(编译后的 .js)
import { helper } from './utils.js';   // 注意是 .js 不是 .ts

这是 Node ESM 的真实规则——TS 只是如实反映。用 tsx/bundler 的项目无此烦恼(bundler 模式允许省略)。

node_modules 解析

裸导入(import express from 'express')的查找:包的 exports 字段 → types 条件 → 主入口。pnpm 的严格结构下,只有声明为依赖的包可被解析——幻影依赖在解析层被拦截。

常见问题(FAQ)

Q:报错 "Cannot find module" 但文件明明在?

九成是 moduleResolution 与项目实际运行方式不匹配:打包器项目误用 NodeNext(缺扩展名报错)或反之。先确认代码最终怎么跑。

Q:paths 别名和解析策略的关系?

paths 是解析前的重写层——TS 侧生效;运行时需工具链配合(tsx/esbuild 自动,Node 原生需 imports 字段或 loader)。

Q:迁移老项目到 NodeNext 的成本?

主要工作:相对导入补 .js 扩展名(有 codemod)、默认导入 CJS 包的互操作检查。一次投入换来与 Node 语义严格一致。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台