ClojureScript

cljs.spec impact on :advanced builds

Details

  • Type: Defect Defect
  • Status: Open Open
  • Priority: Minor Minor
  • Resolution: Unresolved
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None

Description

Investigate the impact of cljs.spec on :advanced builds.

Currently all specs are kept in the (private) cljs.spec/registry-ref atom. This atom is not understood by the Closure Compiler and cannot be eliminated as dead code. So even if specs are not used in "production" they still bloat the generated JS size. Some specs may be used at runtime and cannot not be removed, the gen parts however are probably never required in :advanced builds and should be omitted somehow.

In a test build (with 1.9.93) this adds 11kb (102kb vs 91kb) as soon as cljs.spec is :require'd somewhere and goes up with each defined spec.

Activity

Hide
Orestis Markou added a comment -

In the current CLJS version (1.10.439), I'm observing 60kb of (optimized) javascript with a few specs – I'm just defining specs & fdefs, never using any of s/valid? or similar calls from spec in my production code. I'm getting my numbers from the shadow-cljs report:

cljs/spec/alpha.cljs 35.84 KB
cljs/spec/gen/alpha.cljs 27.33 KB

Show
Orestis Markou added a comment - In the current CLJS version (1.10.439), I'm observing 60kb of (optimized) javascript with a few specs – I'm just defining specs & fdefs, never using any of s/valid? or similar calls from spec in my production code. I'm getting my numbers from the shadow-cljs report: cljs/spec/alpha.cljs 35.84 KB cljs/spec/gen/alpha.cljs 27.33 KB
Hide
David Nolen added a comment -

I'll note that this problem seems remarkable similar to multimethods. Given that specs are a very dynamic feature I'm skeptical that much can be done about this issue.

Show
David Nolen added a comment - I'll note that this problem seems remarkable similar to multimethods. Given that specs are a very dynamic feature I'm skeptical that much can be done about this issue.
Hide
Mark Hayes added a comment -

I see what you mean about multimethods (the use of a global registry) but with specs I thought they could be entirely removed by the build if compile-asserts
(or something similar) is set to false, when s/valid is not used.

Show
Mark Hayes added a comment - I see what you mean about multimethods (the use of a global registry) but with specs I thought they could be entirely removed by the build if compile-asserts (or something similar) is set to false, when s/valid is not used.
Hide
David Nolen added a comment -

Mark, `compile-asserts` only applies to actual `assert` usage. So not related at all to specs and probably unlikely to be.

Show
David Nolen added a comment - Mark, `compile-asserts` only applies to actual `assert` usage. So not related at all to specs and probably unlikely to be.
Hide
Mark Hayes added a comment -

Perhaps a new var (compile-specs?) could be added, to prevent specs from being added to the registry.

My perspective is that I can't use specs in CLJS without some way to remove them in a production build, because of the code size addition for mobile apps which are very size sensitive. Not everyone is in this situation of course, but the use of specs in this case would be very beneficial for testing and dev time debugging.

Show
Mark Hayes added a comment - Perhaps a new var (compile-specs?) could be added, to prevent specs from being added to the registry. My perspective is that I can't use specs in CLJS without some way to remove them in a production build, because of the code size addition for mobile apps which are very size sensitive. Not everyone is in this situation of course, but the use of specs in this case would be very beneficial for testing and dev time debugging.
Hide
Thomas Heller added a comment -

Specs are easy enough to remove completely if required, the problem is if you want to keep some you must keep all as we can't tell what will be used at compile time.

So I agree that we probably cannot do much about that part. We could probably reduce the amount of generated code though. Given that some of it is constructed and thrown away immediately at runtime. For example cljs.spec.alpha/def-impl is passed 3 arguments via the macro code. One is the raw code form of the second spec argument. For a large amount of specs the form argument will be thrown away immediately and never used, as the second spec argument supersedes that. Closure will never remove the form however, so we can probably be smarter about that in the macro.

Show
Thomas Heller added a comment - Specs are easy enough to remove completely if required, the problem is if you want to keep some you must keep all as we can't tell what will be used at compile time. So I agree that we probably cannot do much about that part. We could probably reduce the amount of generated code though. Given that some of it is constructed and thrown away immediately at runtime. For example cljs.spec.alpha/def-impl is passed 3 arguments via the macro code. One is the raw code form of the second spec argument. For a large amount of specs the form argument will be thrown away immediately and never used, as the second spec argument supersedes that. Closure will never remove the form however, so we can probably be smarter about that in the macro.
Hide
Mark Hayes added a comment -

Removing the specs completely with a new var for this purpose or a compiler option would meet my needs.

I find it very difficult to remove specs from the production build by putting them in separate namespaces and rigging the dev build, and I wasn't able to completely get rid of them that way. I suspect others would also find it difficult. Also it is not at all desirable for me to have a large distance between a spec and its function, I'd much rather put them side by side.

For now I'm just sticking with assert.

Show
Mark Hayes added a comment - Removing the specs completely with a new var for this purpose or a compiler option would meet my needs. I find it very difficult to remove specs from the production build by putting them in separate namespaces and rigging the dev build, and I wasn't able to completely get rid of them that way. I suspect others would also find it difficult. Also it is not at all desirable for me to have a large distance between a spec and its function, I'd much rather put them side by side. For now I'm just sticking with assert.
Hide
Thomas Heller added a comment -

The Closure Compiler has a few options to remove code from a build by name or prefix/suffix [1]. I added that recently to shadow-cljs [2]. This allows brute force stripping of everything spec via

:strip-type-prefixes #{"cljs.spec"}

It only allows complete removal but if that is what you want it works reasonably well. Should probably open a separate ticket to port these options.

[1] https://shadow-cljs.github.io/docs/UsersGuide.html#_code_stripping
[2] https://github.com/thheller/shadow-cljs/blob/69316cfd0e041ef064696479cd33a65cfd4167d2/src/main/shadow/build/closure.clj#L165-L175

Show
Thomas Heller added a comment - The Closure Compiler has a few options to remove code from a build by name or prefix/suffix [1]. I added that recently to shadow-cljs [2]. This allows brute force stripping of everything spec via
:strip-type-prefixes #{"cljs.spec"}
It only allows complete removal but if that is what you want it works reasonably well. Should probably open a separate ticket to port these options. [1] https://shadow-cljs.github.io/docs/UsersGuide.html#_code_stripping [2] https://github.com/thheller/shadow-cljs/blob/69316cfd0e041ef064696479cd33a65cfd4167d2/src/main/shadow/build/closure.clj#L165-L175
Hide
Mark Hayes added a comment -

Thank you Thomas. It is good to know about the strip options and I am currently using Shadow.

Show
Mark Hayes added a comment - Thank you Thomas. It is good to know about the strip options and I am currently using Shadow.
Hide
Thomas Heller added a comment -

Note that Closure doesn't like some patterns and can't currently strip cljs.spec completely. It works fine for things like cljs.pprint but it has issues with the code generated by defonce so that would need to be adjusted I guess.

Show
Thomas Heller added a comment - Note that Closure doesn't like some patterns and can't currently strip cljs.spec completely. It works fine for things like cljs.pprint but it has issues with the code generated by defonce so that would need to be adjusted I guess.

People

Vote (17)
Watch (11)

Dates

  • Created:
    Updated: