Share your thoughts in the 2024 State of Clojure Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by

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.

11 Answers

0 votes
by

Comment made by: orestis

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

0 votes
by

Comment made by: dnolen

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.

0 votes
by

Comment made by: greybird

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.

0 votes
by

Comment made by: dnolen

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

0 votes
by

Comment made by: greybird

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.

0 votes
by

Comment made by: thheller

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.

0 votes
by

Comment made by: greybird

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.

0 votes
by

Comment made by: thheller

The Closure Compiler has a few options to remove code from a build by name or prefix/suffix (link: 1). I added that recently to shadow-cljs (link: 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.

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

0 votes
by

Comment made by: greybird

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

0 votes
by

Comment made by: thheller

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.

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-1701 (reported by thheller)
...