Skip to content

Commit

Permalink
(graphcache) - Reduce overall bundle-size (#611)
Browse files Browse the repository at this point in the history
* Replace invalidate operation with shim based on query

Instead of fully traversing a query to invalidate its data,
we now use the query operation to recursively invalidate all
entities associated with the query instead. This does a little
more than before, but shouldn't break any apps.

* Share Context type and creation in operations

* Share context updater for info/context field in resolvers

* Merge readSelection and readResolverResult

* Combine writeRootField and writeField

* Simplify shouldInclude implementation

* Refactor variables helpers

* Delete dead code in node helpers

* Refactor minor quirks in schemaPredicates helpers

* Simplify Store constructor and remove Store#gc

* Simplify cache execution in cacheExchange

* Remove GC suite from benchmark

* Add ANALYZE env option to Rollup config

* Remove redundant code from data.ts

* Remove non-cacheExchange exports

* Remove dead inline function in cacheExchange

* Readd exports

* remove writeFragment, initDataState, clearDataState, getCurrentDependencies and clearLayer from the root exports. Make cacheExchange a bit more readable

* remove the read export

* golf a few bytes by making the value undefined in writeLink and writeRecord

* moving writeRoot to writeSelection

* Fix writeSelection with merged withRoot code

* Fix key condition in writeSelection

* Simplify refactored writeSelection

* Remove store.getRootKey

* Remove custom iteration from writeOptimistic

* Fix lint errors

* Add changeset

Co-authored-by: Jovi De Croock <[email protected]>
  • Loading branch information
kitten and JoviDeCroock authored Mar 13, 2020
1 parent 64d4664 commit 42657ba
Show file tree
Hide file tree
Showing 21 changed files with 404 additions and 730 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-humans-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/exchange-graphcache': patch
---

Refactor parts of Graphcache for a minor performance boost and bundlesize reductions.
54 changes: 0 additions & 54 deletions exchanges/graphcache/benchmark/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,60 +87,6 @@ suite('10,000 entries write', () => {
});
});

suite('100 entries GC', () => {
const urqlStore = new Store();
const apolloCache = new InMemoryCache({ resultCaching: false });

benchmark('apollo', () => {
apolloCache.writeQuery({ query: TodosQuery, data: { todos: hundredEntries } })
apolloCache.writeQuery({ query: TodosQuery, data: { todos: [] } })
apolloCache.gc();
});

benchmark('urql', () => {
write(urqlStore, { query: TodosQuery }, { todos: hundredEntries });
write(urqlStore, { query: TodosQuery }, { todos: [] });
urqlStore.gcScheduled = false;
urqlStore.gc();
});
});

suite('1,000 entries GC', () => {
const urqlStore = new Store();
const apolloCache = new InMemoryCache({ resultCaching: false });

benchmark('apollo', () => {
apolloCache.writeQuery({ query: TodosQuery, data: { todos: thousandEntries } })
apolloCache.writeQuery({ query: TodosQuery, data: { todos: [] } })
apolloCache.gc();
});

benchmark('urql', () => {
write(urqlStore, { query: TodosQuery }, { todos: thousandEntries });
write(urqlStore, { query: TodosQuery }, { todos: [] });
urqlStore.gcScheduled = false;
urqlStore.gc();
});
});

suite('10,000 entries GC', () => {
const urqlStore = new Store();
const apolloCache = new InMemoryCache({ resultCaching: false });

benchmark('apollo', () => {
apolloCache.writeQuery({ query: TodosQuery, data: { todos: tenThousandEntries } })
apolloCache.writeQuery({ query: TodosQuery, data: { todos: [] } })
apolloCache.gc();
});

benchmark('urql', () => {
write(urqlStore, { query: TodosQuery }, { todos: tenThousandEntries });
write(urqlStore, { query: TodosQuery }, { todos: [] });
urqlStore.gcScheduled = false;
urqlStore.gc();
});
});

suite('100 entries read', () => {
const urqlStore = new Store();
const apolloCache = new InMemoryCache({ resultCaching: false });;
Expand Down
46 changes: 0 additions & 46 deletions exchanges/graphcache/src/ast/node.test.ts

This file was deleted.

22 changes: 3 additions & 19 deletions exchanges/graphcache/src/ast/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ import {
InlineFragmentNode,
FieldNode,
FragmentDefinitionNode,
GraphQLOutputType,
Kind,
isWrappingType,
GraphQLWrappingType,
} from 'graphql';

export type SelectionSet = ReadonlyArray<SelectionNode>;
export type GraphQLFlatType = Exclude<GraphQLOutputType, GraphQLWrappingType>;

/** Returns the name of a given node */
export const getName = (node: { name: NameNode }): string => node.name.value;
Expand All @@ -23,34 +19,22 @@ export const getFragmentTypeName = (node: FragmentDefinitionNode): string =>

/** Returns either the field's name or the field's alias */
export const getFieldAlias = (node: FieldNode): string =>
node.alias !== undefined ? node.alias.value : getName(node);
node.alias ? node.alias.value : getName(node);

/** Returns the SelectionSet for a given inline or defined fragment node */
export const getSelectionSet = (node: {
selectionSet?: SelectionSetNode;
}): SelectionSet =>
node.selectionSet !== undefined ? node.selectionSet.selections : [];
}): SelectionSet => (node.selectionSet ? node.selectionSet.selections : []);

export const getTypeCondition = ({
typeCondition,
}: {
typeCondition?: NamedTypeNode;
}): string | null =>
typeCondition !== undefined ? getName(typeCondition) : null;
}): string | null => (typeCondition ? getName(typeCondition) : null);

export const isFieldNode = (node: SelectionNode): node is FieldNode =>
node.kind === Kind.FIELD;

export const isInlineFragment = (
node: SelectionNode
): node is InlineFragmentNode => node.kind === Kind.INLINE_FRAGMENT;

export const unwrapType = (
type: null | undefined | GraphQLOutputType
): GraphQLFlatType | null => {
if (isWrappingType(type)) {
return unwrapType(type.ofType);
}

return type || null;
};
9 changes: 3 additions & 6 deletions exchanges/graphcache/src/ast/schemaPredicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export const isFieldNullable = (
fieldName: string
): boolean => {
const field = getField(schema, typename, fieldName);
if (field === undefined) return false;
return isNullableType(field.type);
return !!field && isNullableType(field.type);
};

export const isListNullable = (
Expand All @@ -27,7 +26,7 @@ export const isListNullable = (
fieldName: string
): boolean => {
const field = getField(schema, typename, fieldName);
if (field === undefined) return false;
if (!field) return false;
const ofType = isNonNullType(field.type) ? field.type.ofType : field.type;
return isListType(ofType) && isNullableType(ofType.ofType);
};
Expand Down Expand Up @@ -69,7 +68,7 @@ const getField = (
expectObjectType(object, typename);

const field = object.getFields()[fieldName];
if (field === undefined) {
if (!field) {
warn(
'Invalid field: The field `' +
fieldName +
Expand All @@ -80,8 +79,6 @@ const getField = (
'Traversal will continue, however this may lead to undefined behavior!',
4
);

return undefined;
}

return field;
Expand Down
29 changes: 12 additions & 17 deletions exchanges/graphcache/src/ast/traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,24 @@ export const shouldInclude = (
vars: Variables
): boolean => {
const { directives } = node;
if (directives === undefined) {
return true;
}
if (!directives) return true;

// Finds any @include or @skip directive that forces the node to be skipped
for (let i = 0, l = directives.length; i < l; i++) {
const directive = directives[i];
const name = getName(directive);

// Ignore other directives
const isInclude = name === 'include';
if (!isInclude && name !== 'skip') continue;

// Get the first argument and expect it to be named "if"
const arg = directive.arguments ? directive.arguments[0] : null;
if (!arg || getName(arg) !== 'if') continue;

const value = valueFromASTUntyped(arg.value, vars);
if (typeof value !== 'boolean' && value !== null) continue;

// Return whether this directive forces us to skip
// `@include(if: false)` or `@skip(if: true)`
return isInclude ? !!value : !value;
if (
(name === 'include' || name === 'skip') &&
directive.arguments &&
directive.arguments[0] &&
getName(directive.arguments[0]) === 'if'
) {
// Return whether this directive forces us to skip
// `@include(if: false)` or `@skip(if: true)`
const value = valueFromASTUntyped(directive.arguments[0].value, vars);
return name === 'include' ? !!value : !value;
}
}

return true;
Expand Down
45 changes: 19 additions & 26 deletions exchanges/graphcache/src/ast/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,16 @@ export const getFieldArguments = (
node: FieldNode,
vars: Variables
): null | Variables => {
if (node.arguments === undefined || node.arguments.length === 0) {
return null;
}

const args = makeDict();
let argsSize = 0;

for (let i = 0, l = node.arguments.length; i < l; i++) {
const arg = node.arguments[i];
const value = valueFromASTUntyped(arg.value, vars);
if (value !== undefined && value !== null) {
args[getName(arg)] = value;
argsSize++;
if (node.arguments && node.arguments.length) {
for (let i = 0, l = node.arguments.length; i < l; i++) {
const arg = node.arguments[i];
const value = valueFromASTUntyped(arg.value, vars);
if (value !== undefined && value !== null) {
args[getName(arg)] = value;
argsSize++;
}
}
}

Expand All @@ -38,24 +35,20 @@ export const normalizeVariables = (
node: OperationDefinitionNode,
input: void | object
): Variables => {
if (node.variableDefinitions === undefined) {
return {};
}

const args: Variables = (input as Variables) || {};

return node.variableDefinitions.reduce((vars, def) => {
const name = getName(def.variable);
let value = args[name];
if (value === undefined) {
if (def.defaultValue !== undefined) {
const vars = makeDict();
if (node.variableDefinitions) {
for (let i = 0, l = node.variableDefinitions.length; i < l; i++) {
const def = node.variableDefinitions[i];
const name = getName(def.variable);
let value = args[name];
if (value === undefined && def.defaultValue) {
value = valueFromASTUntyped(def.defaultValue, args);
} else {
return vars;
}

vars[name] = value;
}
}

vars[name] = value;
return vars;
}, makeDict());
return vars;
};
Loading

0 comments on commit 42657ba

Please sign in to comment.