Skip to content

Commit 84689d1

Browse files
committed
Improve performance of printer
1 parent c079ae3 commit 84689d1

File tree

6 files changed

+165
-52
lines changed

6 files changed

+165
-52
lines changed

benchmark/benchmark.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ function maxBy(array, fn) {
243243

244244
// Prepare all revisions and run benchmarks matching a pattern against them.
245245
async function runBenchmarks(benchmarks, benchmarkProjects) {
246-
for (const benchmark of benchmarks) {
246+
for (const benchmark of benchmarks.filter(x => x.includes('visit-') || x.includes('printer-'))) {
247247
const results = [];
248248
for (let i = 0; i < benchmarkProjects.length; ++i) {
249249
const { revision, projectPath } = benchmarkProjects[i];

benchmark/fixtures.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ exports.bigSchemaSDL = fs.readFileSync(
88
'utf8',
99
);
1010

11+
exports.bigDocument = fs.readFileSync(
12+
path.join(__dirname, 'kitchen-sink.graphql'),
13+
'utf8',
14+
);
15+
1116
exports.bigSchemaIntrospectionResult = JSON.parse(
1217
fs.readFileSync(path.join(__dirname, 'github-schema.json'), 'utf8'),
1318
);

benchmark/kitchen-sink.graphql

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright (c) 2015-present, Facebook, Inc.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
7+
whoever123is: node(id: [123, 456]) {
8+
id
9+
... on User @onInlineFragment {
10+
field2 {
11+
id
12+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
13+
id
14+
...frag @onFragmentSpread
15+
}
16+
}
17+
}
18+
... @skip(unless: $foo) {
19+
id
20+
}
21+
... {
22+
id
23+
}
24+
}
25+
}
26+
27+
mutation likeStory @onMutation {
28+
like(story: 123) @onField {
29+
story {
30+
id @onField
31+
}
32+
}
33+
}
34+
35+
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput)
36+
@onSubscription {
37+
storyLikeSubscribe(input: $input) {
38+
story {
39+
likers {
40+
count
41+
}
42+
likeSentence {
43+
text
44+
}
45+
}
46+
}
47+
}
48+
49+
fragment frag on Friend @onFragmentDefinition {
50+
foo(
51+
size: $site
52+
bar: 12
53+
obj: {
54+
key: "value"
55+
block: """
56+
block string uses \"""
57+
"""
58+
}
59+
)
60+
}
61+
62+
query teeny {
63+
unnamed(truthy: true, falsey: false, nullish: null)
64+
query
65+
}
66+
67+
query tiny {
68+
__typename
69+
}

benchmark/printer-benchmark.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const { parse } = require('graphql/language/parser.js');
4+
const { print } = require('graphql/language/printer.js');
5+
const { bigDocument } = require('./fixtures')
6+
7+
const document = parse(bigDocument)
8+
9+
module.exports = {
10+
name: 'Print ktichen-sink query',
11+
count: 1000,
12+
measure() {
13+
print(document);
14+
},
15+
};

src/language/printer.ts

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ const printDocASTReducer: ASTReducer<string> = {
2323
// Document
2424

2525
Document: {
26-
leave: (node) => join(node.definitions, '\n\n'),
26+
leave: (node) => truthyJoin(node.definitions, '\n\n'),
2727
},
2828

2929
OperationDefinition: {
3030
leave(node) {
31-
const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')');
31+
const varDefs = wrap('(', truthyJoin(node.variableDefinitions, ', '), ')');
3232
const prefix = join(
3333
[
3434
node.operation,
35+
// TODO: optimize
3536
join([node.name, varDefs]),
36-
join(node.directives, ' '),
37+
truthyJoin(node.directives, ' '),
3738
],
3839
' ',
3940
);
@@ -50,20 +51,20 @@ const printDocASTReducer: ASTReducer<string> = {
5051
': ' +
5152
type +
5253
wrap(' = ', defaultValue) +
53-
wrap(' ', join(directives, ' ')),
54+
wrap(' ', truthyJoin(directives, ' ')),
5455
},
5556
SelectionSet: { leave: ({ selections }) => block(selections) },
5657

5758
Field: {
5859
leave({ alias, name, arguments: args, directives, selectionSet }) {
5960
const prefix = wrap('', alias, ': ') + name;
60-
let argsLine = prefix + wrap('(', join(args, ', '), ')');
61+
let argsLine = prefix + wrap('(', truthyJoin(args, ', '), ')');
6162

6263
if (argsLine.length > MAX_LINE_LENGTH) {
63-
argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)');
64+
argsLine = prefix + wrap('(\n', indent(truthyJoin(args, '\n')), '\n)');
6465
}
6566

66-
return join([argsLine, join(directives, ' '), selectionSet], ' ');
67+
return join([argsLine, truthyJoin(directives, ' '), selectionSet], ' ');
6768
},
6869
},
6970

@@ -73,7 +74,7 @@ const printDocASTReducer: ASTReducer<string> = {
7374

7475
FragmentSpread: {
7576
leave: ({ name, directives }) =>
76-
'...' + name + wrap(' ', join(directives, ' ')),
77+
'...' + name + wrap(' ', truthyJoin(directives, ' ')),
7778
},
7879

7980
InlineFragment: {
@@ -82,7 +83,7 @@ const printDocASTReducer: ASTReducer<string> = {
8283
[
8384
'...',
8485
wrap('on ', typeCondition),
85-
join(directives, ' '),
86+
truthyJoin(directives, ' '),
8687
selectionSet,
8788
],
8889
' ',
@@ -99,8 +100,8 @@ const printDocASTReducer: ASTReducer<string> = {
99100
}) =>
100101
// Note: fragment variable definitions are experimental and may be changed
101102
// or removed in the future.
102-
`fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
103-
`on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
103+
`fragment ${name}${wrap('(', truthyJoin(variableDefinitions, ', '), ')')} ` +
104+
`on ${typeCondition} ${wrap('', truthyJoin(directives, ' '), ' ')}` +
104105
selectionSet,
105106
},
106107

@@ -115,15 +116,15 @@ const printDocASTReducer: ASTReducer<string> = {
115116
BooleanValue: { leave: ({ value }) => (value ? 'true' : 'false') },
116117
NullValue: { leave: () => 'null' },
117118
EnumValue: { leave: ({ value }) => value },
118-
ListValue: { leave: ({ values }) => '[' + join(values, ', ') + ']' },
119-
ObjectValue: { leave: ({ fields }) => '{' + join(fields, ', ') + '}' },
119+
ListValue: { leave: ({ values }) => '[' + truthyJoin(values, ', ') + ']' },
120+
ObjectValue: { leave: ({ fields }) => '{' + truthyJoin(fields, ', ') + '}' },
120121
ObjectField: { leave: ({ name, value }) => name + ': ' + value },
121122

122123
// Directive
123124

124125
Directive: {
125126
leave: ({ name, arguments: args }) =>
126-
'@' + name + wrap('(', join(args, ', '), ')'),
127+
'@' + name + wrap('(', truthyJoin(args, ', '), ')'),
127128
},
128129

129130
// Type
@@ -147,7 +148,7 @@ const printDocASTReducer: ASTReducer<string> = {
147148
ScalarTypeDefinition: {
148149
leave: ({ description, name, directives }) =>
149150
wrap('', description, '\n') +
150-
join(['scalar', name, join(directives, ' ')], ' '),
151+
join(['scalar', name, truthyJoin(directives, ' ')], ' '),
151152
},
152153

153154
ObjectTypeDefinition: {
@@ -157,8 +158,8 @@ const printDocASTReducer: ASTReducer<string> = {
157158
[
158159
'type',
159160
name,
160-
wrap('implements ', join(interfaces, ' & ')),
161-
join(directives, ' '),
161+
wrap('implements ', truthyJoin(interfaces, ' & ')),
162+
truthyJoin(directives, ' '),
162163
block(fields),
163164
],
164165
' ',
@@ -170,18 +171,18 @@ const printDocASTReducer: ASTReducer<string> = {
170171
wrap('', description, '\n') +
171172
name +
172173
(hasMultilineItems(args)
173-
? wrap('(\n', indent(join(args, '\n')), '\n)')
174-
: wrap('(', join(args, ', '), ')')) +
174+
? wrap('(\n', indent(truthyJoin(args, '\n')), '\n)')
175+
: wrap('(', truthyJoin(args, ', '), ')')) +
175176
': ' +
176177
type +
177-
wrap(' ', join(directives, ' ')),
178+
wrap(' ', truthyJoin(directives, ' ')),
178179
},
179180

180181
InputValueDefinition: {
181182
leave: ({ description, name, type, defaultValue, directives }) =>
182183
wrap('', description, '\n') +
183184
join(
184-
[name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')],
185+
[name + ': ' + type, wrap('= ', defaultValue), truthyJoin(directives, ' ')],
185186
' ',
186187
),
187188
},
@@ -193,8 +194,8 @@ const printDocASTReducer: ASTReducer<string> = {
193194
[
194195
'interface',
195196
name,
196-
wrap('implements ', join(interfaces, ' & ')),
197-
join(directives, ' '),
197+
wrap('implements ', truthyJoin(interfaces, ' & ')),
198+
truthyJoin(directives, ' '),
198199
block(fields),
199200
],
200201
' ',
@@ -205,26 +206,26 @@ const printDocASTReducer: ASTReducer<string> = {
205206
leave: ({ description, name, directives, types }) =>
206207
wrap('', description, '\n') +
207208
join(
208-
['union', name, join(directives, ' '), wrap('= ', join(types, ' | '))],
209+
['union', name, truthyJoin(directives, ' '), wrap('= ', truthyJoin(types, ' | '))],
209210
' ',
210211
),
211212
},
212213

213214
EnumTypeDefinition: {
214215
leave: ({ description, name, directives, values }) =>
215216
wrap('', description, '\n') +
216-
join(['enum', name, join(directives, ' '), block(values)], ' '),
217+
join(['enum', name, truthyJoin(directives, ' '), block(values)], ' '),
217218
},
218219

219220
EnumValueDefinition: {
220221
leave: ({ description, name, directives }) =>
221-
wrap('', description, '\n') + join([name, join(directives, ' ')], ' '),
222+
wrap('', description, '\n') + join([name, truthyJoin(directives, ' ')], ' '),
222223
},
223224

224225
InputObjectTypeDefinition: {
225226
leave: ({ description, name, directives, fields }) =>
226227
wrap('', description, '\n') +
227-
join(['input', name, join(directives, ' '), block(fields)], ' '),
228+
join(['input', name, truthyJoin(directives, ' '), block(fields)], ' '),
228229
},
229230

230231
DirectiveDefinition: {
@@ -233,24 +234,24 @@ const printDocASTReducer: ASTReducer<string> = {
233234
'directive @' +
234235
name +
235236
(hasMultilineItems(args)
236-
? wrap('(\n', indent(join(args, '\n')), '\n)')
237-
: wrap('(', join(args, ', '), ')')) +
237+
? wrap('(\n', indent(truthyJoin(args, '\n')), '\n)')
238+
: wrap('(', truthyJoin(args, ', '), ')')) +
238239
(repeatable ? ' repeatable' : '') +
239240
' on ' +
240-
join(locations, ' | '),
241+
truthyJoin(locations, ' | '),
241242
},
242243

243244
SchemaExtension: {
244245
leave: ({ directives, operationTypes }) =>
245246
join(
246-
['extend schema', join(directives, ' '), block(operationTypes)],
247+
['extend schema', truthyJoin(directives, ' '), block(operationTypes)],
247248
' ',
248249
),
249250
},
250251

251252
ScalarTypeExtension: {
252253
leave: ({ name, directives }) =>
253-
join(['extend scalar', name, join(directives, ' ')], ' '),
254+
join(['extend scalar', name, truthyJoin(directives, ' ')], ' '),
254255
},
255256

256257
ObjectTypeExtension: {
@@ -259,8 +260,8 @@ const printDocASTReducer: ASTReducer<string> = {
259260
[
260261
'extend type',
261262
name,
262-
wrap('implements ', join(interfaces, ' & ')),
263-
join(directives, ' '),
263+
wrap('implements ', truthyJoin(interfaces, ' & ')),
264+
truthyJoin(directives, ' '),
264265
block(fields),
265266
],
266267
' ',
@@ -273,8 +274,8 @@ const printDocASTReducer: ASTReducer<string> = {
273274
[
274275
'extend interface',
275276
name,
276-
wrap('implements ', join(interfaces, ' & ')),
277-
join(directives, ' '),
277+
wrap('implements ', truthyJoin(interfaces, ' & ')),
278+
truthyJoin(directives, ' '),
278279
block(fields),
279280
],
280281
' ',
@@ -287,21 +288,21 @@ const printDocASTReducer: ASTReducer<string> = {
287288
[
288289
'extend union',
289290
name,
290-
join(directives, ' '),
291-
wrap('= ', join(types, ' | ')),
291+
truthyJoin(directives, ' '),
292+
wrap('= ', truthyJoin(types, ' | ')),
292293
],
293294
' ',
294295
),
295296
},
296297

297298
EnumTypeExtension: {
298299
leave: ({ name, directives, values }) =>
299-
join(['extend enum', name, join(directives, ' '), block(values)], ' '),
300+
join(['extend enum', name, truthyJoin(directives, ' '), block(values)], ' '),
300301
},
301302

302303
InputObjectTypeExtension: {
303304
leave: ({ name, directives, fields }) =>
304-
join(['extend input', name, join(directives, ' '), block(fields)], ' '),
305+
join(['extend input', name, truthyJoin(directives, ' '), block(fields)], ' '),
305306
},
306307
};
307308

@@ -313,7 +314,30 @@ function join(
313314
maybeArray: Maybe<ReadonlyArray<string | undefined>>,
314315
separator = '',
315316
): string {
316-
return maybeArray?.filter((x) => x).join(separator) ?? '';
317+
if (!maybeArray) return ''
318+
319+
const list = maybeArray.filter((x) => x);
320+
const listLength = list.length;
321+
let result = '';
322+
for (let i = 0; i < listLength; i++) {
323+
if (i === listLength - 1) return result + list[i];
324+
else result += list[i] + separator;
325+
}
326+
return result
327+
}
328+
329+
function truthyJoin(
330+
list: ReadonlyArray<string> | undefined,
331+
separator: string,
332+
): string {
333+
if (!list) return ''
334+
const listLength = list.length;
335+
let result = '';
336+
for (let i = 0; i < listLength; i++) {
337+
if (i === listLength - 1) return result + list[i];
338+
else result += list[i] + separator;
339+
}
340+
return result
317341
}
318342

319343
/**

0 commit comments

Comments
 (0)