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

Scala.js classes for results? #276

Open
rpiaggio opened this issue Oct 29, 2021 · 9 comments
Open

Scala.js classes for results? #276

rpiaggio opened this issue Oct 29, 2021 · 9 comments

Comments

@rpiaggio
Copy link
Collaborator

In order to reduce the size of Scala.js code, we could add of the option of generating native Scala.js traits/classes to hold query results instead of Scala case classes.

Ideally, these would behave as facades into the JS object of the result, using js.Array instead of List, js.UndefOr instead of Option, etc. We wouldn't even need a Decoder, which means revising the base Query type.

Things that may be tricky:

  • Generating Reusability, Eq and Show instances (we do have all the info to generate them though).
  • Generating lenses. Again, we have everything to generate them, but we would have to clone objects, I don't think the Focus macro would work.

It remains to be seen how much of an improvement this would be, in order to evaluate it it's worth the effort.

@armanbilge
Copy link
Contributor

On further thought, I'm not sure if using native JS objects instead of case classes would actually improve anything. The benefit of doing this is that the SJS compiler won't have to emit classes for these since they are backed by native JS objects, which would reduce the size of the generated code. However, this overlooks the fact that such fields in JS native traits/objects in SJS are exempt from the name-mangling etc. that occurs during the optimization stages: they will use precisely their names as defined in the code (because it is assumed they are interopping with JS code that is using those names). So unless these field names are trivially short, I think it's very likely the size of your generated code would increase :)

@rpiaggio
Copy link
Collaborator Author

rpiaggio commented Nov 2, 2021

Thanks for that insight @armanbilge, I hadn't considered that. However, full field names appear at least once in the code already, in the decoder. So there may still be some improvement, depending on usage. If fields are referenced once in code, I'd say we are even WRT to names not being mangled. Of course, there will probably also be references also in the Eq, Reusability and Show instances if generated (which may be pruned away if not used anyway). On the other hand, there will be no Decoder code, and none of the rest of the code generated for case classes (toString, copy, etc.). So, yes, there will be some code which may not get as optimized as before, but there should be less code anyway. I don't think I have the knowledge to compute beforehand if and how much things would improve, but they will depend on the usage (how many times fields are referenced in code).

@armanbilge
Copy link
Contributor

armanbilge commented Nov 2, 2021

Yes, basically the penalty shifts from:

  1. one-time overhead per model, but efficient use via mangled names (case classes)
  2. less/minimal one-time overhead per model, but (possibly sizable?) penalty for every use (Scala.js native classes)

So you are right, if uses are actually sparse, then possibly strategy (2) will still work to your benefit.

@armanbilge
Copy link
Contributor

armanbilge commented Nov 2, 2021

Sorry, one more thought about this: the next way to save code would be to use dynamic "reflectiony" derived Eq, Show, etc. if possible. E.g. using Object.keys(). Even better, this would avoid using the non-mangled names.

@rpiaggio
Copy link
Collaborator Author

rpiaggio commented Nov 4, 2021

Just to document some tangential results:

I tried building explore without generating Eq and Show instances for the generated types.
After JS building (which includes mangling/compressing/etc):

- With `Eq` and `Show`:    9989651 B / brotli: 1190.31 KiB
- Without `Eq` and `Show`: 9975665 B / brotli: 1188.68 KiB

So, generating this increases the final artifact size in ~0.14%. I'd say it's negligible.

@armanbilge
Copy link
Contributor

Wait, how were you able to not generate the Eq and Show instances. Does that mean they are not being used in the code?

@rpiaggio
Copy link
Collaborator Author

rpiaggio commented Nov 4, 2021

Wait, how were you able to not generate the Eq and Show instances. Does that mean they are not being used in the code?

You can pick and choose what's generated by configuring in .scalafix.conf. These are options and their defaults:

GraphQLGen.schemaDirs=[]
GraphQLGen.catsEq=true
GraphQLGen.catsShow=true,
GraphQLGen.monocleLenses=true
GraphQLGen.scalaJSReactReuse=false
GraphQLGen.circeEncoder=true
GraphQLGen.circeDecoder=true
GraphQLGen.scalaJSReactReuse=false

circeEncoder only applies for input types, and circeDecoder only applies for Data types. I should update the documentation. BTW, these 2 are the only ones that are actually needed by clue, the rest are generated for convenience if the client code needs them.

In our main project (explore), we are not using Show and we were only using Eq in a couple of places, which I changed to equals for the test above.

@rpiaggio
Copy link
Collaborator Author

rpiaggio commented Nov 4, 2021

Sorry, one more thought about this: the next way to save code would be to use dynamic "reflectiony" derived Eq, Show, etc. if possible. E.g. using Object.keys(). Even better, this would avoid using the non-mangled names.

That's a good suggestion, thank you.

@armanbilge
Copy link
Contributor

In our main project (explore), we are not using Show and we were only using Eq in a couple of places, which I changed to equals for the test above.

Right, so if you are not using these instances, then the Scala.js optimizer removes them anyway. That's why there's a negligible difference in generated JS size whether you generate them or not.

@armanbilge armanbilge linked a pull request Oct 12, 2022 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants