分享好友 站长动态首页 网站导航

当 Egg 遇到 TypeScript,收获茶叶蛋一枚

网友发布 2022-10-09 22:51 · 头闻号编程技术

# 前言

Typescript is a typed superset of Javascript that compiles to plain Javascript.
Typescript 的静态类型检查,智能提示,IDE 友好性等特性,对于大规模企业级应用,是非常的有价值的。详见:Typescript体系调研报告 。

然而,此前使用 Typescript 开发 Egg ,会遇到一些影响开发者体验问题:

本文主要阐述:具体的折腾过程参见:

知乎专栏的编辑体验真不咋滴,欢迎访问 Node.js 语雀专栏 ,以获取更佳的阅读体验。


# 快速入门通过骨架快速初始化:$ npx egg-init --type

=

ts showcase$

cd

showcase

&&

npm i$ npm run dev
上述骨架会生成一个极简版的示例更完整的示例参见:eggjs/examples/hackernews-async-ts
# 目录规范一些约束:整体目录结构上跟 Egg 普通项目没啥区别:showcase├── app│ ├── controller│ │ └── home.ts│ ├── service│ │ └── news.ts│ └── router.ts├── config│ ├── config.default.ts│ ├── config.local.ts│ ├── config.prod.ts│ └── plugin.ts├──

test

│ └── ***.d.ts├── README.md├── package.json├── tsconfig.json└── tslint.json
## Controller// app/controller/home.tsimport { 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;await ctx.render; }}## Router

// app/router.ts

import

{

Application

}

from

'egg'

;

export

default

=>

{

const

{

router

,

controller

}

=

app

;

router

.

get

;

};

## Service

// app/service/news.ts

import

{

Service

}

from

'egg'

;

export

default

class

NewsService

extends

Service

{

public

async

list

:

Promise

<

NewsItem

[]

>

{

return

[];

}

}

export

interface

NewsItem

{

id

:

number

;

title

:

string

;

}

## Middleware// app/middleware/robot.tsimport { Context } from 'egg';export default function robotMiddleware { return async => {await next; };}因为 Middleware 定义是支持入参,第一个参数为同名的 Config,如有需求,可以用完整版:// app/middleware/news.tsimport { Context, Application } from 'egg';import { BizConfig } from '../../config/config.default';// 注意,这里必须要用 ['news'] 而不能用 .news,因为 BizConfig 是 type,不是实例export default function newsMiddleware { return async => Promise) => {console.info;await next; };}## Extend// app/extend/context.tsimport { Context } from 'egg';export default { isAjax {return this.get === 'XMLHttpRequest'; },}// app.tsexport default app => { app.beforeStart => {await Promise.resolve; });};## ConfigConfig 这块稍微有点复杂,因为要支持:// app/config/config.default.tsimport { EggAppInfo, EggAppConfig, PowerPartial } from 'egg';// 提供给 config.{env}.ts 使用export type DefaultConfig = PowerPartialnfig & BizConfig>;// 应用本身的配置 Schemeexport interface BizConfig { news: {pageSize: number;serverUrl: string; };}export default => { const config = {} as PowerPartial & BizConfig; // 覆盖框架,插件的配置 config.keys = appInfo.name + '123456'; config.view = {defaultViewEngine: 'nunjucks',mapping: { '.tpl': 'nunjucks',}, }; // 应用本身的配置 config.news = {pageSize: 30,serverUrl: 'https://hacker-news.firebaseio.com/v0', }; return config;};简单版:// app/config/config.local.tsimport { DefaultConfig } from './config.default';export default => { const config: DefaultConfig = {}; config.news = {pageSize: 20, }; return config;};备注:// {egg}/index.d.tstype PowerPartial = { [U in keyof T] : T[U] extends {} PowerPartial: T[U]};## Plugin// config/plugin.tsimport { EggPlugin } from 'egg';const plugin: EggPlugin = { static: true, nunjucks: {enable: true,package: 'egg-view-nunjucks', },};export default plugin;## Typings该目录为 TS 的规范,在里面的 ***.map"

:

true

// 光注释掉 when 这行无效,需全部干掉

// "**/*.js": {

// "when": "$.ts"

// }

},

"typescript.tsdk"

:

"node_modules/typescript/lib"

}

## package.json完整的配置如下:

{

"name"

:

"hackernews-async-ts"

,

"version"

:

"1.0.0"

,

"description"

:

"hackernews showcase using typescript && egg"

,

"private"

:

true

,

"egg"

:

{

"typescript"

:

true

},

"scripts"

:

{

"start"

:

"egg-scripts start --

,

"stop"

:

"egg-scripts stop --

,

"dev"

:

"egg-bin dev -r egg-ts-helper/register"

,

"debug"

:

"egg-bin debug -r egg-ts-helper/register"

,

"test-local"

:

"egg-bin test -r egg-ts-helper/register"

,

"test"

:

"npm run lint -- --fix && npm run test-local"

,

"cov"

:

"egg-bin cov -r egg-ts-helper/register"

,

"tsc"

:

"ets && tsc -p tsconfig.json"

,

"ci"

:

"npm run lint && npm run tsc && egg-bin cov --no-ts"

,

"autod"

:

"autod"

,

"lint"

:

"tslint ."

,

"clean"

:

"ets clean"

},

"dependencies"

:

{

"egg"

:

"^2.6.0"

,

"egg-scripts"

:

"^2.6.0"

},

"devDependencies"

:

{

"@types/mocha"

:

"^2.2.40"

,

"@types/node"

:

"^7.0.12"

,

"@types/supertest"

:

"^2.0.0"

,

"autod"

:

"^3.0.1"

,

"autod-egg"

:

"^1.1.0"

,

"egg-bin"

:

"^4.6.3"

,

"egg-mock"

:

"^3.16.0"

,

"egg-ts-helper"

:

"^1.5.0"

,

"tslib"

:

"^1.9.0"

,

"tslint"

:

"^4.0.0"

,

"typescript"

:

"^2.8.1"

},

"engines"

:

{

"node"

:

">=8.9.0"

}

}

# 高级用法## 装饰器通过 TS 的装饰器,可以实现 依赖注入 / 参数校验 / 日志前置处理 等。import { Controller } from 'egg';export default class NewsController extends Controller { @GET public async detail {const { ctx, service } = this;const id = ctx.params.id;const result = await service.news.get;await ctx.render; }}目前装饰器属于锦上添花,因为暂不做约定。交给开发者自行实践,期望能看到社区优秀实践反馈,也可以参考下:egg-di 。

友情提示:要适度,不要滥用。
## tegg未来可能还会封装一个上层框架 tegg,具体 RFC 还没出,还在孕育中,敬请期待。名字典故:typescript + egg -> ts-egg -> tea egg -> 茶叶蛋 Logo:见题图
# 写在最后早在一年多前,阿里内部就有很多 BU 在实践 TS + Egg 了。随着 TS 的完善,终于能完美解决我们的开发者体验问题,也因此才有了本文。

本来以为只需要 2 个 PR 搞定的,结果变为 Hail Hydra,好长的 List:

终于完成了 Egg 2.0 发布时的一大承诺,希望能通过这套最佳实践规范,提升社区开发者的研发体验。

免责声明:本平台仅供信息发布交流之途,请谨慎判断信息真伪。如遇虚假诈骗信息,请立即举报

举报
反对 0
打赏 0
更多相关文章

评论

0

收藏

点赞