放弃 tsc+nodemon 使用 tsx 构建Node 环境下 TypeScript + ESM 开发环境搭建指南
目标
在 node 环境下构建 typescript + esmodule模块 开发环境,这样可以使用 typescript 提供的类型安全和类型提示便利性。
我们要实现下面的效果
- 文件目录
- src/index.ts 注意是 esmodule 规范
- 运行效果
TSX 模块介绍
TSX 模块 TSX 是一个专为现代 Node.js 设计的 TypeScript 运行时,它替代了传统的
ts-node
和--loader
方案,特别适合在 ESM 环境下使用。
- 原生 ESM 支持 - 完全支持 Node.js 的 ES Modules 规范 - 无需额外的
--loader
或--experimental
标志 - 自动处理.ts
和.tsx
文件导入- 高性能 - 使用 Rust 编写的快速转译器 (基于 SWC) - 比传统
ts-node
快 5-20 倍 - 智能缓存机制减少重复编译- 零配置 - 自动检测项目中的
tsconfig.json
- 内置支持 TypeScript 路径映射 - 自动处理 CommonJS 和 ESM 互操作- 替代nodemon -
tsx watch
会监视文件变化并自动重新加载
为什么选择 ESM?
ES Modules 是 JavaScript 的官方模块标准,相比传统的 CommonJS (CJS) 具有以下优势:
- 静态分析能力,便于 tree-shaking
- 浏览器原生支持
- 更好的异步加载特性
- 更清晰的模块语法
环境准备
确保你已安装:
- Node.js 16+ (推荐 20+ 或最新 LTS 版本)
- pnpm(推荐) 或者 npm 或 yarn
- 代码编辑器 (VS Code 推荐)
项目初始化
bash
mkdir ts-esm-project
cd ts-esm-project
pnpm init
安装必要依赖
sql
pnpm install typescript @types/node --save-dev
pnpm install tsx --save-dev
配置 TypeScript
创建 tsconfig.json
文件:
json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
关键配置说明:
"module": "NodeNext"
- 使用 Node.js 的模块系统"moduleResolution": "NodeNext"
- 使用 Node.js 的模块解析算法"esModuleInterop": true
- 允许与 CommonJS 模块互操作
配置 package.json
修改 package.json
:
json
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node ./dist/index.js",
"dev": "tsx watch src/index.ts",
"test": "NODE_OPTIONS='--import tsx' vitest"
},
"devDependencies": {
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
}
项目结构示例
css
ts-esm-project/
├── src/
│ ├── index.ts
│ ├── utils/
│ │ └── helper.ts
├── tsconfig.json
├── package.json
示例代码
src/index.ts
:
javascript
import { readFile } from 'node:fs/promises';
import { helper } from '@/utils/helper';// 文件后缀js ts 或者不加都可以
const main = async () => {
const content = await readFile('./package.json', { encoding: 'utf-8' });
console.log('Package content:', content);
helper();
};
main().catch(console.error);
src/utils/helper.ts
:
javascript
export function helper() {
console.log('Helper function called');
}
import { helper } from '@/utils/helper';// 文件后缀 js ts 或者不加都可以
开发工作流
-
开发模式:
arduinonpm run dev
tsx watch
会监视文件变化并自动重新加载 -
生产构建:
arduinonpm run build
生成的文件会输出到
dist/
目录 -
运行生产代码:
sqlnpm start
常见问题解决(可能遇到)
1. 文件扩展名问题
ESM 要求导入时必须指定完整扩展名。解决方案:
arduino
// 正确
import './utils/index.js'
// 正确
import './utils/index'
// 正确
import './utils/index.ts'
// 错误
import './utils'
2. __dirname 替代方案
ESM 中没有 __dirname
,替代方案:
javascript
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
3. 第三方模块兼容性
如果遇到 ESM/CJS 混合问题,可以尝试:
-
检查该模块是否有 ESM 版本
-
使用动态导入:
dartconst { default: pkg } = await import('some-package');
测试环境配置
推荐使用 Vitest:
css
npm install vitest --save-dev
vite.config.ts
:
php
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['**/*.test.ts'],
globals: true
}
});
部署注意事项
-
生产环境应该运行编译后的
.js
文件 -
确保
package.json
中包含:json{ "type": "module", "engines": { "node": ">=18.0.0" } }
总结
666666666666666666666666