salesforce / lwc

⚡️ LWC - A Blazing Fast, Enterprise-Grade Web Components Foundation
https://lwc.dev
Other
1.63k stars 395 forks source link

graphql loader at build time as an sfdx plugin using babel #3615

Closed AllanOricil closed 1 year ago

AllanOricil commented 1 year ago

I was creating my own loader/transformer to enable graphql imports and then I realized that what I did doesnt work when a .gql|graphql file has more than 1 query, or a fragment. My loader is basically copying and pasting the whole content of the query file in the lwc component, inside the wire method.

So since my idea doesnt work for these use cases I gave up and googled an alternative. Then I found this babel plugin

https://github.com/gajus/babel-plugin-graphql-tag

The plan is to call this plugin during some sfdx hooks, such as predeploy. Would lwc components transformed with this plugin be deployable?

I'm not sure if the platform enforces this sintax


const bar = gql`query foo{}`

If it does, then we can't get the benefits of this plugin.

I think someone should take a look at this. The graphql feature is horrible without loaders.

AllanOricil commented 1 year ago

https://github.com/salesforce/lwc/issues/3581

AllanOricil commented 1 year ago

@mburr-salesforce @caridy @nolanlawson would you be able to tell why the platform does not let us to pre process graphql queries ? I used this loader in this example to pre process the query but it did not work. My goal with this experiment was to enable salesforce developers to work with .graphql|gql files instead of poluting their lwc components with these huge strings, as well as enabling the other benefits described here.

import { LightningElement, wire } from "lwc";
import { graphql } from "lightning/uiGraphQLApi";
import { bigAccounts } from "./test.js";

export default class AccountsGQL extends LightningElement {

  ...

  @wire(graphql, {
    query: bigAccounts,
    variables: "$variables",
    operationName: "bigAccounts"
  })

  ...
}

//pre processed graphql query

var bigAccounts = {
  kind: "Document",
  definitions: [
    {
      kind: "OperationDefinition",
      operation: "query",
      name: {
        kind: "Name",
        value: "bigAccounts"
      },
      variableDefinitions: [
        {
          kind: "VariableDefinition",
          variable: {
            kind: "Variable",
            name: {
              kind: "Name",
              value: "minAmount"
            }
          },
          type: {
            kind: "NamedType",
            name: {
              kind: "Name",
              value: "Currency"
            }
          },
          directives: []
        }
      ],
      directives: [],
      selectionSet: {
        kind: "SelectionSet",
        selections: [
          {
            kind: "Field",
            name: {
              kind: "Name",
              value: "uiapi"
            },
            arguments: [],
            directives: [],
            selectionSet: {
              kind: "SelectionSet",
              selections: [
                {
                  kind: "Field",
                  name: {
                    kind: "Name",
                    value: "query"
                  },
                  arguments: [],
                  directives: [],
                  selectionSet: {
                    kind: "SelectionSet",
                    selections: [
                      {
                        kind: "Field",
                        name: {
                          kind: "Name",
                          value: "Account"
                        },
                        arguments: [
                          {
                            kind: "Argument",
                            name: {
                              kind: "Name",
                              value: "where"
                            },
                            value: {
                              kind: "ObjectValue",
                              fields: [
                                {
                                  kind: "ObjectField",
                                  name: {
                                    kind: "Name",
                                    value: "AnnualRevenue"
                                  },
                                  value: {
                                    kind: "ObjectValue",
                                    fields: [
                                      {
                                        kind: "ObjectField",
                                        name: {
                                          kind: "Name",
                                          value: "gte"
                                        },
                                        value: {
                                          kind: "Variable",
                                          name: {
                                            kind: "Name",
                                            value: "minAmount"
                                          }
                                        }
                                      }
                                    ]
                                  }
                                }
                              ]
                            }
                          }
                        ],
                        directives: [],
                        selectionSet: {
                          kind: "SelectionSet",
                          selections: [
                            {
                              kind: "Field",
                              name: {
                                kind: "Name",
                                value: "edges"
                              },
                              arguments: [],
                              directives: [],
                              selectionSet: {
                                kind: "SelectionSet",
                                selections: [
                                  {
                                    kind: "Field",
                                    name: {
                                      kind: "Name",
                                      value: "node"
                                    },
                                    arguments: [],
                                    directives: [],
                                    selectionSet: {
                                      kind: "SelectionSet",
                                      selections: [
                                        {
                                          kind: "Field",
                                          name: {
                                            kind: "Name",
                                            value: "Id"
                                          },
                                          arguments: [],
                                          directives: []
                                        },
                                        {
                                          kind: "Field",
                                          name: {
                                            kind: "Name",
                                            value: "Name"
                                          },
                                          arguments: [],
                                          directives: [],
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              {
                                                kind: "Field",
                                                name: {
                                                  kind: "Name",
                                                  value: "value"
                                                },
                                                arguments: [],
                                                directives: []
                                              }
                                            ]
                                          }
                                        },
                                        {
                                          kind: "Field",
                                          name: {
                                            kind: "Name",
                                            value: "AnnualRevenue"
                                          },
                                          arguments: [],
                                          directives: [],
                                          selectionSet: {
                                            kind: "SelectionSet",
                                            selections: [
                                              {
                                                kind: "Field",
                                                name: {
                                                  kind: "Name",
                                                  value: "displayValue"
                                                },
                                                arguments: [],
                                                directives: []
                                              }
                                            ]
                                          }
                                        }
                                      ]
                                    }
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ],
  loc: {
    start: 0,
    end: 339,
    source: {
      body: "query bigAccounts($minAmount: Currency) {\\n  uiapi {\\n    query {\\n      Account(where: { AnnualRevenue: { gte: $minAmount } }) {\\n        edges {\\n          node {\\n            Id\\n            Name {\\n              value\\n            }\\n            AnnualRevenue {\\n              displayValue\\n            }\\n          }\\n        }\\n      }\\n    }\\n  }\\n}\\n",
      name: "GraphQL request",
      locationOffset: {
        line: 1,
        column: 1
      }
    }
  }
};
export { bigAccounts };
AllanOricil commented 1 year ago

Maybe the graphql adapter is expecting a string? Could not find docs about it.

mburr-salesforce commented 1 year ago

The babel plugin you reference appears to be specific to react & apollo. Despite the similarities in naming, the Salesforce gql performs additional functions that are not readily amenable with development- or compile-time query parsing. While the Salesforce gql does transform the GraphQL query into an AST representation, it more importantly allows for deployment-time validation of the GraphQL query against the entities & fields defined for the org. This is much easier for us to do when the query is represented as a string. The current structure that you see in the Salesforce GraphQL support was very intentionally designed to give us the ability to evolve/optimize how/where the query is handled internally by the framework without breaking backwards compatibility. If you were to look at how the queries are loaded in the browser, you'd see that we actually do factor them out into a separate module like you're trying to do. :-)

We have plans for future improvements that would allow component authors to import both GraphQL fragments and querys from other modules as you describe above, but that work did not make it into the initial release. This work will require orchestration across several framework teams within Salesforce & It will be up to our product management folks to decide when that work happens.

Thanks as always for your feedback!