Skip to content

Commit fe2e2e2

Browse files
committed
[gql_code_builder] optimize slow code generation.
1 parent 02179e1 commit fe2e2e2

File tree

6 files changed

+259
-31
lines changed

6 files changed

+259
-31
lines changed

codegen/gql_code_builder/lib/data.dart

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "package:built_collection/built_collection.dart";
22
import "package:code_builder/code_builder.dart";
33
import "package:gql/ast.dart";
4+
import "package:gql_code_builder/src/common.dart";
45
import "package:gql_code_builder/src/config/when_extension_config.dart";
56

67
import "./source.dart";
@@ -19,6 +20,9 @@ Library buildDataLibrary(
1920
generateMaybeWhenExtensionMethod: false,
2021
),
2122
]) {
23+
final fragmentMap = _fragmentMap(docSource);
24+
final dataClassAliasMap = _dataClassAliasMap(docSource, fragmentMap);
25+
2226
final operationDataClasses = docSource.document.definitions
2327
.whereType<OperationDefinitionNode>()
2428
.expand(
@@ -28,6 +32,8 @@ Library buildDataLibrary(
2832
schemaSource,
2933
typeOverrides,
3034
whenExtensionConfig,
35+
fragmentMap,
36+
dataClassAliasMap,
3137
),
3238
)
3339
.toList();
@@ -41,6 +47,8 @@ Library buildDataLibrary(
4147
schemaSource,
4248
typeOverrides,
4349
whenExtensionConfig,
50+
fragmentMap,
51+
dataClassAliasMap,
4452
),
4553
)
4654
.toList();
@@ -54,3 +62,127 @@ Library buildDataLibrary(
5462
]),
5563
);
5664
}
65+
66+
67+
Map<String, SourceSelections> _fragmentMap(SourceNode source) => {
68+
for (var def
69+
in source.document.definitions.whereType<FragmentDefinitionNode>())
70+
def.name.value: SourceSelections(
71+
url: source.url,
72+
selections: def.selectionSet.selections,
73+
),
74+
for (var import in source.imports) ..._fragmentMap(import)
75+
};
76+
77+
Map<String, Reference> _dataClassAliasMap(SourceNode source, Map<String, SourceSelections> fragmentMap, [Map<String, Reference>? aliasMap, Set<String>? visitedSource]) {
78+
aliasMap ??= {};
79+
visitedSource ??= {};
80+
81+
source.imports.forEach((s) {
82+
if (!visitedSource!.contains(source.url)) {
83+
visitedSource.add(source.url);
84+
_dataClassAliasMap(s, fragmentMap, aliasMap);
85+
}
86+
});
87+
88+
for (final def in source.document.definitions.whereType<OperationDefinitionNode>()) {
89+
_dataClassAliasMapDFS(
90+
typeRefPrefix: builtClassName("${def.name!.value}Data"),
91+
getAliasTypeName: (fragmentName) => "${builtClassName(fragmentName)}Data",
92+
selections: def.selectionSet.selections,
93+
fragmentMap: fragmentMap,
94+
aliasMap: aliasMap,
95+
);
96+
}
97+
98+
for (final def in source.document.definitions.whereType<FragmentDefinitionNode>()) {
99+
_dataClassAliasMapDFS(
100+
typeRefPrefix: builtClassName(def.name.value),
101+
getAliasTypeName: builtClassName,
102+
selections: def.selectionSet.selections,
103+
fragmentMap: fragmentMap,
104+
aliasMap: aliasMap,
105+
);
106+
_dataClassAliasMapDFS(
107+
typeRefPrefix: builtClassName("${def.name.value}Data"),
108+
getAliasTypeName: (fragmentName) => "${builtClassName(fragmentName)}Data",
109+
selections: def.selectionSet.selections,
110+
fragmentMap: fragmentMap,
111+
aliasMap: aliasMap,
112+
);
113+
}
114+
115+
return aliasMap;
116+
}
117+
118+
void _dataClassAliasMapDFS({
119+
required String typeRefPrefix,
120+
required String Function(String fragmentName) getAliasTypeName,
121+
required List<SelectionNode> selections,
122+
required Map<String, SourceSelections> fragmentMap,
123+
required Map<String, Reference> aliasMap,
124+
}) {
125+
if (selections.isEmpty) return;
126+
127+
// flatten selections to extract untouched fragments while visiting children.
128+
final shrunkenSelections = shrinkSelections(mergeSelections(selections, fragmentMap), fragmentMap);
129+
130+
// alias single fragment and finish
131+
final selectionsWithoutTypename = shrunkenSelections.where((s) => !(s is FieldNode && s.name.value == "__typename"));
132+
if (selectionsWithoutTypename.length == 1 && selectionsWithoutTypename.first is FragmentSpreadNode) {
133+
final node = selectionsWithoutTypename.first as FragmentSpreadNode;
134+
final fragment = fragmentMap[node.name.value];
135+
final fragmentTypeName = getAliasTypeName(node.name.value);
136+
aliasMap[typeRefPrefix] = refer(fragmentTypeName, "${fragment!.url ?? ""}#data");
137+
// print("alias $typeRefPrefix => $fragmentTypeName");
138+
return;
139+
}
140+
141+
for (final node in selectionsWithoutTypename) {
142+
if (node is FragmentSpreadNode) {
143+
// exclude redefined selections from each fragment selections
144+
final fragmentSelections = fragmentMap[node.name.value]!.selections;
145+
final exclusiveFragmentSelections = mergeSelections(fragmentSelections, fragmentMap).where((s1) {
146+
if (s1 is FieldNode) {
147+
final name = (s1.alias ?? s1.name).value;
148+
return selectionsWithoutTypename.whereType<FieldNode>().every((s2) => name != (s2.alias ?? s2.name).value);
149+
} else if (s1 is InlineFragmentNode && s1.typeCondition != null) {
150+
/// TODO: Handle inline fragments without a type condition
151+
final name = s1.typeCondition!.on.name.value;
152+
return selectionsWithoutTypename.whereType<InlineFragmentNode>().every((s2) => name != s2.typeCondition?.on.name.value);
153+
}
154+
return false;
155+
}).toList();
156+
157+
_dataClassAliasMapDFS(
158+
typeRefPrefix: typeRefPrefix,
159+
getAliasTypeName: getAliasTypeName,
160+
selections: exclusiveFragmentSelections,
161+
fragmentMap: fragmentMap,
162+
aliasMap: aliasMap,
163+
);
164+
} else if (node is InlineFragmentNode) {
165+
if (node.typeCondition != null) {
166+
/// TODO: Handle inline fragments without a type condition
167+
_dataClassAliasMapDFS(
168+
typeRefPrefix: "${typeRefPrefix}__as${node.typeCondition!.on.name.value}",
169+
getAliasTypeName: getAliasTypeName,
170+
selections: [
171+
...selections.where((s) => !(s is InlineFragmentNode)),
172+
...node.selectionSet.selections,
173+
],
174+
fragmentMap: fragmentMap,
175+
aliasMap: aliasMap,
176+
);
177+
}
178+
} else if (node is FieldNode && node.selectionSet != null) {
179+
_dataClassAliasMapDFS(
180+
typeRefPrefix: "${typeRefPrefix}_${(node.alias ?? node.name).value}",
181+
getAliasTypeName: getAliasTypeName,
182+
selections: node.selectionSet!.selections,
183+
fragmentMap: fragmentMap,
184+
aliasMap: aliasMap,
185+
);
186+
}
187+
}
188+
}

codegen/gql_code_builder/lib/src/built_class.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Class builtClass({
1111
Map<String, Expression>? initializers,
1212
Map<String, SourceSelections> superclassSelections = const {},
1313
List<Method> methods = const [],
14+
Map<String, Reference>? dataClassAliasMap,
1415
}) {
1516
final className = builtClassName(name);
1617
return Class(
@@ -30,7 +31,9 @@ Class builtClass({
3031
],
3132
),
3233
),
33-
...superclassSelections.keys.map<Reference>(
34+
...superclassSelections.keys
35+
.where((superName) => dataClassAliasMap?.containsKey(builtClassName(superName)) != true)
36+
.map<Reference>(
3437
(superName) => refer(
3538
builtClassName(superName),
3639
(superclassSelections[superName]?.url ?? "") + "#data",

codegen/gql_code_builder/lib/src/common.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Method buildGetter({
138138
required TypeNode typeNode,
139139
required SourceNode schemaSource,
140140
Map<String, Reference> typeOverrides = const {},
141+
Reference? typeRefAlias,
141142
String? typeRefPrefix,
142143
bool built = true,
143144
bool isOverride = false,
@@ -151,7 +152,9 @@ Method buildGetter({
151152

152153
final typeMap = {
153154
...defaultTypeMap,
154-
if (typeRefPrefix != null)
155+
if (typeRefAlias != null)
156+
typeName: typeRefAlias
157+
else if (typeRefPrefix != null)
155158
typeName: refer("${typeRefPrefix}_${nameNode.value}")
156159
else if (typeDef != null)
157160
typeName: refer(

codegen/gql_code_builder/lib/src/inline_fragment_classes.dart

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ List<Spec> buildInlineFragmentClasses({
2222
required String type,
2323
required Map<String, Reference> typeOverrides,
2424
required Map<String, SourceSelections> fragmentMap,
25+
required Map<String, Reference> dataClassAliasMap,
2526
required Map<String, SourceSelections> superclassSelections,
2627
required List<InlineFragmentNode> inlineFragments,
2728
required bool built,
@@ -31,26 +32,30 @@ List<Spec> buildInlineFragmentClasses({
3132
baseTypeName: name,
3233
inlineFragments: inlineFragments,
3334
config: whenExtensionConfig,
35+
dataClassAliasMap: dataClassAliasMap,
3436
);
3537
return [
3638
Class(
3739
(b) => b
3840
..abstract = true
3941
..name = builtClassName(name)
4042
..implements.addAll(
41-
superclassSelections.keys.map<Reference>(
42-
(superName) => refer(
43-
builtClassName(superName),
44-
(superclassSelections[superName]?.url ?? "") + "#data",
43+
superclassSelections.keys
44+
.where((superName) => !dataClassAliasMap.containsKey(builtClassName(superName)))
45+
.map<Reference>(
46+
(superName) => refer(
47+
builtClassName(superName),
48+
(superclassSelections[superName]?.url ?? "") + "#data",
49+
),
4550
),
46-
),
4751
)
4852
..methods.addAll([
4953
...fieldGetters,
5054
if (built)
5155
..._inlineFragmentRootSerializationMethods(
5256
name: builtClassName(name),
5357
inlineFragments: inlineFragments,
58+
dataClassAliasMap: dataClassAliasMap,
5459
),
5560
]),
5661
),
@@ -65,6 +70,7 @@ List<Spec> buildInlineFragmentClasses({
6570
fragmentMap,
6671
),
6772
fragmentMap: fragmentMap,
73+
dataClassAliasMap: dataClassAliasMap,
6874
schemaSource: schemaSource,
6975
type: type,
7076
typeOverrides: typeOverrides,
@@ -77,7 +83,17 @@ List<Spec> buildInlineFragmentClasses({
7783

7884
/// TODO: Handle inline fragments without a type condition
7985
/// https://spec.graphql.org/June2018/#sec-Inline-Fragments
80-
...inlineFragments.where((frag) => frag.typeCondition != null).expand(
86+
...inlineFragments.where((frag) {
87+
if (frag.typeCondition == null) {
88+
return false;
89+
}
90+
final typeName = builtClassName("${name}__as${frag.typeCondition!.on.name.value}");
91+
if (dataClassAliasMap.containsKey(typeName)) {
92+
// print("alias $typeName => ${dataClassAliasMap[typeName]!.symbol}");
93+
return false;
94+
}
95+
return true;
96+
}).expand(
8197
(inlineFragment) => buildSelectionSetDataClasses(
8298
name: "${name}__as${inlineFragment.typeCondition!.on.name.value}",
8399
selections: mergeSelections(
@@ -89,6 +105,7 @@ List<Spec> buildInlineFragmentClasses({
89105
fragmentMap,
90106
),
91107
fragmentMap: fragmentMap,
108+
dataClassAliasMap: dataClassAliasMap,
92109
schemaSource: schemaSource,
93110
type: inlineFragment.typeCondition!.on.name.value,
94111
typeOverrides: typeOverrides,
@@ -104,6 +121,7 @@ List<Spec> buildInlineFragmentClasses({
104121
List<Method> _inlineFragmentRootSerializationMethods({
105122
required String name,
106123
required List<InlineFragmentNode> inlineFragments,
124+
required Map<String, Reference> dataClassAliasMap,
107125
}) =>
108126
[
109127
buildSerializerGetter(name).rebuild(
@@ -121,7 +139,7 @@ List<Method> _inlineFragmentRootSerializationMethods({
121139
{
122140
for (var v in inlineFragments
123141
.where((frag) => frag.typeCondition != null))
124-
"${v.typeCondition!.on.name.value}": refer(
142+
"${v.typeCondition!.on.name.value}": dataClassAliasMap["${name}__as${v.typeCondition!.on.name.value}"] ?? refer(
125143
"${name}__as${v.typeCondition!.on.name.value}",
126144
)
127145
},

0 commit comments

Comments
 (0)