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

perf: reduce query size #1594

Merged
merged 2 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 131 additions & 8 deletions packages/npm/@amazeelabs/codegen-operation-ids/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ const schema = buildSchema(`
`);

describe('mode: map', () => {
function runPlugin(documents: Array<Types.DocumentFile>) {
return plugin(schema, documents, {}, { outputFile: 'map.json' });
function runPlugin(
documents: Array<Types.DocumentFile>,
mode: 'inline' | 'attach' | undefined = 'inline',
) {
return plugin(
schema,
documents,
{ fragments: mode },
{ outputFile: 'map.json' },
);
}

it('creates a map entry for a single operation', async () => {
Expand Down Expand Up @@ -123,7 +131,30 @@ describe('mode: map', () => {
`);
});

it('adds used fragments', async () => {
it('adds used fragments inline', async () => {
const result = await runPlugin([
{
location: 'a.gql',
document: parse(`
fragment Page on Page { title }
query Home { loadPage(path: "/") { ...Page } }
`),
},
]);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:dc086da112964a8f85ae0520dab3fa68a84e067ee6aa8b0e06305f4cb5e9898a": "query Home {
loadPage(path: "/") {
... on Page {
title
}
}
}",
}
`);
});

it('attaches used fragments', async () => {
const result = await runPlugin([
{
location: 'a.gql',
Expand All @@ -135,7 +166,7 @@ describe('mode: map', () => {
]);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:37d40553a898c4026ba372c8f42af3df9c3451953b65695b823a8e1e7b5fd90d": "query Home {
"HomeQuery:dc086da112964a8f85ae0520dab3fa68a84e067ee6aa8b0e06305f4cb5e9898a": "query Home {
loadPage(path: "/") {
... on Page {
title
Expand All @@ -145,6 +176,7 @@ describe('mode: map', () => {
}
`);
});

it('inlines multiple invocations', async () => {
const result = await runPlugin([
{
Expand All @@ -166,7 +198,7 @@ describe('mode: map', () => {
]);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:e8b5953fe0f339244ebb14102eddc5d0e23259606de6f697574f69bfe468ac53": "query Home {
"HomeQuery:9e615243c0c61d86531091aa395544df99db822d94d50a761c2809d61caf3164": "query Home {
loadPage(path: "/") {
... on Page {
title
Expand All @@ -190,6 +222,59 @@ describe('mode: map', () => {
}
`);
});

it('attaches multiple invocations', async () => {
const result = await runPlugin(
[
{
location: 'a.gql',
document: parse(`
fragment Page on Page { title, related { path } }
fragment Teaser on Page { path }
query Home {
loadPage(path: "/") {
...Page,
related {
...Page
...Teaser
}
}
}
`),
},
],
'attach',
);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:c25b93055bb9dce4d474a8e9031df3842a686ad4ad1b3ce2806ef528eb5b1c47": "query Home {
loadPage(path: "/") {
...Page
related {
...Page
...Teaser
}
}
}
fragment Page on Page {
title
related {
path
}
}
fragment Page on Page {
title
related {
path
}
}
fragment Teaser on Page {
path
}",
}
`);
});

it('adds nested fragments', async () => {
const result = await runPlugin([
{
Expand All @@ -207,7 +292,7 @@ describe('mode: map', () => {
]);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:37d40553a898c4026ba372c8f42af3df9c3451953b65695b823a8e1e7b5fd90d": "query Home {
"HomeQuery:7f81601e1df0c179be7354f3f834201b9615d0ea191d4ba5f83bb45b0bfe052d": "query Home {
loadPage(path: "/") {
... on Page {
title
Expand All @@ -223,6 +308,44 @@ describe('mode: map', () => {
`);
});

it('attaches nested fragments', async () => {
const result = await runPlugin(
[
{
location: 'a.gql',
document: parse(`
fragment RelatedPage on Page { title }
fragment Page on Page { title, related { ...RelatedPage } }
query Home {
loadPage(path: "/") {
...Page,
}
}
`),
},
],
'attach',
);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:fe7f24087d5deae03464a1e58e1c5a50cd6dbbbf4b4e68c41e5f6b9f2f947d3c": "query Home {
loadPage(path: "/") {
...Page
}
}
fragment Page on Page {
title
related {
...RelatedPage
}
}
fragment RelatedPage on Page {
title
}",
}
`);
});

it('adds fragments from different documents', async () => {
const result = await runPlugin([
{
Expand All @@ -244,7 +367,7 @@ describe('mode: map', () => {
]);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:37d40553a898c4026ba372c8f42af3df9c3451953b65695b823a8e1e7b5fd90d": "query Home {
"HomeQuery:dc086da112964a8f85ae0520dab3fa68a84e067ee6aa8b0e06305f4cb5e9898a": "query Home {
loadPage(path: "/") {
... on Page {
title
Expand Down Expand Up @@ -282,7 +405,7 @@ describe('mode: map', () => {
]);
expect(JSON.parse(result)).toMatchInlineSnapshot(`
{
"HomeQuery:37d40553a898c4026ba372c8f42af3df9c3451953b65695b823a8e1e7b5fd90d": "query Home {
"HomeQuery:7f81601e1df0c179be7354f3f834201b9615d0ea191d4ba5f83bb45b0bfe052d": "query Home {
loadPage(path: "/") {
... on Page {
title
Expand Down
27 changes: 22 additions & 5 deletions packages/npm/@amazeelabs/codegen-operation-ids/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'graphql';

import { inlineFragments } from './inline';
import { scanFragments } from './scan';

class OperationIdVisitor extends ClientSideBaseVisitor {
_extractFragments() {
Expand All @@ -30,16 +31,17 @@ class OperationIdVisitor extends ClientSideBaseVisitor {

return `export const ${operationResultType} = "${queryId(
node,
print(node),
)}" as OperationId<${operationResultType},${operationVariablesTypes}${
hasRequiredVariables ? '' : ' | undefined'
}>;`;
}
}

function queryId(node: OperationDefinitionNode) {
function queryId(node: OperationDefinitionNode, content: string) {
return `${node.name?.value ?? 'anonymous'}${pascalCase(
node.operation,
)}:${crypto.createHash('sha256').update(print(node)).digest('hex')}`;
)}:${crypto.createHash('sha256').update(content).digest('hex')}`;
}

export const plugin: PluginFunction<any, string> = async (
Expand Down Expand Up @@ -69,9 +71,24 @@ export const plugin: PluginFunction<any, string> = async (
const idMap = new Map<string, string>();
visit(allAst, {
OperationDefinition(node) {
const query = [print(inlineFragments(node, fragmentMap))];
const id = queryId(node);
operationMap.set(id, query.join('\n'));
const query = [
print(
config.fragments === 'inline'
? inlineFragments(node, fragmentMap)
: node,
),
];
if (config.fragments === 'attach') {
scanFragments(node, fragmentMap).forEach((name) => {
const fragment = fragmentMap.get(name);
if (fragment) {
query.push(print(fragment));
}
});
}
const queryString = query.join('\n');
const id = queryId(node, queryString);
operationMap.set(id, queryString);
if (node.name) {
idMap.set(node.name.value, id);
}
Expand Down
97 changes: 97 additions & 0 deletions packages/npm/@amazeelabs/codegen-operation-ids/src/scan.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
DefinitionNode,
FragmentDefinitionNode,
Kind,
OperationDefinitionNode,
parse,
} from 'graphql';
import { describe, expect, it } from 'vitest';

import { scanFragments } from './scan';

function isFragmentDefinitionNode(
def: DefinitionNode,
): def is FragmentDefinitionNode {
return def.kind === Kind.FRAGMENT_DEFINITION;
}

function isOperationDefinitionNode(
def: DefinitionNode,
): def is OperationDefinitionNode {
return def.kind === Kind.OPERATION_DEFINITION;
}

describe('scanFragments', () => {
it('detects a fragment on a query', () => {
const doc = parse(`
query {
...A
}
fragment A on Query {
myprop
}
`);
const [query] = doc.definitions.filter(isOperationDefinitionNode);
const [A] = doc.definitions.filter(isFragmentDefinitionNode);
const fragments = scanFragments(query, new Map(Object.entries({ A })));
expect(fragments).toEqual(['A']);
});

it('detects a fragment on a field', () => {
const doc = parse(`
query {
a {
...A
}
}
fragment A on A {
myprop
}
`);
const [query] = doc.definitions.filter(isOperationDefinitionNode);
const [A] = doc.definitions.filter(isFragmentDefinitionNode);
const fragments = scanFragments(query, new Map(Object.entries({ A })));
expect(fragments).toEqual(['A']);
});

it('detects nested fragments', () => {
const doc = parse(`query {
a {
propA
...A
}
}
fragment A on A {
propA
propB {
...B
}
}
fragment B on B {
propC
}`);
const [query] = doc.definitions.filter(isOperationDefinitionNode);
const [A, B] = doc.definitions.filter(isFragmentDefinitionNode);
const fragments = scanFragments(query, new Map(Object.entries({ A, B })));
expect(fragments).toEqual(['A', 'B']);
});
it('detects mixed fragments', () => {
const doc = parse(`query {
a {
propA
... on A {
propB {
...B
}
}
}
}
fragment B on B {
propC
}`);
const [query] = doc.definitions.filter(isOperationDefinitionNode);
const [B] = doc.definitions.filter(isFragmentDefinitionNode);
const fragments = scanFragments(query, new Map(Object.entries({ B })));
expect(fragments).toEqual(['B']);
});
});
Loading