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

Feature Request: Generate types from GraphQL in Dart files #130

Open
baconcheese113 opened this issue Jul 19, 2022 · 1 comment
Open

Feature Request: Generate types from GraphQL in Dart files #130

baconcheese113 opened this issue Jul 19, 2022 · 1 comment
Labels
enhancement New feature or request

Comments

@baconcheese113
Copy link
Contributor

As discussed in #123

Do you have plans to allow Queries/Fragments to be defined in dart files? Can you elaborate about where the challenge is here and how it might be approached?

Response

I've considered it, it amounts to analyzing the dart files and come up with a way to link the generated queries to the right methods/widgets. It is doable, but will be different than Relay because of the difference between the TS/Flow type system and Darts typesystem. Not unmanageable, but not started.

While using graphql_flutter without type generation from Artemis, I would define my queries/fragments in the same file as the widget that was planned to use them. This made for a great developer experience for several reasons:

  1. Easy upkeep/refactoring: can use Ctrl+F to Ctrl+D to quickly check if variables are still used or rename them
  2. Less hierarchy bloat: If a widget in home.dart had both a query and a mutation, it would go from 1 file to 6 when switched to use graphql_codegen...
    home.dart
    home.query.graphql
    home.query.graphql.dart
    home.query.graphql.g.dart
    home.mutation.graphql
    home.mutation.graphql.dart
    home.mutation.graphql.g.dart
    The generated files are fine, because they don't require much manual work and can be ignored in a __generated__ folder easily, but needing to create several .graphql files for each widget quickly adds up
  3. Easier to drill into dependencies: Since importing the fragment came from the file housing the widget, I could command click into the child fragment quickly and follow the hierarchy down. With operations in separate files I either have to command click into the type and look at the class names that it implements and then pair them to the widget, or find the associated widget, switch to that file, search for the child fragment, then search for a widget that uses it's type. Or take guesses at the other files in the folder.

Before switching to generated types, I would declare fragments in the widget as a static variable like below in FeedCard

class FeedCard extends StatelessWidget {
  final Map<String, dynamic> hubFrag;
  const FeedCard({Key? key, required this.hubFrag}) : super(key: key);

  static final fragment = addFragments(gql(r'''
      fragment feedCard_hub on Hub {
        id
        name
        serial
        ...feedCardArm_hub
        ...feedCardMap_hub
        ...hubUpdater_hub
      }
    '''), [FeedCardArm.fragment, FeedCardMap.fragment, HubUpdater.fragment]);
    
  @override
  Widget build(BuildContext context) {
  ...

where addFragments is defined as

DocumentNode addFragments(DocumentNode doc, List<DocumentNode> fragments) {
  final newDefinitions = Set<DefinitionNode>.from(doc.definitions);
  for (final frag in fragments) {
    newDefinitions.addAll(frag.definitions);
  }
  return DocumentNode(definitions: newDefinitions.toList(), span: doc.span);
}

In React/ApolloClient I would define them as below in SceneEditor the sceneEditorQuery type is generated and SceneViewer exports an object named fragments where the keys are each fragment for it's component. Again, able to click straight into the file from wherever the fragment is spread

export default function SceneEditor() {
  const { data, loading, error } = useQuery<sceneEditorQuery>(
    gql`
      query sceneEditorQuery($id: Int!) {
        scene(where: { id: $id }) {
          id
          ...sceneViewer_scene
        }
      }
      ${SceneViewer.fragments.scene}
    `,
    {
      variables: { id: currentScene },
    },
  )
  ...
}

In React/Relay I used the FragmentContainer HOC:

function TodoList(props) => // return component using props.list fragment

export default createFragmentContainer(TodoList, {
  // This `list` fragment corresponds to the prop named `list` that is
  // expected to be populated with server data by the `<TodoList>` component.
  list: graphql`
    fragment TodoList_list on TodoList {
      # Specify any fields required by '<TodoList>' itself.
      title
      # Include a reference to the fragment from the child component.
      todoItems {
        ...TodoItem_item
      }
    }
  `,
});

Although they have since introduced a hook for defining the fragment

import type {UserComponent_user$key} from 'UserComponent_user.graphql';
const UsernameSection = require('./UsernameSection.react');

type Props = {
  user: UserComponent_user$key,
};

function UserComponent(props: Props) {
  const user = useFragment(
    graphql`
      fragment UserComponent_user on User {
        name
        age
        # Include child fragment:
        ...UsernameSection_user
      }
    `,
    props.user,
  );

  ....
}
@budde377
Copy link
Contributor

budde377 commented Jul 24, 2022

I love the idea, just a note on the file bloat: You can definitely define multiple operations/fragments per .graphql file.

@budde377 budde377 added the enhancement New feature or request label Aug 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants