Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS IntelliSense in Egg #16

Open
whxaxes opened this issue Dec 5, 2018 · 18 comments
Open

JS IntelliSense in Egg #16

whxaxes opened this issue Dec 5, 2018 · 18 comments

Comments

@whxaxes
Copy link
Owner

whxaxes commented Dec 5, 2018

IntelliSense(智能提示) 在 IDE 中已经是标配功能,它能在某种程度上提高我们的开发效率,让我们可以更关注功能开发,而不用去来回翻看代码查看变量或者方法的定义。因此在 Egg 中我也在一直尝试更优的开发体验。而用过 Egg 的都知道,Egg 中的模块,都是 Egg Loader 自动加载进去的,因此 IDE 很难自动识别到那些被自动 load 进 egg 中的模块,IntelliSense 就自然无法起作用了。

为了解决这个问题,提升开发体验,在几个月前,我参与了 egg 支持 ts 的开发工作( 戳:当 Egg 遇到 TypeScript,收获茶叶蛋一枚 ),当时写了一个 egg-ts-helper 的工具来自动生成 d.ts 并通过 TS 提供的 Declaration Merging 的能力将 loader 加载的模块合并到 egg 的声明当中。从而实现了 TS 项目中的 IntelliSense

实现 TS 的 IntelliSense 之后,就开始考虑如何在 JS 项目中也能够跟 TS 项目一样能有智能提示,毕竟 Egg 的大部分项目都还是用 js 的,做了一些尝试之后,发现只要结合 vscode & jsdoc & egg-ts-helper 就能在 js 项目中也有跟 TS 项目中差不多的 IntelliSense 效果了,( github 中会裁剪动图,因此请点击动图以便看到全图 ):

image

具体实现如下:

声明生成

跟 TS 项目一样,先用 egg-ts-helper 在 js 项目下生成 d.ts ( 请使用 egg-ts-helper 的最新版本 1.17.0 )

$ npx ets

然后项目下的 typings/app 目录就已经生成对应的 d.ts

// typings/app/controller/index.d.ts

import 'egg';
import ExportBlog = require('../../../app/controller/blog');
import ExportHome = require('../../../app/controller/home');

declare module 'egg' {
  interface IController {
    blog: ExportBlog;
    home: ExportHome;
  }
}

然后再在项目下创建一个 jsconfig.json ,然后写入以下代码

{
  "include": [
    "**/*"
  ]
}

这个 jsconfig.jsontsconfig.json 类似,具体可以看官方文档描述,创建好这个文件并且 include **/* 之后,就会去加载 egg-ts-helper 生成的 d.ts 了。

上面这个 jsconfig 有个需要注意的点,如果打开 vscode ,vscode 提醒 Configure Excludes 的话,就需要配置一下 exclude,因为 include 的文件超过一千个的话,vscode 就会提醒让配置 exclude,如果不配置的话 vscode 就不会去处理 d.ts 的文件了,比如我这边负责的项目,前端构建多次又没有去清目录的话,轻轻松松文件数就破千了。我这边的 exclude 配置如下,可以参考一二

{
  "include": [
    "**/*"
  ],
  "exclude": [
    "node_modules/",
    "app/web/",
    "app/view/",
    "public/",
    "app/mocks/",
    "coverage/",
    "logs/"
  ]
}

完成这些配置之后,你就会发现,在 controller 这些用类的形式来写的模块中就已经可以拿到代码提示了。

image

原理跟 TS 项目一样,有了 jsconfig.json 的配置之后,vscode 会去解析 egg-ts-helper 生成的声明,这些声明会引入项目中的各个模块,通过 Declaration Merging 合并到 egg 已有的类型中。而 controller 这些模块的写法,都是需要从 egg 中 import 相关类来进行拓展的,因此自然就能顺利读到 egg 的类型,从而获得代码提示。

JSDOC

上面在类的形式写的 js 中是可以获取到代码提示了,那在非类的形式中怎么来获取呢,比如在 router.js 中,也很简单,直接通过写个 jsdoc 即可。

// app/router.js

/**
 * @param {import('egg').Application} app
 */
module.exports = app => {
  const { controller, router } = app;
  router.post('/sync', controller.home.sync);
  router.get('/', controller.blog.index);
};

看到注释中的代码了么,就可以通过这种方式就能够指定 app 为 egg 中的某个类型

@param {import('egg').Application} app

注意:如果使用了最新版本的 egg-ts-helper ,会自动生成一个声明文件将 egg 注册到一个名为 Egg 的全局 namespace 中,就可以不使用 import ,而是直接使用 Egg 来拿类型即可。

@param {Egg.Application} app

添加 jsdoc 之后就获得代码提示了

image

在其他非拓展类的模块中也差不多,比如:

middleware

// app/middleware/access.js

/**
 * @returns {(ctx: import('egg').Context, next: any) => Promise<any>}
 */
module.exports = () => {
  return async (ctx, next) => {
    await next();
  };
};

config

/**
 * @param {import('egg').EggAppInfo} appInfo
 */
module.exports = appInfo => {
  /** @type {import('egg').EggAppConfig} */
  const config = exports = {};

  config.keys = appInfo.name + '123123';

  return {
    ...config,

    biz: {
      test: '123',
    },
  };
};

上面 biz 是在最后才写到返回对象中,是为了将这种自定义类型合并到 egg 的 EggAppConfig 中。

集成到项目

安装 egg-ts-helper

$ npm install egg-ts-helper --save-dev

添加 jsconfig.json 文件

{
  "include": [
    "**/*"
  ],
  "exclude": [
    "node_modules/",
    "app/web/",
    "app/view/",
    "public/"
  ]
}

更改 egg-bin dev 的运行指令

{
  ...
  "dev": "egg-bin dev -r egg-ts-helper/register",
  ...
}

执行 dev

$ npm run dev

当看到有 [egg-ts-helper] xxx created 的日志后,就说明声明已经生成好了,用 vscode 打开项目即可获得代码提示,在 router.js 这些需要按照上面描述的加一下 jsdoc 就行了。

如果有用到 custom loader,可以看一下 egg-ts-helper#Extend 配置,再或者直接参考下面这个 demo 。

https://github.com/whxaxes/egg-boilerplate-d-js

有兴趣的可以 clone 过去自行尝试一二。

最后

要集成该代码提示功能需要具备一些 typescript 的知识基础,可以阅读一下 egg-ts-helper 生成的声明文件,知道类型是如何合并的,会更好的帮助你们获得更优异的开发体验,有相关问题可以直接到 egg-ts-helper 项目下提 issue ,我会尽快回复。

@zhump
Copy link

zhump commented Feb 18, 2019

对于extend/context.ts ,extend/helper.ts 扩展的 ctx和ctx.helper对象如何智能提示扩展后的对象方法。

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 如果你使用 egg-ts-helper 自动生成 dts 的话,就自动支持了

@zhump
Copy link

zhump commented Feb 18, 2019

其实已经使用了,但是没有提示出来。。。
image
controller.ts
image
helper.ts
image

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 你这 ts ,干嘛还用 commonjs 的写法 ... 用了 commonjs ts 的类型检查就会失效 ...

@zhump
Copy link

zhump commented Feb 18, 2019

我用了export default {} ,helper.ts里面就不能使用 this.ctx ,代码检查就失败了。
image

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 可以指定 this type

// extend/helper.ts
import { BaseContextClass } from 'egg';
export default {
  test(this: BaseContextClass) {
    return this.ctx;
  }
}

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 如果你用 module.exports ,你就跟写 js 无异了,那你干嘛还用 ts ?

@zhump
Copy link

zhump commented Feb 18, 2019

嗯嗯 被代码检查逼的 extend/helper.ts 使用了 module.exports。
采用

test(this: BaseContextClass)

确实解决了代码提示的问题。但this有时还会使用本身的方法属性。有些类 我改成 this:any了。虽然有点损失。但是代码提示和代码检查问题都解决了,多谢了🙏

@atian25
Copy link

atian25 commented Feb 18, 2019

已经内置到 egg-bin 了。

可以看下 https://zhuanlan.zhihu.com/p/56780733

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump

// extend/helper.ts
import { BaseContextClass, IHelper } from 'egg';
export default {
  test(this: BaseContextClass & IHelper) {
    return this.ctx;
  }
}

这样应该就可以完美了,这个确实要对 egg 的声明比较熟悉才知道,我晚点补充一下到官方文档

@zhump
Copy link

zhump commented Feb 18, 2019

nb...果然完美了。

@zhump
Copy link

zhump commented Feb 18, 2019

发现两个问题。
第一个:
midwayjs这样的库,他把app文件放到src,用户配置只能一个个的去覆盖,没有一个的统一baseUrl,方便用户迁移,当然手动现在可以临时解决。
https://github.com/whxaxes/egg-ts-helper/blob/master/src/index.ts

第二个:
还是上午的疑惑:

  test(this: BaseContextClass & IHelper) {
    return this.ctx;
  }

vs

  test(this: any) {
    return this.ctx;
  }

前置无法在vscode中 this.ctx.helper.test提示出来,并且编译器报错。后者可以正常使用。

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 编译器报什么错?给个可以稳定复现的 repo ,我帮你看看

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump midwayjs 不是推荐用 decorator 么,egg-ts-helper 在设计之初倒是没有想到这种完全改掉目录的,欢迎 PR

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 哦,有,你配置 cwd 即可

@zhump
Copy link

zhump commented Feb 18, 2019

😂公司不给传代码到外网。

egg-init --type=ts
yarn 
yarn dev

新增extend/helper.ts,除此之外一行代码未加。

import { BaseContextClass, IHelper } from 'egg';
export default {
  a(this: BaseContextClass & IHelper) {

  },
  b(this: any) {

  },
  c() {

  },
};

controller/home.ts ctx调用helper,如下。此时a是否被提示的。

image

@whxaxes
Copy link
Owner Author

whxaxes commented Feb 18, 2019

@zhump 因为 ctx.helper 是 IHelper 类型,所以里面的方法指定 this type 也只能指定为 IHelper ,不然就导致被过滤了,可以先这么解决

import { IHelper } from 'egg';

declare module 'egg' {
  interface IHelper extends BaseContextClass { }
}

export default {
  test(this: IHelper) {
    console.info(this.ctx);
  },

  other(this: IHelper) {

  },
};

把 BaseContextClass 拓展到 IHelper 上

@zhump
Copy link

zhump commented Feb 18, 2019

可以了,目前没啥问题了。感谢大佬解答和产出这么棒的辅助工具。👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants