@@ -5,6 +5,8 @@ import autoBind from 'auto-bind';
5
5
import { pascalCase } from 'change-case-all' ;
6
6
import { DepGraph } from 'dependency-graph' ;
7
7
import {
8
+ DefinitionNode ,
9
+ DocumentNode ,
8
10
FragmentDefinitionNode ,
9
11
FragmentSpreadNode ,
10
12
GraphQLSchema ,
@@ -228,8 +230,14 @@ export interface ClientSideBasePluginConfig extends ParsedConfig {
228
230
pureMagicComment ?: boolean ;
229
231
optimizeDocumentNode : boolean ;
230
232
experimentalFragmentVariables ?: boolean ;
233
+ unstable_onExecutableDocumentNode ?: Unstable_OnExecutableDocumentNode ;
234
+ unstable_omitDefinitions ?: boolean ;
231
235
}
232
236
237
+ type ExecutableDocumentNodeMeta = Record < string , unknown > ;
238
+
239
+ type Unstable_OnExecutableDocumentNode = ( documentNode : DocumentNode ) => void | ExecutableDocumentNodeMeta ;
240
+
233
241
export class ClientSideBaseVisitor <
234
242
TRawConfig extends RawClientSideBasePluginConfig = RawClientSideBasePluginConfig ,
235
243
TPluginConfig extends ClientSideBasePluginConfig = ClientSideBasePluginConfig
@@ -239,9 +247,13 @@ export class ClientSideBaseVisitor<
239
247
protected _additionalImports : string [ ] = [ ] ;
240
248
protected _imports = new Set < string > ( ) ;
241
249
250
+ private _onExecutableDocumentNode ?: Unstable_OnExecutableDocumentNode ;
251
+ private _omitDefinitions ?: boolean ;
252
+ private _fragments : Map < string , LoadedFragment > ;
253
+
242
254
constructor (
243
255
protected _schema : GraphQLSchema ,
244
- protected _fragments : LoadedFragment [ ] ,
256
+ fragments : LoadedFragment [ ] ,
245
257
rawConfig : TRawConfig ,
246
258
additionalConfig : Partial < TPluginConfig > ,
247
259
documents ?: Types . DocumentFile [ ]
@@ -271,9 +283,10 @@ export class ClientSideBaseVisitor<
271
283
experimentalFragmentVariables : getConfigValue ( rawConfig . experimentalFragmentVariables , false ) ,
272
284
...additionalConfig ,
273
285
} as any ) ;
274
-
275
286
this . _documents = documents ;
276
-
287
+ this . _onExecutableDocumentNode = ( rawConfig as any ) . unstable_onExecutableDocumentNode ;
288
+ this . _omitDefinitions = ( rawConfig as any ) . unstable_omitDefinitions ;
289
+ this . _fragments = new Map ( fragments . map ( fragment => [ fragment . name , fragment ] ) ) ;
277
290
autoBind ( this ) ;
278
291
}
279
292
@@ -293,7 +306,7 @@ export class ClientSideBaseVisitor<
293
306
names . add ( node . name . value ) ;
294
307
295
308
if ( withNested ) {
296
- const foundFragment = this . _fragments . find ( f => f . name === node . name . value ) ;
309
+ const foundFragment = this . _fragments . get ( node . name . value ) ;
297
310
298
311
if ( foundFragment ) {
299
312
const childItems = this . _extractFragments ( foundFragment . node , true ) ;
@@ -312,20 +325,14 @@ export class ClientSideBaseVisitor<
312
325
return Array . from ( names ) ;
313
326
}
314
327
315
- protected _transformFragments ( document : FragmentDefinitionNode | OperationDefinitionNode ) : string [ ] {
316
- const includeNestedFragments =
317
- this . config . documentMode === DocumentMode . documentNode ||
318
- ( this . config . dedupeFragments && document . kind === 'OperationDefinition' ) ;
319
-
320
- return this . _extractFragments ( document , includeNestedFragments ) . map ( document =>
321
- this . getFragmentVariableName ( document )
322
- ) ;
328
+ protected _transformFragments ( fragmentNames : Array < string > ) : string [ ] {
329
+ return fragmentNames . map ( document => this . getFragmentVariableName ( document ) ) ;
323
330
}
324
331
325
332
protected _includeFragments ( fragments : string [ ] , nodeKind : 'FragmentDefinition' | 'OperationDefinition' ) : string {
326
333
if ( fragments && fragments . length > 0 ) {
327
334
if ( this . config . documentMode === DocumentMode . documentNode ) {
328
- return this . _fragments
335
+ return Array . from ( this . _fragments . values ( ) )
329
336
. filter ( f => fragments . includes ( this . getFragmentVariableName ( f . name ) ) )
330
337
. map ( fragment => print ( fragment . node ) )
331
338
. join ( '\n' ) ;
@@ -346,8 +353,35 @@ export class ClientSideBaseVisitor<
346
353
return documentStr ;
347
354
}
348
355
356
+ private _generateDocumentNodeMeta (
357
+ definitions : ReadonlyArray < DefinitionNode > ,
358
+ fragmentNames : Array < string >
359
+ ) : ExecutableDocumentNodeMeta | void {
360
+ // If the document does not contain any executable operation, we don't need to hash it
361
+ if ( definitions . every ( def => def . kind !== Kind . OPERATION_DEFINITION ) ) {
362
+ return undefined ;
363
+ }
364
+
365
+ const allDefinitions = [ ...definitions ] ;
366
+
367
+ for ( const fragment of fragmentNames ) {
368
+ const fragmentRecord = this . _fragments . get ( fragment ) ;
369
+ if ( fragmentRecord ) {
370
+ allDefinitions . push ( fragmentRecord . node ) ;
371
+ }
372
+ }
373
+
374
+ const documentNode : DocumentNode = { kind : Kind . DOCUMENT , definitions : allDefinitions } ;
375
+
376
+ return this . _onExecutableDocumentNode ( documentNode ) ;
377
+ }
378
+
349
379
protected _gql ( node : FragmentDefinitionNode | OperationDefinitionNode ) : string {
350
- const fragments = this . _transformFragments ( node ) ;
380
+ const includeNestedFragments =
381
+ this . config . documentMode === DocumentMode . documentNode ||
382
+ ( this . config . dedupeFragments && node . kind === 'OperationDefinition' ) ;
383
+ const fragmentNames = this . _extractFragments ( node , includeNestedFragments ) ;
384
+ const fragments = this . _transformFragments ( fragmentNames ) ;
351
385
352
386
const doc = this . _prepareDocument ( `
353
387
${ print ( node ) . split ( '\\' ) . join ( '\\\\' ) /* Re-escape escaped values in GraphQL syntax */ }
@@ -375,11 +409,39 @@ export class ClientSideBaseVisitor<
375
409
...fragments . map ( name => `...${ name } .definitions` ) ,
376
410
] . join ( ) ;
377
411
378
- return `{"kind":"${ Kind . DOCUMENT } ","definitions":[${ definitions } ]}` ;
412
+ let hashPropertyStr = '' ;
413
+
414
+ if ( this . _onExecutableDocumentNode ) {
415
+ const meta = this . _generateDocumentNodeMeta ( gqlObj . definitions , fragmentNames ) ;
416
+ if ( meta ) {
417
+ hashPropertyStr = `"__meta__": ${ JSON . stringify ( meta ) } , ` ;
418
+ if ( this . _omitDefinitions === true ) {
419
+ return `{${ hashPropertyStr } }` ;
420
+ }
421
+ }
422
+ }
423
+
424
+ return `{${ hashPropertyStr } "kind":"${ Kind . DOCUMENT } ", "definitions":[${ definitions } ]}` ;
425
+ }
426
+
427
+ let meta : ExecutableDocumentNodeMeta | void ;
428
+
429
+ if ( this . _onExecutableDocumentNode ) {
430
+ meta = this . _generateDocumentNodeMeta ( gqlObj . definitions , fragmentNames ) ;
431
+ const metaNodePartial = { [ '__meta__' ] : meta } ;
432
+
433
+ if ( this . _omitDefinitions === true ) {
434
+ return JSON . stringify ( metaNodePartial ) ;
435
+ }
436
+
437
+ if ( meta ) {
438
+ return JSON . stringify ( { ...metaNodePartial , ...gqlObj } ) ;
439
+ }
379
440
}
380
441
381
442
return JSON . stringify ( gqlObj ) ;
382
443
}
444
+
383
445
if ( this . config . documentMode === DocumentMode . string ) {
384
446
return '`' + doc + '`' ;
385
447
}
@@ -411,7 +473,7 @@ export class ClientSideBaseVisitor<
411
473
private get fragmentsGraph ( ) : DepGraph < LoadedFragment > {
412
474
const graph = new DepGraph < LoadedFragment > ( { circular : true } ) ;
413
475
414
- for ( const fragment of this . _fragments ) {
476
+ for ( const fragment of this . _fragments . values ( ) ) {
415
477
if ( graph . hasNode ( fragment . name ) ) {
416
478
const cachedAsString = print ( graph . getNodeData ( fragment . name ) . node ) ;
417
479
const asString = print ( fragment . node ) ;
@@ -438,7 +500,7 @@ export class ClientSideBaseVisitor<
438
500
}
439
501
440
502
public get fragments ( ) : string {
441
- if ( this . _fragments . length === 0 || this . config . documentMode === DocumentMode . external ) {
503
+ if ( this . _fragments . size === 0 || this . config . documentMode === DocumentMode . external ) {
442
504
return '' ;
443
505
}
444
506
0 commit comments