shadow-cljs icon indicating copy to clipboard operation
shadow-cljs copied to clipboard

IE11 not working at all...

Open mtruyens opened this issue 3 years ago • 16 comments

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.

image

mtruyens avatar Dec 07 '21 20:12 mtruyens

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".

thheller avatar Dec 07 '21 20:12 thheller

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.

mtruyens avatar Dec 08 '21 12:12 mtruyens

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?

thheller avatar Dec 08 '21 16:12 thheller

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.

mtruyens avatar Dec 08 '21 16:12 mtruyens

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. :)

thheller avatar Dec 08 '21 16:12 thheller

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.

mtruyens avatar Dec 08 '21 18:12 mtruyens

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).

mtruyens avatar Dec 08 '21 18:12 mtruyens

Yikes, yeah thats a lot of schema/spec. defrecords 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?

thheller avatar Dec 09 '21 06:12 thheller

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.

mtruyens avatar Dec 09 '21 12:12 mtruyens

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.

thheller avatar Dec 09 '21 20:12 thheller

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.

mtruyens avatar Dec 09 '21 20:12 mtruyens

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
....

thheller avatar Dec 09 '21 21:12 thheller

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?

thheller avatar Dec 09 '21 21:12 thheller

Here's the output: "Optimizing CLJS Constants took 671ms"

https://gist.github.com/mtruyens/a9537b1bb0ec12b27b9bb1b7942f3d23

mtruyens avatar Dec 09 '21 22:12 mtruyens

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.

mtruyens avatar Dec 09 '21 22:12 mtruyens

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.

thheller avatar Dec 10 '21 06:12 thheller

Someone has to say it. IE11 is past EOL. https://learn.microsoft.com/en-us/lifecycle/faq/internet-explorer-microsoft-edge

sirmspencer avatar Oct 13 '22 22:10 sirmspencer

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.

thheller avatar Oct 14 '22 09:10 thheller