TypeScript 配合 ES Modules 开发的核心是配置准确、语法规范、环境明确:tsconfig.json 必须设 module: "ESNext"、target: "ES2020" 或更高、moduleResolution: "Node";代码仅用标准 import/export,类型导入加 type;运行环境需声明 "type": "module" 或工具支持 ESM。

用 TypeScript 配合 ES Modules 开发,关键不在“写法多炫酷”,而在于配置准确、语法规范、构建无歧义。核心就三点:tsconfig 选对模块目标、代码用标准 import/export、运行环境明确支持 ESM。
tsconfig.json 必须配对的三个选项
这些不是可选建议,是现代 ESM 项目的基础底线:
-
module: "ESNext" —— 让 TypeScript 输出原生
import/export语句,不降级成 CommonJS; -
target: "ES2020" 或更高(如
ES2022)—— 确保生成的 JS 语法能被现代浏览器和 Node.js 原生执行,比如支持Promise.allSettled、globalThis、顶层 await; -
moduleResolution: "Node" —— 启用 Node.js 风格的模块查找逻辑(比如自动识别
package.json#exports和index.ts),避免路径解析失败。
代码里只用标准 ESM 语法,别混 CommonJS
TS 支持 require() 和 module.exports,但那是为兼容旧项目留的后门。在纯 ESM 项目中,请坚持以下写法:
- 导出类型和值分开处理:
export interface User { ... }(编译后消失) +export const api = fetch(...)(保留为 JS); - 只导入类型时,加
type关键字:import type { Config } from './types';,防止打包工具误打包; - 默认导出和命名导出共存没问题,但别在一个文件里又
export default又export =(后者是 CommonJS 风格,已淘汰); - 浏览器中 import 路径必须带扩展名(
./utils.js),Node.js 中若设了"type": "module",也推荐显式写.js或.mts。
运行与构建环境要主动声明 ESM
TS 编译只是第一步,Node.js 或打包工具还得认这个模块系统:
- 在
package.json加上"type": "module",否则 Node.js 会按 CommonJS 解析.js文件,报错Cannot use import statement outside a module; - 如果用 Vite、Rspack、Rollup 等现代工具,它们默认支持 ESM,无需额外配置;Webpack 5+ 也支持,但需确认
experiments.outputModule: true已启用; - 想直接用
ts-node运行 TS 模块?得配合ts-node --esm,并确保tsconfig.json里有"module": "ESNext"; - 路径别名(如
@/components)不能靠 TS 单独解决——baseUrl+paths只影响类型检查,实际运行仍需 Vite 的resolve.alias或 Webpack 的resolve.alias来重写。
别再用 namespace,它和 ESM 不是一套逻辑
namespace 是 TS 早期为弥补 JS 模块缺失设计的方案,现在已完全被 ESM 取代:
- 它无法被 Tree Shaking,会导致未使用代码残留;
- 它不生成真实模块边界,打包工具难以分析依赖;
- 它会在编译后包裹一层 IIFE,增加体积且破坏作用域隔离;
- 唯一合理使用场景:写
.d.ts声明文件时合并全局类型(比如扩展现有库的window接口)。
不复杂但容易忽略。


















