forked from OpenNeo/impress
Always been a bit annoyed to have even the item name load in so weird and slow 😅 this fixes it to come in much faster!
This also allows us to SSR the item name in the page title, since we've put it in the GraphQL cache at SSR time!
102 lines
3.2 KiB
JavaScript
102 lines
3.2 KiB
JavaScript
const { InMemoryCache } = require("@apollo/client");
|
|
const { ApolloServer, gql } = require("apollo-server");
|
|
const { config } = require("./index");
|
|
|
|
const server = new ApolloServer(config);
|
|
|
|
async function loadGraphqlQuery({ query, variables = {} }) {
|
|
// Edit the query to serve our needs, then send a local in-memory request to
|
|
// a simple `ApolloServer` instance just for SSR.
|
|
const convertedQuery = addTypenameToSelections(removeClientOnlyFields(query));
|
|
const { data, errors } = await server.executeOperation({
|
|
query: convertedQuery,
|
|
variables,
|
|
});
|
|
|
|
// To get the cache data, we build a new temporary cache object, write this
|
|
// query result to it, and dump it out. (Building a normalized cache state is
|
|
// really tricky, this simplifies it a lot without bringing in the weight of
|
|
// a whole client!)
|
|
const cache = new InMemoryCache();
|
|
cache.writeQuery({ query, variables, data });
|
|
const graphqlState = cache.extract();
|
|
|
|
// We return the data, errors, and cache state: we figure callers will almost
|
|
// always want the errors and state, and may also want the data!
|
|
return { data, errors, graphqlState };
|
|
}
|
|
|
|
/**
|
|
* addTypenameToSelections recursively adds __typename to every selection set
|
|
* in the query, and returns a copy. This enables us to use the query data to
|
|
* populate a cache!
|
|
*/
|
|
function addTypenameToSelections(node) {
|
|
if (node.kind === "SelectionSet") {
|
|
return {
|
|
...node,
|
|
selections: [
|
|
{
|
|
kind: "Field",
|
|
name: {
|
|
kind: "Name",
|
|
value: "__typename",
|
|
arguments: [],
|
|
directives: [],
|
|
},
|
|
},
|
|
...node.selections.map((s) => addTypenameToSelections(s)),
|
|
],
|
|
};
|
|
} else if (node.selectionSet != null) {
|
|
return {
|
|
...node,
|
|
selectionSet: addTypenameToSelections(node.selectionSet),
|
|
};
|
|
} else if (node.kind === "Document") {
|
|
return {
|
|
...node,
|
|
definitions: node.definitions.map((d) => addTypenameToSelections(d)),
|
|
};
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* removeClientOnlyFields recursively removes any fields marked with `@client`
|
|
* in the given GraphQL document node, and returns a new copy. This enables us
|
|
* to borrow queries and fragments from the client, and ignore the fields they
|
|
* won't need preloaded for SSR. (This isn't just an optimization: the server
|
|
* can't handle the `@client` directive and the query will fail if present!)
|
|
*/
|
|
function removeClientOnlyFields(node) {
|
|
if (node.kind === "SelectionSet") {
|
|
return {
|
|
...node,
|
|
selections: node.selections
|
|
.filter(
|
|
(selection) =>
|
|
!(
|
|
selection.kind === "Field" &&
|
|
selection.directives.some((d) => d.name.value === "client")
|
|
)
|
|
)
|
|
.map((selection) => removeClientOnlyFields(selection)),
|
|
};
|
|
} else if (node.selectionSet != null) {
|
|
return { ...node, selectionSet: removeClientOnlyFields(node.selectionSet) };
|
|
} else if (node.kind === "Document") {
|
|
return {
|
|
...node,
|
|
definitions: node.definitions.map((d) => removeClientOnlyFields(d)),
|
|
};
|
|
} else {
|
|
return node;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
loadGraphqlQuery,
|
|
gql,
|
|
};
|