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

feat(compiler): add type declaration with relay config file types #4569

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

noghartt
Copy link
Contributor

@noghartt noghartt commented Jan 7, 2024

resolve #4037

@captbaritone
Copy link
Contributor

I love this idea, and I've wanted it for some time. I was hopeful that there would be some way to use serde to derive this, but so far I haven't found such a took that works with our somewhat complicated setup.

I'm a little hesitant to merge this given that I don't think it's fully complete. For example I don't see feature flags or mutli-project config. Would you be interested in taking it the rest of the way and capturing the full config? If so, I think this would be a great addition.

If we do go this route, I'd love to add a few more things:

  1. Comments on the relevent config structs reminding people that any changes to the struct should also be added to these types.
  2. Documentation updates adding the types as comments, similar to what next-js does (/** @type {import('next').NextConfig} */

Thanks for taking the time to propose this!

@noghartt
Copy link
Contributor Author

noghartt commented Jan 9, 2024

I'm a little hesitant to merge this given that I don't think it's fully complete. For example I don't see feature flags or mutli-project config. Would you be interested in taking it the rest of the way and capturing the full config? If so, I think this would be a great addition.

I'd love to take the rest of the config and documentate it.

But yes, it isn't fully complete because I didn't find some good documentations related to the rest of the config. I saw that the relay-config crate has other fields, but I couldn't find anything that documentate these configurations, any idea where I can find it?

What I follow was the README from the react-compiler package, but I think that is a little outdated, right?

@tobias-tengler
Copy link
Contributor

If it's just about adding the TypeScript declarations, I think you can work of the declaration file I derived from the config last year: https://github.com/facebook/relay/blob/d873d5b8927b4a5bb6ca2c4b9a4c504ea0723b6b/packages/relay-compiler/index.d.ts
I would also like to continue my effort regarding the VSCode integration for config completion in JSON files: #4162 Now that I have some more experience with the compiler, maybe I have more luck than back then :D

Copy link
Contributor

@sibelius sibelius left a comment

Choose a reason for hiding this comment

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

cool

@captbaritone
Copy link
Contributor

@noghartt

I couldn't find anything that documentate these configurations, any idea where I can find it?

Sadly the best source of truth is the Rust crate. Luckily most of the fields are commented with public-facing comments. You can find it here: https://github.com/facebook/relay/blob/main/compiler/crates/relay-compiler/src/config.rs#L931

You'll need to learn a bit about how Serde (which is a Rust crate that can derived a parser for a Rust structure based on its definition) works to understand what the JS/JSON shape will look like. The main thinks to look out for:

  1. Each child value/object will have its own deserialization logic, usually derived from a Serde derived trait at the structs declaration (simple values like strings/paths/etc generally have intuitive inputs)
  2. rename_all = "camelCase" converts snake_case names to camelCase
  3. #[serde(flatten)] will push all the keys of the child object up into the parent object.

Thank you so much for your interest in this. The lack of documentation for the Relay config is a huge gap, and TypeScript types will offer both in-editor support and human readable documentation that we can point people to.

@noghartt
Copy link
Contributor Author

cc @captbaritone @tobias-tengler

I improved the types and wrote more JSDocs based on the config.rs as mentioned before. Improve the doc too, what do you think?

Copy link
Contributor

@captbaritone captbaritone left a comment

Choose a reason for hiding this comment

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

So exciting! This is going to be such a good improvement. A few requests:

  1. Could you do another pass to ensure no properties which are actually optional are marked as non-optional?
  2. In the Rust code, can you add a comment at the top of each struct that is part of this config with a note that any changes made to the struct should also be made to these types?

packages/relay-compiler/index.d.ts Outdated Show resolved Hide resolved
packages/relay-compiler/index.d.ts Outdated Show resolved Hide resolved
variableNamesComment?: boolean;
featureFlags?: RelayConfigFeatureFlags;
/** A placeholder for allowing extra information in the config file */
extra: unknown;
Copy link
Contributor

Choose a reason for hiding this comment

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

Presumably this should be optional?

Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably just be [configKey: string]: any?

Copy link
Contributor Author

@noghartt noghartt Jan 10, 2024

Choose a reason for hiding this comment

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

I pushed a fix into this tht turns it into an optional field.

This should probably just be [configKey: string]: any?

In case, I think that if we should type it, we should type accordingly to the serde_json::Value struct, that can be any JSON-friendly value. What do you think?

@captbaritone
Copy link
Contributor

Another point: We don't have authoritative docs on this config. As a baseline we should link out to this definition from our docs as a "Here's where you can go to find out what options are available in the config". As a stretch, I wonder if it would be possible to use something like https://www.npmjs.com/package/docusaurus-plugin-typedoc to generate an actual docs page (or pages) from this?

Maybe we can start with the former and then (optionally) explore the later in a separate PR.

@noghartt
Copy link
Contributor Author

Another point: We don't have authoritative docs on this config. As a baseline we should link out to this definition from our docs as a "Here's where you can go to find out what options are available in the config". As a stretch, I wonder if it would be possible to use something like https://www.npmjs.com/package/docusaurus-plugin-typedoc to generate an actual docs page (or pages) from this?

Maybe we can start with the former and then (optionally) explore the later in a separate PR.

Would be really cool if we could have a docs specifically for these configs, explaining which things each field are doing. I think that we can derive some docs from this config, like one about feature flags and what are the features related.

@noghartt
Copy link
Contributor Author

Anything else I need to fix, what do you think?

cc @captbaritone @tobias-tengler

*/
jsModuleFormat?: 'commonjs' | 'haste';
/** Options for configuring the output of compiler diagnostics. */
diagnosticReportConfig: RelayConfigDiagnosticReport;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to understand defaults a little better, since I know a config without this value (for example, but others as well) is accepted by the compiler.

Ah, looking a bit more closely, it looks like the struct itself is annotated with default. Can you check each of the structs and see how that impacts these types?

#[serde(deny_unknown_fields, rename_all = "camelCase", default)]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I checked all the structs and update some JSDocs with new @default values. I think that everything is OK now, what do you think?

@@ -74,7 +75,7 @@ module.exports = {
```

This configuration also can be specified in `"relay"` section of the `package.json` file.
For more details, and configuration options see: [Relay Compiler Configuration](https://github.com/facebook/relay/tree/main/packages/relay-compiler)
For more details, and configuration options see: [Relay Compiler Configuration](https://github.com/facebook/relay/tree/main/packages/relay-compiler/index.d.ts)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added! I updated the README with a note admonition containing the same info.

packages/relay-compiler/index.d.ts Outdated Show resolved Hide resolved
packages/relay-compiler/index.d.ts Outdated Show resolved Hide resolved
@facebook-github-bot
Copy link
Contributor

@captbaritone has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@captbaritone
Copy link
Contributor

I've imported this, but it will race with #4585 which changes the config types. I'll try to fix it up internally before I land.

@captbaritone
Copy link
Contributor

Hey. After importing this, I'm seeing a bunch more errors. How have you been validating this addition? Could you find a way to ensure it's working end to end on a given config file? Maybe include a screenshot of what errors look like in VSCode?

| {
/** The path of your schema file. */
schema: string
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think a semicolon is valid here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that this isn't a problem, but I removed it to follow the pattern

* ```
*/
diagnosticReportConfig?: RelayConfigDiagnosticReportConfig;
featureFlags?: RelayFeatureFlags;
Copy link
Contributor

Choose a reason for hiding this comment

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

This type is not defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

* (TypeScript only) Whether to use the `import type` syntax introduced in Typescript version 3.8. This will prevent warnings from `importsNotUsedAsValues`.
*/
useImportTypeSyntax?: boolean;
customScalarTypes?: CustomScalarTypes;
Copy link
Contributor

Choose a reason for hiding this comment

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

This type is not defined

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

*
* @default false
*/
noFutureProofEnums?: NoFutureProofEnums;
Copy link
Contributor

Choose a reason for hiding this comment

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

This type is not defined

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I defined this type, fixed

* inlined or not depends on whether it actually uses that directive.
*/
no_inline?: RelayConfigFeatureFlag;
enable_3d_branch_arg_generation?: bool;
Copy link
Contributor

Choose a reason for hiding this comment

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

bool is not a valid TypeScript type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, nice catch, fixed

/** Create normalization nodes for client edges to client objects. */
emit_normalization_nodes_for_client_edges?: boolean;
/** Fully build the normalization AST for Resolvers. */
enable_resolver_normalization_ast?: bool;
Copy link
Contributor

Choose a reason for hiding this comment

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

bool is not a valid TypeScript type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@captbaritone
Copy link
Contributor

@noghartt Can you please add a test plan that demonstrates this working end to end? For example, a screenshot of it reporting errors on an invalid config file in VSCode? (and one of it not erroring on a valid config file)?

@noghartt
Copy link
Contributor Author

@noghartt Can you please add a test plan that demonstrates this working end to end? For example, a screenshot of it reporting errors on an invalid config file in VSCode? (and one of it not erroring on a valid config file)?

Of course. But in case, VSCode and LSP does not infer type checking over type infered objects that comes from JSDocs annotation, so it'll won't trigger a visual error for the final user, only if they have a relay.config.ts and let the tsserver does it for us.

But in case, I took some screenshots that shows this type working, I'll let also a test plan containing how I test it:

Test plan

  • build the packages using gulp with the command yarn gulp mainrelease
  • create a new relay.config.js file
  • add type annotations targeting the exported RelayConfig type from ./dist build package

the boilerplate

// relay.config.js

/** @type {import('./dist/relay-compiler').RelayConfig} */
module.exports = {
  // ...
}

Screenshots

starting single project
image

multi project config
image

feature flags
image


As an example, even the next.config.js aren't being typechecked by the LSP.

image

@facebook-github-bot
Copy link
Contributor

@captbaritone has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@@ -17,6 +17,7 @@ use serde::Serialize;

use crate::Rollout;

/// **NOTE**: Every change on this struct should be reflected into the `relay-compiler/index.d.ts` file.
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can make use of something like this: https://github.com/Aleph-Alpha/ts-rs here?

We would generate these typescript definitions based on the actual rust types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did some POCs with specta yesterday, it seems to works well. I think that we can bring it to these structs too.

They have just a limitation around some JSDocs comments, like the @default and others, but it's working.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, @noghartt! I think this would be a much better solution to always keep these definitions in sync.

Copy link
Contributor

Choose a reason for hiding this comment

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

@noghartt Just checking in here. Were you able to make any progress with Spectra? Do you think it can offer a viable automated solution?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@noghartt Just checking in here. Were you able to make any progress with Spectra? Do you think it can offer a viable automated solution?

I think that we can bring an automated solution yes. But as I commented, specta doesn't seem to fit all the types for us, as an example, it doesn't fill the JSDocs related fields, like the @default. I couldn't find a workaround for that scenario.

Except that, it seems to works well. Do you think that we can bring this improvement in this PR or maybe moving to another?

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

Successfully merging this pull request may close these issues.

Improve docs for relay.config.js with Typescript support from JSDoc
6 participants