Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS] Dedup enums and inputs by using global types file #520

Merged
merged 4 commits into from
Aug 2, 2018

Conversation

danilobuerger
Copy link
Contributor

@danilobuerger danilobuerger commented Jul 31, 2018

This change will deduplicate enum and input definitions by placing them in a global types file (${outputPath}/globalTypes.ts). Those global types will no longer be printed into the "local" type files, instead they will be imported.

To not trigger noUnusedLocals errors, the actual used global types are determined by getGlobalTypesUsedForOperation. This is the part I see the most risk of a regression within this PR (the rest is straightforward). It could be that imports are either under- or over- added. I did test this with a live project though and it came out clean.

@ghost ghost added the 🎉 feature New addition or enhancement to existing solutions label Jul 31, 2018
Copy link
Contributor

@shadaj shadaj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass. Changes look good but we could do a bit of cleanup to make them even more awesome!

case 'Field':
case 'TypeCondition':
if (selection.selectionSet) {
selectionAcc = reduceTypesForDocument(selection.selectionSet, selectionAcc);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating selectionAcc and returning later introduces a bit of unnecessary mutability. Could you just directly return the additional types here and return selectionAcc for the default case?

type = type.ofType
}

acc = maybePush(acc, type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, could this be replaced by an immutable update [...acc, type]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, the intention of maybePush from the original reduceTypesUsed was that there are no duplicated types in acc as maybePush checks for dups. However since we filter later anyway, we could accumulate duplicates here. Or we could collect them as an object instead thus having O(1) later for the lookups.

type = getNullableType(type);
}

if (type instanceof GraphQLList) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with nested lists here? This might need a recursive call so the body of this branch would be something like return reduceGlobalTypesUsed(acc, type.ofType).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this currently fails on [[EnumCommentTestCase!]!]!. Added a test case.

acc: (GraphQLType | GraphQLOutputType)[],
type: GraphQLType
) =>{
if (type instanceof GraphQLNonNull) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as below, probably best to just recurse in case you have a non-nullable list for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this currently fails on [[EnumCommentTestCase!]!]!. Added a test case.

public getGlobalTypesUsedForOperation(doc: Operation | Fragment, context: CompilerContext) {
let docTypesUsed: GraphQLType[] = [];

if (doc.hasOwnProperty('operationName')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be replaced by an instanceof or kind check? Then it wouldn't be necessary to explicitly cast the value later.

nested: SelectionSet,
acc: GraphQLType[]
) => {
acc = nested.selections
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting and immediately returning acc, can you just directly return it here?


if (nextToSources || (fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory())) {
if (nextToSources && !fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case you want to be checking and creating the joined path, right? I think you might need to move the dir adjustment logic from below up to the top of this branch so that you are using the same directory everywhere. Plus, it's expensive to check directories for every file so it would be faster to just do that once here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand what you are trying to say. This line would create the directory for globalTypes.ts if we are placing next to sources. In that case we are not guaranteed to have the /__generated__ (or whatever it is set to) directory (as there is no source next to it), so its checked here. The later dir logic is for the directories we need for placing next to the source. That part is just copied from the flow target. If instead we are using output flat, no checks or creates would happen except the initial one (on Line 129).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was confusing sourcePath and outputPath. You're right, this makes sense for the globalTypes.ts file.

[fileName: string]: BasicGeneratedFile;
} = {};

if (nextToSources || (fs.existsSync(outputPath) && fs.statSync(outputPath).isDirectory())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic still seems pretty similar to Flow other than the change in the call to content. Would it be possible to extract out the common logic?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, you could probably use the TypeScript code for Flow as is. The Flow generator would receive the additional property globalSourcePath but it would just ignore it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the separation was with intent. Yes there is a lot of logic, but there is also apollographql/rover#410 which I didn't want to interfere with. I wanted to keep them separate until Flow catches up and dedups as well. Then those paths could be merged again (if similar enough). What do you think?

If we use the TS code as is for flow, we would also have to modify the flow package to return content as a closure. (Which I am not really interested in doing to be honest as I don't care much about Flow.)

@danilobuerger
Copy link
Contributor Author

Thanks for the thorough review @shadaj !

Initially I just copied the code for getGlobalTypesUsedForOperation from getTypesUsedForOperation and tried to make it work. But your review inspired me to do better, so I rewrote it from scratch. Now it looks far better (and fixed the nested non-null list bug).

@shadaj
Copy link
Contributor

shadaj commented Aug 2, 2018

Looks good to me, thanks for the awesome work @danilobuerger!

@shadaj shadaj merged commit 6570f72 into apollographql:master Aug 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🎉 feature New addition or enhancement to existing solutions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants