Skip to content
This repository has been archived by the owner on Jul 6, 2020. It is now read-only.

Reduce size impact of populateExchange #122

Merged
merged 11 commits into from
Dec 8, 2019
117 changes: 57 additions & 60 deletions src/populateExchange.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { pipe, tap, map } from 'wonka';
import { Exchange, Operation } from 'urql';
import {
DocumentNode,
buildClientSchema,
Expand All @@ -19,6 +17,11 @@ import {
isAbstractType,
visit,
} from 'graphql';

import { pipe, tap, map } from 'wonka';
import { Exchange, Operation } from 'urql';

import { getName, getSelectionSet } from './ast';
import { invariant, warn } from './helpers/help';

interface ExchangeArgs {
Expand Down Expand Up @@ -71,7 +74,6 @@ export const populateExchange = ({
}

activeOperations.add(key);

if (parsedOperations.has(key)) {
return;
}
Expand All @@ -86,13 +88,10 @@ export const populateExchange = ({
query,
});

userFragments = newFragments.reduce(
(state, fragment) => ({
...state,
[fragment.name.value]: fragment,
}),
userFragments
);
userFragments = newFragments.reduce((state, fragment) => {
kitten marked this conversation as resolved.
Show resolved Hide resolved
state[getName(fragment)] = fragment;
return state;
}, userFragments);

typeFragments = newSelections.reduce((state, { selections, type }) => {
kitten marked this conversation as resolved.
Show resolved Hide resolved
const current = state[type] || [];
Expand All @@ -115,19 +114,17 @@ export const populateExchange = ({
},
type,
};
return {
...state,
[type]: [...current, entry],
};

current.push(entry);
state[type] = current;
return state;
}, typeFragments);
};

const handleIncomingTeardown = ({ key, operationName }: Operation) => {
if (operationName !== 'teardown') {
return;
if (operationName === 'teardown') {
activeOperations.delete(key);
}

activeOperations.delete(key);
};

return ops$ => {
Expand Down Expand Up @@ -167,23 +164,21 @@ export const extractSelectionsFromQuery = ({
schema,
query,
}: MakeFragmentsFromQueryArg) => {
let selections: { selections: SelectionSetNode; type: string }[] = [];
let fragments: FragmentDefinitionNode[] = [];
const selections: { selections: SelectionSetNode; type: string }[] = [];
const fragments: FragmentDefinitionNode[] = [];
const typeInfo = new TypeInfo(schema);

visit(
query,
visitWithTypeInfo(typeInfo, {
Field: node => {
if (!node.selectionSet) {
return undefined;
if (node.selectionSet) {
const type = getTypeName(typeInfo);
selections.push({ selections: node.selectionSet, type });
kitten marked this conversation as resolved.
Show resolved Hide resolved
}

const type = getTypeName(typeInfo);
selections = [...selections, { selections: node.selectionSet, type }];
},
FragmentDefinition: node => {
fragments = [...fragments, node];
fragments.push(node);
},
})
);
Expand All @@ -206,24 +201,32 @@ export const addFragmentsToQuery = ({
userFragments,
}: AddFragmentsToQuery) => {
const typeInfo = new TypeInfo(schema);
const requiredUserFragments = new Set<FragmentDefinitionNode>();
let additionalFragments: Record<string, FragmentDefinitionNode> = {};

const requiredUserFragments: Record<
string,
FragmentDefinitionNode
> = Object.create(null);

const additionalFragments: Record<
string,
FragmentDefinitionNode
> = Object.create(null);
kitten marked this conversation as resolved.
Show resolved Hide resolved

return visit(
query,
visitWithTypeInfo(typeInfo, {
Field: {
enter: node => {
if (
!node.directives ||
!node.directives.find(d => d.name.value === 'populate')
) {
if (!node.directives) {
return;
}

const directives = node.directives.filter(
d => d.name.value !== 'populate'
d => getName(d) !== 'populate'
);
if (directives.length === node.directives.length) {
return;
}

const types = getTypes(schema, typeInfo);
const newSelections = types.reduce((p, t) => {
Expand All @@ -235,30 +238,30 @@ export const addFragmentsToQuery = ({
return [
...p,
...typeFragments[t.name].map(({ fragment }) => {
const fragmentName = getName(fragment);
const usedFragments = getUsedFragments(fragment);

// Add used fragment for insertion at Document node
getUsedFragments(fragment).forEach(f =>
requiredUserFragments.add(userFragments[f])
);
for (let i = 0, l = usedFragments.length; i < l; i++) {
kitten marked this conversation as resolved.
Show resolved Hide resolved
const name = usedFragments[i];
requiredUserFragments[name] = userFragments[name];
}

// Add fragment for insertion at Document node
additionalFragments = {
...additionalFragments,
[fragment.name.value]: fragment,
};
additionalFragments[fragmentName] = fragment;

return {
kind: 'FragmentSpread',
name: {
kind: 'Name',
value: fragment.name.value,
value: fragmentName,
},
} as const;
}),
];
}, [] as FragmentSpreadNode[]);

const existingSelections =
(node.selectionSet && node.selectionSet.selections) || [];
const existingSelections = getSelectionSet(node);

const selections =
existingSelections.length + newSelections.length !== 0
Expand All @@ -285,16 +288,12 @@ export const addFragmentsToQuery = ({
},
Document: {
leave: node => {
return {
...node,
definitions: [
...node.definitions,
...Object.keys(additionalFragments).map(
k => additionalFragments[k]
), // Object.values
...Array.from(requiredUserFragments),
],
};
const definitions = [...node.definitions];
for (const key in additionalFragments)
definitions.push(additionalFragments[key]);
for (const key in requiredUserFragments)
definitions.push(requiredUserFragments[key]);
return { ...node, definitions };
},
},
})
Expand All @@ -317,11 +316,9 @@ const getTypes = (schema: GraphQLSchema, typeInfo: TypeInfo) => {
return [];
}

if (isInterfaceType(type) || isUnionType(type)) {
return schema.getPossibleTypes(type);
}

return [type];
return isInterfaceType(type) || isUnionType(type)
? schema.getPossibleTypes(type)
: [type];
};

/** Get name of non-abstract type for adding to 'typeFragments'. */
Expand All @@ -343,11 +340,11 @@ const getTypeName = (t: TypeInfo) => {

/** Get fragment names referenced by node. */
const getUsedFragments = (node: ASTNode) => {
let names: string[] = [];
const names: string[] = [];

visit(node, {
FragmentSpread: f => {
names = [...names, f.name.value];
names.push(getName(f));
},
});

Expand Down