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

Support for Next.JS projects in generated clients #289

Closed
wants to merge 3 commits into from

Conversation

DomBlack
Copy link
Contributor

@DomBlack DomBlack commented Jun 24, 2022

This commit adds support for generated Typescript clients to be
compitable with Next.JS applications with the new --preset nextjs argument.

The existing client was not compiatble as we where using namespaces
to mimic the packages within the Encore application, however namespaces
are somewhat depcreated and not all transpliers support them; the key one
here being Babel (which Next.JS uses under the hood).

When turned on the new --preset nextjs flag, therefore prevents the code generator
from emitting namespaces, and instead it uses the package name as a prefix for
generated types; such as $package_$struct.

To make NextJS development easier, this flag also tells the TypeScript code generator
to generate SWR wrappers for all API's, while maintaining the
promise based functions if direct calls are still required (i.e. to perform actions).

If the original api call was:

const api = new Client()

const rsp: Promise<Data> = api.MySvc.MyAPI("segment", { name: "bob" })

The SWR wrapper will be:

const api = new Client() // this needs to be a singleton for the application instance

const rsp: SWRResponse<Data> = api.MySvc.useMyAPI("segment", { name: "bob" }, { refreshInternval: 1000 })

@DomBlack DomBlack requested a review from eandre June 24, 2022 15:24
@encore-cla
Copy link

encore-cla bot commented Jun 24, 2022

All committers have signed the CLA.

This commit adds support for generated Typescript clients to be
compitable with [Next.JS](https://nextjs.org/) applications with the new `--nextjs` argument.

The existing client was not compiatble as we where using namespaces
to mimic the packages within the Encore application, however namespaces
are somewhat depcreated and not all transpliers support them; the key one
here being Babel (which Next.JS uses under the hood).

When turned on the new `--nextjs` flag, therefore prevents the code generator
from emitting namespaces, and instead it uses the package name as a prefix for
generated types; such as `$package_$struct`.

To make NextJS development easier, this flag also tells the TypeScript code generator
to generate [SWR](https://swr.vercel.app/) wrappers for all API's, while maintaining the
promise based functions if direct calls are still required (i.e. to perform actions).

If the original api call was:
```
const api = new Client()

const rsp: Promise<Data> = api.MySvc.MyAPI("segment", { name: "bob" })
```

The SWR wrapper will be:
```
const api = new Client() // this needs to be a singleton for the application instance

const rsp: SWRResponse<Data> = api.MySvc.useMyAPI("segment", { name: "bob" }, { refreshInternval: 1000 })
```
@eandre
Copy link
Member

eandre commented Jun 27, 2022

This looks great! My only concern is that Next.JS is a moving target, and I can imagine lots of other scenarios where the same code generation options (no namespaces, swr support) are desirable by others who don't necessarily use Next. That would lead to the regrettable situation where if Next.JS changes, we'd be torn between updating the --nextjs flag to match the new behavior (and breaking other users who may still want the old behavior), and introducing a new option to select the new behavior (causing --nextjs to lose its intended meaning).

I think a better solution is to introduce code generation options that map to specific functionality rather than moving targets. So in this case we could have --ts.namespaces=false and --ts.swr=true or something to that effect.

@DomBlack
Copy link
Contributor Author

I think a better solution is to introduce code generation options that map to specific functionality rather than moving targets. So in this case we could have --ts.namespaces=false and --ts.swr=true or something to that effect.

Hmm; what we we do both; --nextjs as a moving target that we keep as a preset for the current target of NextJS, and then specific toggles underneath?

@DomBlack
Copy link
Contributor Author

@eandre; I've updated the CLI options and per your suggestion an introduced a --preset nextjs

@@ -214,12 +235,16 @@ export namespace echo {

// Now make the actual call to the API
const resp = await this.baseClient.callAPI("POST", `/echo.HeadersEcho`, undefined, {headers})
try {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the addition of try { this newline seems out of place. Remove?

try {
return await resp.json() as EmptyData
} catch (err) {
throw new APIError(500, { code: ErrCode.DataLoss, message: "unable to unmarshal the response", details: err })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataLoss is not meant for this but rather fatal, permanent data loss on the server side. Change to Internal

@@ -44,7 +45,7 @@ func Detect(path string) (lang Lang, ok bool) {
}

// Client generates an API client based on the given app metadata.
func Client(lang Lang, appSlug string, md *meta.Data) (code []byte, err error) {
func Client(lang Lang, appSlug string, md *meta.Data, tsOptions *daemonpb.GenClientRequest_TypeScriptOptions) (code []byte, err error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess making the typescript options part of this signature is intended as a short-term thing? Ideally a nicer interface would be nicer to represent lang+options as a single protobuf message with a oneof

@@ -54,7 +55,11 @@ func Client(lang Lang, appSlug string, md *meta.Data) (code []byte, err error) {
var gen generator
switch lang {
case LangTypeScript:
gen = &typescript{generatorVersion: typescriptGenLatestVersion}
if tsOptions == nil {
tsOptions = &daemonpb.GenClientRequest_TypeScriptOptions{Namespaces: true}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing this here we could make the client always pass in options as it's the thing that's aware of the presets, and the base case of "no preset" is essentially just the default preset

Comment on lines +82 to +84



Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of empty newlines here, is that intended?

Comment on lines +94 to +95
}
// CallParameters is the type of the parameters to a method call, but require headers to be a Record type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a newline here

Comment on lines +103 to +104


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be single newline?

lang = string(codegen.LangTypeScript)
tsOptions = &daemonpb.GenClientRequest_TypeScriptOptions{
Namespaces: false,
Swr: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sold on coupling swr with next.js. Yes, swr is also developed by the same authors but I believe React Query is increasingly becoming the library of choice for this sort of thing.

What's more, the useBlah helpers are only valid for endpoints that fetch data, not for mutations, since they regularly fire multiple times in the background. Unintentionally calling a stateful, mutating endpoint when not intending to is recipe for disaster, so unless we have a good way of representing which endpoints actually do that and which don't we shouldn't generate such wrappers I don't think.

Comment on lines +48 to +49
Support presets are:
nextjs: Generates a TypScript client for use within a Next.js application
Copy link
Member

@eandre eandre Jun 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Support presets are:
nextjs: Generates a TypScript client for use within a Next.js application
Supported presets are:
nextjs: Generates a TypeScript client for use within a Next.js application

if output == "" && lang == "" {
fatal("specify at least one of --output or --lang.")
if output == "" && lang == "" && preset == "" {
fatal("specify at least one of --output, --lang or --preset.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned that this makes for quite the analysis paralysis when trying to decide how to use this command. Could we combine lang and preset somehow to just a single option? We could have --lang=ts choose the default preset and --lang=ts.nextjs (or --lang=ts/nextjs) choose the nextjs preset, or whatever.

@melkstam
Copy link
Contributor

melkstam commented Aug 4, 2022

We have earlier had compilation errors with namespaces in the generated TS client defined after the class in which they are referenced. However, as of NextJS 12.2.0, this is no longer an issue for us. The generated client compiles and works without any problems. I have not managed to locate the PR that solves this, but have not looked to much at it.

I thought it be good for you to know this since it might change the scope of this PR!

@DomBlack
Copy link
Contributor Author

I'm going to close this PR for now as I'm not sure we need it anymore.

We can re-open it if we needed it agian

@DomBlack DomBlack closed this Sep 29, 2022
auto-merge was automatically disabled September 29, 2022 12:51

Pull request was closed

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

Successfully merging this pull request may close these issues.

None yet

3 participants