Mirror: The spec-compliant minimum of client-side GraphQL.

feat: Optimize performance to match a baseline graphql.js target (#5)

* Optimize parser performance to meet graphql.js perf target

* Optimize printer performance

* Add changeset

* Apply lints

* Fix unused branch of GraphQLError

Changed files
+137 -116
.changeset
src
+5
.changeset/stupid-clouds-switch.md
···
+
---
+
'@0no-co/graphql.web': patch
+
---
+
+
Optimize performance of `print` and `parse` with minor adjustments.
+1 -1
src/error.ts
···
}
}
-
this.extensions = extensions || {};
+
this.extensions = _extensions || {};
}
toJSON(): any {
+15 -6
src/parser.ts
···
return out;
}
-
const ignoredRe = /(?:[\s,]*|#[^\n\r]*)*/y;
+
// Note: This is equivalent to: /(?:[\s,]*|#[^\n\r]*)*/y
function ignored() {
-
ignoredRe.lastIndex = idx;
-
ignoredRe.test(input);
-
idx = ignoredRe.lastIndex;
+
for (
+
let char = input.charCodeAt(idx++) | 0;
+
char === 9 /*'\t'*/ ||
+
char === 10 /*'\n'*/ ||
+
char === 13 /*'\r'*/ ||
+
char === 32 /*' '*/ ||
+
char === 35 /*'#'*/ ||
+
char === 44 /*','*/ ||
+
char === 65279 /*'\ufeff'*/;
+
char = input.charCodeAt(idx++) | 0
+
) {
+
if (char === 35 /*'#'*/) while ((char = input.charCodeAt(idx++)) !== 10 && char !== 13);
+
}
+
idx--;
}
const nameRe = /[_\w][_\d\w]*/y;
···
}
function field(): ast.FieldNode | undefined {
-
ignored();
let _name = name();
if (_name) {
ignored();
···
const fragmentSpreadRe = /\.\.\./y;
function fragmentSpread(): ast.FragmentSpreadNode | ast.InlineFragmentNode | undefined {
-
ignored();
if (advance(fragmentSpreadRe)) {
ignored();
const _idx = idx;
+116 -109
src/printer.ts
···
const MAX_LINE_LENGTH = 80;
-
export function print(node: ASTNode): string {
-
let out: string;
-
switch (node.kind) {
-
case 'OperationDefinition':
-
if (
-
node.operation === 'query' &&
-
!node.name &&
-
!hasItems(node.variableDefinitions) &&
-
!hasItems(node.directives)
-
) {
-
return print(node.selectionSet);
-
}
-
out = node.operation;
-
if (node.name) out += ' ' + node.name.value;
-
if (hasItems(node.variableDefinitions)) {
-
if (!node.name) out += ' ';
-
out += '(' + node.variableDefinitions.map(print).join(', ') + ')';
-
}
-
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
-
return out + ' ' + print(node.selectionSet);
-
-
case 'VariableDefinition':
-
out = print(node.variable) + ': ' + print(node.type);
-
if (node.defaultValue) out += ' = ' + print(node.defaultValue);
-
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
-
return out;
-
-
case 'Field':
-
out = (node.alias ? print(node.alias) + ': ' : '') + node.name.value;
-
if (hasItems(node.arguments)) {
-
const args = node.arguments.map(print);
-
const argsLine = out + '(' + args.join(', ') + ')';
-
out =
-
argsLine.length > MAX_LINE_LENGTH
-
? out + '(\n ' + args.join('\n').replace(/\n/g, '\n ') + '\n)'
-
: argsLine;
-
}
-
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
-
return node.selectionSet ? out + ' ' + print(node.selectionSet) : out;
-
-
case 'StringValue':
-
return node.block ? printBlockString(node.value) : printString(node.value);
-
-
case 'BooleanValue':
-
return '' + node.value;
-
-
case 'NullValue':
-
return 'null';
-
-
case 'IntValue':
-
case 'FloatValue':
-
case 'EnumValue':
-
case 'Name':
-
return node.value;
-
-
case 'ListValue':
-
return '[' + node.values.map(print).join(', ') + ']';
-
-
case 'ObjectValue':
-
return '{' + node.fields.map(print).join(', ') + '}';
-
-
case 'ObjectField':
-
return node.name.value + ': ' + print(node.value);
-
-
case 'Variable':
-
return '$' + node.name.value;
-
-
case 'Document':
-
return hasItems(node.definitions) ? node.definitions.map(print).join('\n\n') : '';
-
-
case 'SelectionSet':
-
return '{\n ' + node.selections.map(print).join('\n').replace(/\n/g, '\n ') + '\n}';
-
-
case 'Argument':
-
return node.name.value + ': ' + print(node.value);
-
-
case 'FragmentSpread':
-
out = '...' + node.name.value;
-
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
-
return out;
-
-
case 'InlineFragment':
-
out = '...';
-
if (node.typeCondition) out += ' on ' + node.typeCondition.name.value;
-
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
-
return out + ' ' + print(node.selectionSet);
-
-
case 'FragmentDefinition':
-
out = 'fragment ' + node.name.value;
-
out += ' on ' + node.typeCondition.name.value;
-
if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' ');
-
return out + ' ' + print(node.selectionSet);
-
-
case 'Directive':
-
out = '@' + node.name.value;
-
if (hasItems(node.arguments)) out += '(' + node.arguments.map(print).join(', ') + ')';
-
return out;
-
-
case 'NamedType':
-
return node.name.value;
-
-
case 'ListType':
-
return '[' + print(node.type) + ']';
-
-
case 'NonNullType':
-
return print(node.type) + '!';
+
const nodes: {
+
[NodeT in ASTNode as NodeT['kind']]?: (node: NodeT) => string;
+
} = {
+
OperationDefinition(node) {
+
if (
+
node.operation === 'query' &&
+
!node.name &&
+
!hasItems(node.variableDefinitions) &&
+
!hasItems(node.directives)
+
) {
+
return nodes.SelectionSet!(node.selectionSet);
+
}
+
let out: string = node.operation;
+
if (node.name) out += ' ' + node.name.value;
+
if (hasItems(node.variableDefinitions)) {
+
if (!node.name) out += ' ';
+
out += '(' + node.variableDefinitions.map(nodes.VariableDefinition!).join(', ') + ')';
+
}
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
+
return out + ' ' + nodes.SelectionSet!(node.selectionSet);
+
},
+
VariableDefinition(node) {
+
let out = nodes.Variable!(node.variable) + ': ' + print(node.type);
+
if (node.defaultValue) out += ' = ' + print(node.defaultValue);
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
+
return out;
+
},
+
Field(node) {
+
let out = (node.alias ? node.alias.value + ': ' : '') + node.name.value;
+
if (hasItems(node.arguments)) {
+
const args = node.arguments.map(nodes.Argument!);
+
const argsLine = out + '(' + args.join(', ') + ')';
+
out =
+
argsLine.length > MAX_LINE_LENGTH
+
? out + '(\n ' + args.join('\n').replace(/\n/g, '\n ') + '\n)'
+
: argsLine;
+
}
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
+
return node.selectionSet ? out + ' ' + nodes.SelectionSet!(node.selectionSet) : out;
+
},
+
StringValue(node) {
+
return node.block ? printBlockString(node.value) : printString(node.value);
+
},
+
BooleanValue(node) {
+
return '' + node.value;
+
},
+
NullValue(_node) {
+
return 'null';
+
},
+
IntValue(node) {
+
return node.value;
+
},
+
FloatValue(node) {
+
return node.value;
+
},
+
EnumValue(node) {
+
return node.value;
+
},
+
Name(node) {
+
return node.value;
+
},
+
Variable(node) {
+
return '$' + node.name.value;
+
},
+
ListValue(node) {
+
return '[' + node.values.map(print).join(', ') + ']';
+
},
+
ObjectValue(node) {
+
return '{' + node.fields.map(nodes.ObjectField!).join(', ') + '}';
+
},
+
ObjectField(node) {
+
return node.name.value + ': ' + print(node.value);
+
},
+
Document(node) {
+
return hasItems(node.definitions) ? node.definitions.map(print).join('\n\n') : '';
+
},
+
SelectionSet(node) {
+
return '{\n ' + node.selections.map(print).join('\n').replace(/\n/g, '\n ') + '\n}';
+
},
+
Argument(node) {
+
return node.name.value + ': ' + print(node.value);
+
},
+
FragmentSpread(node) {
+
let out = '...' + node.name.value;
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
+
return out;
+
},
+
InlineFragment(node) {
+
let out = '...';
+
if (node.typeCondition) out += ' on ' + node.typeCondition.name.value;
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
+
return out + ' ' + print(node.selectionSet);
+
},
+
FragmentDefinition(node) {
+
let out = 'fragment ' + node.name.value;
+
out += ' on ' + node.typeCondition.name.value;
+
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
+
return out + ' ' + print(node.selectionSet);
+
},
+
Directive(node) {
+
let out = '@' + node.name.value;
+
if (hasItems(node.arguments)) out += '(' + node.arguments.map(nodes.Argument!).join(', ') + ')';
+
return out;
+
},
+
NamedType(node) {
+
return node.name.value;
+
},
+
ListType(node) {
+
return '[' + print(node.type) + ']';
+
},
+
NonNullType(node) {
+
return print(node.type) + '!';
+
},
+
};
-
default:
-
return '';
-
}
+
export function print(node: ASTNode): string {
+
return nodes[node.kind] ? nodes[node.kind]!(node as any) : '';
}