shadow-cljs
shadow-cljs copied to clipboard
IE11 not working at all...
When creating Office plugins, Microsoft requires support for IE11, so that users with old Office-versions can also make use of these plugins. There is no way around :-(
Unfortunately, I cannot get IE11 to work with ShadowCLJS. Even the simple basic Luminus "hello world" web app gives a whole set of errors.
I tried different combinations, e.g. with :compiler-options set to
:compiler-options {:output-feature-set :es5 :language-out :es5}
(or even :es3), but to no avail. I also tried some older versions of ShadowCLJS, but also out of luck there.
Would you have any suggestion? Based on the issues I've read, users seemed to be able to get it working in the past.

Works fine for me with ES5? Even the REPL is working? No special setup I can think of? Can you share the repo?
It might be the devtools since I don't really pay attention to supporting old browser in those. So maybe try :devtools {:enabled false}
in your build config. But as I said it is working for me with a basic "hello world".
Thanks for the {:enabled false}
hint, this indeed helped, because Websockets in IE11 produce a lot of errors.
For future readers:
- Upon further inspection, it also appeared that some errors were caused by a missing Symbol polyfill. Somehow this gets added in the production build, but not in the development builds. And some CLJS libraries explicitly refer to js/Symbol
- I also noticed that IE11 has a limit on the length of the files it can read; as my CLJS application gets quite large (about 30MB in development, reduced to 5 MB in advanced production compilation), it simply stops processing that file at some point, even without error message. I don't yet have an idea on how to solve this.
5MB even after :advanced
is quite excessive for any kind of app. IE11 also isn't exactly known for its speed so cutting that down should help regardless. Did you check a build report to see for something you can maybe cut?
I don't have any issue with websockets in IE11? I don't know of any CLJS libs that directly use js/Symbol
either, can you say which one? I know that many JS libs do (eg. react) but not CLJS?
I don't know what kind of capabilities Office Plugins have regarding lazy loading code but maybe multiple :modules
is an option?
Thanks for your ongoing suggestions!
It's an app with about 125K lines of own code, so the size generated by the CLJS-compiler is not unexpected. Unfortunately, this code is difficult to split into small modules — doing a split in two or three modules is feasible, but (to be further investigated) that may still be too much for IE11.
As for the js/Symbol stuff: the standard Luminus web app even includes it for printing:
(extend-protocol IPrintWithWriter
js/Symbol
(-pr-writer [sym writer _]
(-write writer (str "\"" (.toString sym) "\""))))
and in my own app it also seems to be present in some places, because I received various similar errors. I don't use it anywhere in my own code, so it must be related to some libraries.
Well that extend-protocol
usually exists so that printing any React element in the REPL doesn't blow up. If you don't have a REPL anyways you can safely remove it. Or maybe move it into an optional namespace you can load on demand if needed.
Even 125K lines of code should not generade 5MB of JS after :advanced
. Are you sure you are not importing some huge npm packages? I'd be curious to see that build report. :)
It's actually 89K lines of CLJS code and 68K lines of shared CLJC code. So ShadowCLJS is actually fed about 155K lines of code.
Report: see https://gist.github.com/mtruyens/829e5caec54f3144dda2d6cb0f4ad6b4
(This actually results in a JS-file of about 8 MB in size, but I actually use cljs-build for production releases, as it results in somewhat smaller files (6 instead of 8 MB), and is way faster: about 100 seconds versus 500 seconds; although I have never quite figured out why ShadowCLJS is so slow.)
I don't see any big NPM chunk in the report; I bundle them with webpack into a separate download.
This was indeed an interesting exercise, thanks for the suggestion!
Apparently, quite a chunk of all this code (about 30%) is related to the schema, I guess the 272 defrecords and associated Spec-schema are responsible. In hindsight, I would have avoided those defrecords (for size reasons) and instead would have gone for simple maps (but then you can't easily do an instance-check, and it's somewhat slower in execution) or deftypes (but then you can't easily assoc/dissoc).
Yikes, yeah thats a lot of schema/spec. defrecord
s themselves don't generate much code but schema code I guess can get huge. If you only use your schema relating things during development it might make sense to hide them behing a (when ^boolean js/goog.DEBUG ...)
so they can get eliminated in :advanced
.
Can you expand on the shadow-cljs being slower/bigger issue? I never had reports like that and would like to help figure it out? This should never happen. Maybe running shadow-cljs release app --verbose
can provide some clues? Assuming it is compiling all the same code I can't see it being slower or that much larger?
Thanks for the interest, see https://gist.github.com/mtruyens/a12112b0f9f5a97048caa6b3dff59d51 for the --verbose output! The software spends about 40 seconds or so on the actual compilation, the rest of the time (about 6 minutes) is taken by the Google Closure compiler (and for some reason takes waaaayyyyy longer when invoked with Shadow-CLJS then when invoked with lein-cljsbuild, even though the resulting build of ShadowCLJS is quite larger than the lein-cljsbuild).
The equivalent lein-cljsbuild takes about 116 seconds.
Do I understand correctly that the Google Closure compiler is mostly single-threaded? On my iMac Pro, with the "shadow-cljs release app" command, most of the core sit idle. With the lein-cljsbuild, many more cores seem to be used most of the time.
This is with the same version of cljs/closure? :advanced
itself is single threaded yeah. Compiling CLJS should be using multiple threads and the verbose output confirms that it does.
Technically shadow-cljs is doing a little more during :advanced
. My benchmarks for this however never exceeded a couple ms. Maybe something in your code base is causing some worst case behavior although I'm not sure it could ever account for that much.
Yep, same version of cljs/closure. Commands were executed right after each other (with a clean in between).
I have a few macros (stored in a CLJC file) that generate code in the CLJS files, e.g. related to additional Spec checks. Nothing fancy, however.
Would you mind running a release
build again with --verbose
and version 2.16.8
? I added a timer for the ReplaceCLJSConstants
pass which is pretty much the only difference happening in :advanced
. In the couple builds I tested it ranges from 6 to 24ms. Can't see how this ends up taking 4min but best to confirm.
You should see something like this in your build log
...
-> Closure - Optimizing ...
Optimizing CLJS Constants took 11ms
....
I'm also very curious about the code in sx/cljc/qna/schema.cljc
given that is produces 2mb of JS out of 54,1 KB source when cljs.core
produces 1.3mb out of 339,1 KB? I'm guessing thats one of your macros generating a lot of code?
Here's the output: "Optimizing CLJS Constants took 671ms"
https://gist.github.com/mtruyens/a9537b1bb0ec12b27b9bb1b7942f3d23
That sx/cljc/qna/schema.cljc currently contains 56 defrecords similar to the following:
(defrecord QAnswer [id question-id value])
(defn QAnswer? [obj] (instance? QAnswer obj))
(m/specrec QAnswer
[::id, :question-id ::sch/id, :value ::one|multi-primitivevalue])
and 36 of the following macros for Spec maps:
(m/specmap [:item-type #{:free-answer}, ::answer-type
:value ::qna-export-value])
As for that sx.cljc.qna.schema: it currently contains over 9600 (!) calls to new cljs.core.Keyword
in the Javascript output.
For example, something like
(s/def ::one|multi-value (s/or :one ::qpro/PQValue ; primitive or not :multi (sch/vec-of ::qpro/PQPrimitiveValue)))
Results in
cljs.spec.alpha.def_impl(new cljs.core.Keyword("sx.cljc.qna.schema","one|multi-value","sx.cljc.qna.schema/one|multi-value",255915148),cljs.core.list(new cljs.core.Symbol("cljs.spec.alpha","or","cljs.spec.alpha/or",-831679639,null),new cljs.core.Keyword(null,"one","one",935007904),new cljs.core.Keyword("sx.cljc.qna.protocols","PQValue","sx.cljc.qna.protocols/PQValue",-1642111009),new cljs.core.Keyword(null,"multi","multi",-190293005),cljs.core.list(new cljs.core.Symbol("sx.cljc.schema","vec-of","sx.cljc.schema/vec-of",-2054528343,null),new cljs.core.Keyword("sx.cljc.qna.protocols","PQPrimitiveValue","sx.cljc.qna.protocols/PQPrimitiveValue",-1222375836))),cljs.spec.alpha.or_spec_impl(new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"one","one",935007904),new cljs.core.Keyword(null,"multi","multi",-190293005)], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword("sx.cljc.qna.protocols","PQValue","sx.cljc.qna.protocols/PQValue",-1642111009),cljs.core.list(new cljs.core.Symbol("sx.cljc.schema","vec-of","sx.cljc.schema/vec-of",-2054528343,null),new cljs.core.Keyword("sx.cljc.qna.protocols","PQPrimitiveValue","sx.cljc.qna.protocols/PQPrimitiveValue",-1222375836))], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword("sx.cljc.qna.protocols","PQValue","sx.cljc.qna.protocols/PQValue",-1642111009),cljs.spec.alpha.every_impl.cljs$core$IFn$_invoke$arity$4(new cljs.core.Keyword("sx.cljc.qna.protocols","PQPrimitiveValue","sx.cljc.qna.protocols/PQPrimitiveValue",-1222375836),new cljs.core.Keyword("sx.cljc.qna.protocols","PQPrimitiveValue","sx.cljc.qna.protocols/PQPrimitiveValue",-1222375836),new cljs.core.PersistentArrayMap(null, 6, [new cljs.core.Keyword(null,"into","into",-150836029),cljs.core.PersistentVector.EMPTY,new cljs.core.Keyword("cljs.spec.alpha","kind-form","cljs.spec.alpha/kind-form",-1047104697),new cljs.core.Symbol("cljs.core","vector?","cljs.core/vector?",-1550392028,null),new cljs.core.Keyword("cljs.spec.alpha","cpred","cljs.spec.alpha/cpred",-693471218),(function (G__88306){
The CLJC file is about 1300 lines of code containing 56 defrecords, each time with an associated (instance?) convenience function, and a Spec-declaration containing all the optional and mandatory fields. In addition, there are 36 maps, similarly with a Spec-declaration for each of the fields.
Optimizing CLJS Constants took 671ms
This is indeed much longer than expected, but given the build size still reasonable. Also still fast enough to still not know where the remaining time goes.
it currently contains over 9600 (!) calls to new cljs.core.Keyword in the Javascript output.
This is what ReplaceCLJSConstants
is for. The regular CLJS compiler has :optimize-constants true
which does this during CLJS compilation. This has some drawbacks so shadow-cljs instead does it as part of the :advanced
compilation. The end result in both cases is that each keyword/symbol is only allocated once. There is an additional :compiler-options {:shadow-keywords true}
which optimizes keywords even further and can shrink the output if you have very many namespaced keywords. Usually only a couple percent overall so nowhere near enough to solve your size issues.
Spec generating huge amounts of code is why I generally avoid spec but I'm guessing you are using it at runtime for validations and so on? So not development only.
I'm out of guesses as to what might be causing the longer build time. I guess I'll try and add some more instrumentation.
Someone has to say it. IE11 is past EOL. https://learn.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge
The IE11 issue was solved. Just kept this open due to the discusses build time difference. Closing since there really isn't anything actionable to do here. Can't reproduce any slowdowns since I don't have a comparable project to reproduce.