From d3fcea189edf2686f074509590c24b9dd520919c Mon Sep 17 00:00:00 2001 From: m-shaka Date: Sat, 14 Sep 2024 11:53:23 +0900 Subject: [PATCH 1/6] docs(rpc): add a tip: compiling before using app --- docs/guides/rpc.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/guides/rpc.md b/docs/guides/rpc.md index 2b3e6c07..07afb717 100644 --- a/docs/guides/rpc.md +++ b/docs/guides/rpc.md @@ -395,3 +395,62 @@ export type AppType = typeof routes ``` You can now create a new client using the registered AppType and use it as you would normally. + +## Known issues +### IDE performance +When using RPC, the more routes you have, the slower your IDE will become. One of the main reasons for this is that massive amounts of type instantiations are executed to infer the type of your app. + +For example, suppose your app has a route like this: + +```ts +// index.ts +const app = new Hono() + .get('foo/:id', (c) => c.json({ ok: true }, 200)) + +export default app +``` + +Hono will infer the type as follows: + +```ts +const app = Hono() + .get< + "foo/:id", + "foo/:id", + JSONRespondReturn<{ ok: boolean }, 200>, + BlankInput, + BlankEnv + >('foo/:id', (c) => c.json({ ok: true }, 200)) +``` + +This is a type instantiation for a single route. While the user doesn't need to write these type arguments manually, which is a good thing, it's known that type instantiation takes much time. `tsserver` does this time consuming task every time you use the app. If you have a lot of routes, this can slow down your IDE significantly. + +However, we have some tips to mitigate this issue. + +#### compile your app before using it +`d.ts` for `index.ts` is like this: + +```ts +// index.d.ts +import { Hono } from 'hono'; +declare const app: Hono; +export default app; +``` + +Now `tsserver` doesn't instantiate type arguments of `app` every time you use it, so your IDE will be faster. + From 5f63fa6c433eb44cf9829f2c2f2f5bbc21e15420 Mon Sep 17 00:00:00 2001 From: m-shaka Date: Sat, 14 Sep 2024 12:01:02 +0900 Subject: [PATCH 2/6] docs(rpc): add a tip: specifying type arguments manually --- docs/guides/rpc.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/guides/rpc.md b/docs/guides/rpc.md index 07afb717..76c7b726 100644 --- a/docs/guides/rpc.md +++ b/docs/guides/rpc.md @@ -427,7 +427,7 @@ This is a type instantiation for a single route. While the user doesn't need to However, we have some tips to mitigate this issue. -#### compile your app before using it +#### compile your app before using it (recommended) `d.ts` for `index.ts` is like this: ```ts @@ -452,5 +452,15 @@ declare const app: Hono('foo/:id', (c) => c.json({ ok: true }, 200)) +``` + +Specifying just single type argument make a difference in performance, while it may take you a lot of time and effort if you have a lot of routes. From 1279a8cfcf4b1f35436a94fa50538ab226a9c668 Mon Sep 17 00:00:00 2001 From: m-shaka Date: Sat, 14 Sep 2024 12:09:01 +0900 Subject: [PATCH 3/6] docs(rpc): add a tip: splitting your app and client --- docs/guides/rpc.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/guides/rpc.md b/docs/guides/rpc.md index 76c7b726..49a7f734 100644 --- a/docs/guides/rpc.md +++ b/docs/guides/rpc.md @@ -464,3 +464,22 @@ const app = new Hono() Specifying just single type argument make a difference in performance, while it may take you a lot of time and effort if you have a lot of routes. +#### split your app and client into multiple files +As described in [Using RPC with larger applications](#using-rpc-with-larger-applications), you can split your app into multiple apps. You can also create a client for each app: + +```ts +// authors-cli.ts +import { app as authorsApp } from './authors' +import { hc } from 'hono/client' + +const authorsClient = hc('/authors') + +// books-cli.ts +import { app as booksApp } from './books' +import { hc } from 'hono/client' + +const booksClient = hc('/books') +``` + +This way, `tsserver` doesn't need to instantiate types for all routes at once. + From 9dd869b32117311d7ae6732a40840d87198e708b Mon Sep 17 00:00:00 2001 From: m-shaka Date: Mon, 16 Sep 2024 16:02:23 +0900 Subject: [PATCH 4/6] docs(rpc): add descriptions about compilation process --- docs/guides/rpc.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/guides/rpc.md b/docs/guides/rpc.md index 49a7f734..efba9014 100644 --- a/docs/guides/rpc.md +++ b/docs/guides/rpc.md @@ -454,6 +454,12 @@ export default app; Now `tsserver` doesn't instantiate type arguments of `app` every time you use it because it's already done. It will make your IDE a lot faster! +If your project is a monorepo, this solution does fit well. Using a tool like [`turborepo`](https://turbo.build/repo/docs), you can easily separate the server project and the client project and get better integration managing dependencies between them. + +If your client and server are in the same project, [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) of `tsc` is a good option. + +You can also coordinate your build process manually with tools like `concurrently` or `npm-run-all`. Here is [an example repository](https://github.com/yusukebe/hono-rpc-with-d-ts). + #### specify type arguments manually This is a bit cumbersome, but you can specify type arguments manually to avoid type instantiation. From deab725831b014890091cba920bd7ed35120b01c Mon Sep 17 00:00:00 2001 From: m-shaka Date: Mon, 16 Sep 2024 18:50:53 +0900 Subject: [PATCH 5/6] docs(rpc): suggest to compile the client --- docs/guides/rpc.md | 59 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/docs/guides/rpc.md b/docs/guides/rpc.md index efba9014..b7959efa 100644 --- a/docs/guides/rpc.md +++ b/docs/guides/rpc.md @@ -403,17 +403,15 @@ When using RPC, the more routes you have, the slower your IDE will become. One o For example, suppose your app has a route like this: ```ts -// index.ts -const app = new Hono() +// app.ts +export const app = new Hono() .get('foo/:id', (c) => c.json({ ok: true }, 200)) - -export default app ``` Hono will infer the type as follows: ```ts -const app = Hono() +export const app = Hono() .get< "foo/:id", "foo/:id", @@ -427,38 +425,39 @@ This is a type instantiation for a single route. While the user doesn't need to However, we have some tips to mitigate this issue. -#### compile your app before using it (recommended) -`d.ts` for `index.ts` is like this: +#### compile your code before using it (recommended) +`tsc` can do heavy tasks like type instantiation at compile time! Then, `tsserver` doesn't need to instantiate all the type arguments every time you use it. It will make your IDE a lot faster! + +Compiling your client including the server app gives you the best performance. Put the following code in your project: ```ts -// index.d.ts -import { Hono } from 'hono'; -declare const app: Hono; -export default app; +import { app } from './app' +import { hc } from 'hono/client' + +// this is a trick to calculate the type when compiling +const client = hc('') +export type Client = typeof client + +export const hcWithType = (...args: Parameters): Client => hc(...args) ``` -Now `tsserver` doesn't instantiate type arguments of `app` every time you use it because it's already done. It will make your IDE a lot faster! +After compiling, you can use `hcWithType` instead of `hc` to get the client with the type already calculated. + +```ts +const client = hcWithType('http://localhost:8787/') +const res = await client.posts.$post({ + form: { + title: 'Hello', + body: 'Hono is a cool project', + }, +}) +``` -If your project is a monorepo, this solution does fit well. Using a tool like [`turborepo`](https://turbo.build/repo/docs), you can easily separate the server project and the client project and get better integration managing dependencies between them. +If your project is a monorepo, this solution does fit well. Using a tool like [`turborepo`](https://turbo.build/repo/docs), you can easily separate the server project and the client project and get better integration managing dependencies between them. Here is [a working example](https://github.com/m-shaka/hono-rpc-perf-tips-example). -If your client and server are in the same project, [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) of `tsc` is a good option. +If your client and server are in a single project, [project references](https://www.typescriptlang.org/docs/handbook/project-references.html) of `tsc` is a good option. -You can also coordinate your build process manually with tools like `concurrently` or `npm-run-all`. Here is [an example repository](https://github.com/yusukebe/hono-rpc-with-d-ts). +You can also coordinate your build process manually with tools like `concurrently` or `npm-run-all`. #### specify type arguments manually This is a bit cumbersome, but you can specify type arguments manually to avoid type instantiation. From c4ff8164786ee98b65384386a570f3387b04d462 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Tue, 17 Sep 2024 10:44:29 +0900 Subject: [PATCH 6/6] format and add a short description for `tsserver` --- docs/guides/rpc.md | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/guides/rpc.md b/docs/guides/rpc.md index b7959efa..e7a2bb1f 100644 --- a/docs/guides/rpc.md +++ b/docs/guides/rpc.md @@ -397,35 +397,38 @@ export type AppType = typeof routes You can now create a new client using the registered AppType and use it as you would normally. ## Known issues + ### IDE performance + When using RPC, the more routes you have, the slower your IDE will become. One of the main reasons for this is that massive amounts of type instantiations are executed to infer the type of your app. For example, suppose your app has a route like this: ```ts // app.ts -export const app = new Hono() - .get('foo/:id', (c) => c.json({ ok: true }, 200)) +export const app = new Hono().get('foo/:id', (c) => + c.json({ ok: true }, 200) +) ``` Hono will infer the type as follows: ```ts -export const app = Hono() - .get< - "foo/:id", - "foo/:id", - JSONRespondReturn<{ ok: boolean }, 200>, - BlankInput, - BlankEnv - >('foo/:id', (c) => c.json({ ok: true }, 200)) +export const app = Hono().get< + 'foo/:id', + 'foo/:id', + JSONRespondReturn<{ ok: boolean }, 200>, + BlankInput, + BlankEnv +>('foo/:id', (c) => c.json({ ok: true }, 200)) ``` -This is a type instantiation for a single route. While the user doesn't need to write these type arguments manually, which is a good thing, it's known that type instantiation takes much time. `tsserver` does this time consuming task every time you use the app. If you have a lot of routes, this can slow down your IDE significantly. +This is a type instantiation for a single route. While the user doesn't need to write these type arguments manually, which is a good thing, it's known that type instantiation takes much time. `tsserver` used in your IDE does this time consuming task every time you use the app. If you have a lot of routes, this can slow down your IDE significantly. However, we have some tips to mitigate this issue. -#### compile your code before using it (recommended) +#### Compile your code before using it (recommended) + `tsc` can do heavy tasks like type instantiation at compile time! Then, `tsserver` doesn't need to instantiate all the type arguments every time you use it. It will make your IDE a lot faster! Compiling your client including the server app gives you the best performance. Put the following code in your project: @@ -438,7 +441,8 @@ import { hc } from 'hono/client' const client = hc('') export type Client = typeof client -export const hcWithType = (...args: Parameters): Client => hc(...args) +export const hcWithType = (...args: Parameters): Client => + hc(...args) ``` After compiling, you can use `hcWithType` instead of `hc` to get the client with the type already calculated. @@ -459,17 +463,20 @@ If your client and server are in a single project, [project references](https:// You can also coordinate your build process manually with tools like `concurrently` or `npm-run-all`. -#### specify type arguments manually +#### Specify type arguments manually + This is a bit cumbersome, but you can specify type arguments manually to avoid type instantiation. ```ts -const app = new Hono() - .get<'foo/:id'>('foo/:id', (c) => c.json({ ok: true }, 200)) +const app = new Hono().get<'foo/:id'>('foo/:id', (c) => + c.json({ ok: true }, 200) +) ``` Specifying just single type argument make a difference in performance, while it may take you a lot of time and effort if you have a lot of routes. -#### split your app and client into multiple files +#### Split your app and client into multiple files + As described in [Using RPC with larger applications](#using-rpc-with-larger-applications), you can split your app into multiple apps. You can also create a client for each app: ```ts @@ -487,4 +494,3 @@ const booksClient = hc('/books') ``` This way, `tsserver` doesn't need to instantiate types for all routes at once. -