EggJS 项目中使用 TypeScript 的完整指南
前言
TypeScript 作为 JavaScript 的超集,通过静态类型检查、智能提示和友好的 IDE 支持,为大型企业级应用开发带来了显著优势。本文将详细介绍如何在 EggJS 框架中集成和使用 TypeScript,帮助开发者提升开发效率和代码质量。
为什么选择 TypeScript
TypeScript 为 JavaScript 带来了类型系统,主要优势包括:
- 静态类型检查:在编译阶段就能发现潜在的类型错误
- 智能提示:IDE 能提供更准确的代码补全和文档提示
- 更好的可维护性:类型注解使代码更易于理解和维护
- 渐进式采用:可以逐步将现有 JavaScript 项目迁移到 TypeScript
快速开始
初始化项目
使用以下命令快速创建一个 TypeScript 版本的 EggJS 项目:
mkdir showcase && cd showcase
npm init egg --type=ts
npm i
npm run dev
这个命令会创建一个简单的 TypeScript 示例项目,包含基本的控制器、服务和路由配置。
项目结构
TypeScript 项目结构与常规 EggJS 项目类似,主要区别在于:
- 文件后缀使用
.ts
而不是.js
- 新增
typings
目录存放类型定义文件 - 需要
tsconfig.json
和tslint.json
配置文件
典型目录结构如下:
showcase
├── app
│ ├── controller
│ │ └── home.ts
│ ├── service
│ │ └── news.ts
│ └── router.ts
├── config
│ ├── config.default.ts
│ ├── config.local.ts
│ ├── config.prod.ts
│ └── plugin.ts
├── test
│ └── **/*.test.ts
├── typings
│ └── **/*.d.ts
├── README.md
├── package.json
├── tsconfig.json
└── tslint.json
核心概念实现
控制器(Controller)
控制器示例:
import { Controller } from 'egg';
export default class HomeController extends Controller {
public async index() {
const { ctx, service } = this;
const page = ctx.query.page;
const result = await service.news.list(page);
await ctx.render('home.tpl', result);
}
}
路由(Router)
路由配置示例:
import { Application } from 'egg';
export default (app: Application) => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
服务(Service)
服务层示例:
import { Service } from 'egg';
export default class NewsService extends Service {
public async list(page?: number): Promise<NewsItem[]> {
return [];
}
}
export interface NewsItem {
id: number;
title: string;
}
中间件(Middleware)
中间件实现示例:
import { Context } from 'egg';
export default function fooMiddleware() {
return async (ctx: Context, next: any) => {
await next();
};
}
配置(Config)
配置管理是 TypeScript 集成中最复杂的部分,需要处理:
- 控制器和服务中的多层智能提示配置
- 配置合并时的类型提示
- 自定义配置的类型扩展
配置示例:
import { EggAppInfo, EggAppConfig, PowerPartial } from 'egg';
export default (appInfo: EggAppInfo) => {
const config = {} as PowerPartial<EggAppConfig>;
config.keys = appInfo.name + '123456';
config.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};
const bizConfig = {
news: {
pageSize: 30,
serverUrl: 'https://hacker-news.firebaseio.com/v0',
},
};
return {
...(config as {}),
...bizConfig,
};
};
开发工具链
ts-node 集成
egg-bin
内置了 ts-node
支持,开发时自动加载和编译 .ts
文件。只需在 package.json
中配置:
{
"egg": {
"typescript": true
}
}
egg-ts-helper
由于 EggJS 的自动加载机制,TypeScript 无法静态分析依赖关系。我们使用 egg-ts-helper
工具自动生成类型定义文件:
{
"egg": {
"declarations": true
},
"scripts": {
"dev": "egg-bin dev",
"clean": "ets clean"
}
}
该工具会自动分析项目并生成 typings/{app,config}/
下的类型定义文件,开发者不应手动修改这些文件。
测试与调试
单元测试
测试文件示例:
import assert from 'assert';
import { Context } from 'egg';
import { app } from 'egg-mock/bootstrap';
describe('test/app/service/news.test.js', () => {
let ctx: Context;
before(async () => {
ctx = app.mockContext();
});
it('list()', async () => {
const list = await ctx.service.news.list();
assert(list.length === 30);
});
});
调试配置
调试配置与常规 JavaScript 项目类似,通过 sourcemap 可以准确定位到 TypeScript 源代码:
{
"scripts": {
"debug": "egg-bin debug",
"debug-test": "npm run test-local -- --inspect"
}
}
生产部署
构建流程
生产环境建议将 TypeScript 编译为 JavaScript 再运行:
{
"scripts": {
"start": "egg-scripts start --title=egg-server-showcase",
"tsc": "ets && tsc -p tsconfig.json",
"ci": "npm run lint && npm run cov && npm run tsc"
}
}
错误堆栈
构建时启用 inlineSourceMap 确保线上错误能映射到 TypeScript 源码:
{
"compilerOptions": {
"inlineSourceMap": true
}
}
常见问题解答
生产环境 ts 文件未加载
npm start
使用 egg-scripts
运行,而 ts-node
只集成在 egg-bin
中。生产环境建议先编译再运行:
npm run tsc
npm start
插件对象未加载类型
可能原因:
- 插件缺少类型定义:需要按规范为插件添加
index.d.ts
- 插件有类型定义但未导入:需要显式导入插件类型声明
临时解决方案:
// typings/index.d.ts
import 'egg';
declare module 'egg' {
interface Application {
dashboard: any;
}
}
tsconfig.json 中 paths 无效
tsc
不会转换 import 路径,运行时需要额外工具处理路径映射。建议使用模块别名工具如 module-alias
。
总结
本文详细介绍了在 EggJS 项目中使用 TypeScript 的完整方案,包括项目结构、核心概念实现、开发工具链、测试调试和生产部署等方面。通过合理配置和工具支持,可以在 EggJS 项目中充分发挥 TypeScript 的优势,提升开发体验和代码质量。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考