Open whxaxes opened 6 years ago
期待支持 model 目录
怒赞
强烈支持
照着改造了自己的项目,方便了不少~:satisfied:
感谢,收益匪浅。请问目前 dev 下, ts 稍微报错进程就会崩掉,没有了 js 开发 egg 那种重启子进程,这块有好的办法吗 ?
@IndexXuan 在我的项目( https://github.com/whxaxes/egg-boilerplate-d-ts )中是可以的哦,如果你在出错后将代码改成正确的,就不会崩掉呀。但是如果一直不改正的话,egg 是会自动退出的。所以解决方案是,当写错代码后,尽快改正代码,再或者通过 tsconfig 配置将一些严格的规则给关掉
赞~(≧▽≦)/~
@duncup 已经支持 egg-sequelize
的 model 生成。
@whxaxes 但是 egg-mongoose 也是用的 model 文件夹,现在写死了 sequelize 。。
@xuhong 因为现在 egg-mongoose 没有提供声明,所以我这边要自动生成也不知道怎么生成,再者就是现在不是写死了 sequelize ,而是只写了 sequelize ,你可以看一下生成的声明文件内容。这个对你使用 mongoose 没影响
PowerPartial
@zhu353071655 少了个 typeof 只是多了个 undefined 类型啊,因为是 ?:
测试代码,而且app.config也没关联上
// app special config scheme
export interface BizConfig {
sourceUrl: string;
news: {
pageSize: number;
serverUrl: string;
};
logger: {
level?: 'info' | 'all';
};
}
export default (appInfo: EggAppConfig) => {
const config = {} as PowerPartial<EggAppConfig> & BizConfig;
// app special config
config.sourceUrl = `https://github.com/eggjs/examples/tree/master/${appInfo.name}`;
config.news = {
pageSize: 30,
serverUrl: 'https://hacker-news.firebaseio.com/v0',
};
config.logger = {
level: 'info'
}
// override config from framework / plugin
config.keys = appInfo.name + '123456';
config.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};
config.siteFile = {
'/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')),
};
return config;
};
@zhu353071655 EggAppConfig 里本身就有 level 的属性,用了 & 之后 ts 就取了公用类型,也就是 undefined ... ,你试一下 type bb = ('info' | 'all' | undefined) & ('a' | 'b' | undefined);
就知道了,这个是 ts 的语法。自己去看文档吧
而至于你下面说的,你跑了 ets 了生成声明了么?
@whxaxes 你是说这个么 生成了呀
// This file is created by egg-ts-helper@1.25.6
// Do not modify this file!!!!!!!!!
import 'egg';
import { EggAppConfig } from 'egg';
import ExportConfigDefault from '../../config/config.default';
type ConfigDefault = ReturnType<typeof ExportConfigDefault>;
type NewEggAppConfig = ConfigDefault;
declare module 'egg' {
interface EggAppConfig extends NewEggAppConfig { }
}
@zhu353071655 别发代码了,直接给个可复现 repo 吧
@whxaxes 有时间帮看下哈,代码提交了,主要改了config.default 和home.ts https://github.com/zhu353071655/egg-boilerplate-d-ts/commit/060477e0649704f1375195115dc586f811ea5eb8
现在主要是关联不上的问题,再问一个,有没有类型和值集都取交集的方式,&只能类型交集 值集并集,|只能类型并集值集交集,extend又不能重复定义
咱们的egg ts生产环境是建议直接编译在同文件路径下面么,不用统一编译到dist这种目录下面么
@whxaxes 还有个问题就是egg插件挂载方法到ctx.app之后开发打包成npm引入后 Application的依赖还得手动再定义一次么,不然业务工程里找不到。例如 ctx.app.mongo
还有两个问题很奇怪,在开发环境模式下用egg-bin启动服务,会有两种报错: 1、插件里的middleware提示import失败(非middleware没问题),把同级的ts文件删掉才会访问同名的js,暂时是这样解决的(官方不是说优先读js文件么,同名的情况下) 2、业务工程里会提示 can't overwrite property 'example' from /xxx/example.js by /xxxx/example.ts at /xxx/node_modules/egg-core/lib/loader/file_loader.js:78:47(目前解决方案是本地开发的时候先统一rm掉js文件,为啥会overwrite,egg-bin不是集成了tsnode么) (上面编译都是编译到同级目录,tsconfig都跟demo一致)
最近参与了 egg 的 ts 支持改造工作,写篇文章总结一二。
在上一篇文章 Typescript 在 Egg + Vue 应用中的实践 中在 Egg 项目中做了尝试,但是那时候还不够完善,比如 ts 需要通过 tsc 来生成 js,会产生大量中间文件,而且 scripts 又长又丑。再比如那时实现 controller 等的注入是需要手动写 d.ts 来实现。
所以为了能让我们用 ts 开发 egg 更方便,我们做了以下这些事。
ts-node 支持
loader 改造
我们期望在开发期不产生 js 文件就能够把应用跑起来,而业界刚好有这么个工具 ts-node,能够支持不生成中间文件,直接运行 ts 代码,于是兴冲冲的进行了尝试,结果却注意到因为 egg 是自动加载各个模块,并且 egg-loader 很多是 hardcode 写死了加载
*.js
的,所以为了让 egg 支持 ts-node,做的第一件事就是改造 egg-loader,于是提了个 PR 让 egg-loader 支持了 typescript。简单来说,就是增加了一个
typescript
的入参,如果该值为 true,并且require.extensions
中包含.ts
的处理逻辑,loader 就会去加载*.(ts|js)
。开发工具改造
在改造之前的 scripts 是这样的
又长又臭,因此仅 egg-loader 支持加载 ts 代码之后还不够,我们平时开发习惯性用
egg-bin
进行开发,而egg-bin
启动 egg 是通过 egg-cluster 启动的,所以我们需要给 egg-bin 加上一个入参,能够透传到 egg-cluster 从而开启 egg-loader 的 typescript。最终天猪那边提了 N 个 PR 之后,也完成了 egg-bin 对 ts 的支持。现在我们的 scripts 可以精简到了这样
或者这样(更建议下面这种方式)
模块注入
我们都知道,egg 加载各个模块是直接通过 loader 自动加载的,也就是模块注入是自动的,也正因为如此,ts 是没法知道 controller,service 等目录下的代码被挂载到了 egg 下,会导致在编译或者代码提示上都会有不同层面的影响,而要解决这个问题主要有两种方案,一种是使用 shepherdwind 写的 egg-di 提供的装饰器来做依赖注入,还有一种就是通过 ts 提供的 Declaration Merging 能力来编写 d.ts ,从而告诉 egg 这些模块其实是已经被引入了。
由于我个人习惯于 egg 的自动加载,所以平时项目中都选择了第二种方式,也就是通过编写 d.ts 来实现注入。而注入的原理很简单,比如在 egg 的 d.ts 中申明的 Application 对象。
其中
IController
这个 service 可以当成是一个 slot,可以在应用中通过 Declaration Merging 给 IController 添加属性,然后这些属性也就自动添加进了app.controller
中。这个也是 egg ts 最早的实践者 shepherdwind 他们想出来的办法。但是这个又带来个问题,每次新增一个 controller、service、config 等都需要手动去编写 d.ts 啊,所以又想干脆写个工具来自动生成算了,于是我就开发了一个小工具 egg-ts-helper 来减少手动编写 d.ts 的工作。
自动生成的原理很简单,其实每一个生成都是有规律可循的。比如 controller、service,我只需要知道目录结构就能够生成 d.ts。因为每个 controller 和 service 都是直接 export default 相关 class 的。
因此我只需要遍历 controller、service 的目录,根据 egg 的 loader 命名规范,将所有的 controller、service import 到 d.ts,然后再挂载到
IController
及IService
下即可。ts
typings
而 config 和 extend 就相对要麻烦一些,得去解析代码生成语法树并且做分析,好在 typescript 用来解析代码生成 AST 还是相当方便的,typescript 中提供了很多便利的工具方法用于遍历 AST、节点判断。
比如生成 AST 只需要调用
createSourceFile
即可。然后就可以对 AST 进行遍历操作并且分析,对 AST 的一些操作文档可以看 typescript 的 wiki:Using-the-Compiler-API 。不过文档写的相对比较简略,很多情况下还是需要自己摸索,有兴趣的可以参考 egg-ts-helper 的源码 了解如何使用。
有了 AST 大法之后,就可以解析 extend 目录下的代码,分析 export 出来的对象以及其属性,并且将这些属性加到 d.ts 中,从而实现 extend 的代码的代码提示。
ts
typings
而 config 的话,因为刚好 typescript 2.8 提供了 ReturnType 这个利器,因此也是可以很方便的分析 config export 出来的如果是对象,就直接用 typeof,否则则用 ReturnType。
ts
typings
然后还有最后一个,就是 plugin.ts 的生成啦,也很简单,就是直接分析 plugin 中引入的 package 名称,然后生成 d.ts 并且将 package import 进来。
ts
typings
代码提示
让 egg 支持模块注入之后,基本上该有的代码提示都有了,但是我们还想做的更多,其中一个就是 config 编写中的代码提示。也就是,当我们在写配置的时候,能够有代码提示告诉我们有哪些配置可以选择写!所以首先想到的是
但是这样会报错,为什么呢,因为 EggAppConfig 中的所有配置,都是不带
?
这个 modifier 的,因此每一个配置,都必须要将申明的配置每个都写一遍才行。这样也是不能忍受的,那该如何是好,把 EggAppConfig 的配置全部加上?
么。但是如果加上了,在使用这些配置的时候,又要么得要么就使用
!.
因为加上了
?
之后,配置的类型在 ts 看来都可能是 undefined,所以要么使用!.
要么就只能通过 if else 来判断存在后才能使用。然后又多亏了 typescript 2.8 提供了 Conditional Types 的能力,我们在 Egg 中提供了一个 PowerParitial 的类型。
能够将多层的对象申明都添加上
?
。于是我们的 config 就可以这么写也不会报错了,而且也能得到代码提示。然后如果我想将业务配置,也加到代码提示当中呢?总结下来,有两种方案:
如果不嫌烦的话,可以写一个
BizConfig
的 interface由于上面生成的 d.ts 会将 config 返回的类型注入到 app.config 中,因此这样返回之后,也可以直接在业务代码中通过
app.config.news.pageSize
获得。如果觉得写 interface 很麻烦,那么还有第二种方法,不过就得将业务配置以及 Egg 配置分开写
在 config.default.ts 中写的业务配置,如果我想在 config.local.ts 以及 config.prod.ts 中也有代码提示的话,也很简单,直接通过 ReturnType 就可以拿到 config.default 中返回的配置类型了。
由于我们在 config 中的配置,也能够被同名的 middleware 消费,而如果我们想在 middleware 中拿到配置的代码提示就可以这么写:
除了 config,我们还期望写 plugin 配置的时候也能有代码提示,所以我也给 egg 加了个
EggPlugin
的声明,因此写 plugin 配置的时候,也能够有代码提示了:以上均可以在我自己用来测试的项目 egg-boilerplate-d-ts 中进行体验测试。
最后
至此,就是此次 egg ts 改造所做的一些事了,不合理的地方欢迎指出,共同提升 egg 的 ts 开发体验。