Fork me on GitHub

@doubleagent I just recently decided that I will stop playing the externs game and will start using cljs-oops always for JS interop. That is, everytime I would do (.method jsobject) or (.-property jsobject), and the object doesn't come from some cljsjs closure compiled library

đź‘Ť 4

@tomi.hukkalainen_slac Afaik cljs-oops won't work for npm-deps, when all the JS code will be minimized


I consider it a bad solution as it won't be future proof, especially for libraries. Infer externs is better solution.


Hm, but how would the inferring know about minimization?


If you use cljs-oops to generate string access for every JS lib method and property access, this call won't be minimized, but with npm-deps the JS code will be optimized so also the call from Cljs to JS Should be renamed.


Infer externs doesn't generate externs for calls into npm-deps, I think.


Not sure. Could be infer-externs doesn't work with npm-deps either, and the only solution for libraries that work with both foreign-libs and npm-deps is real extern files.


For applications infer-externs and cljs-oops are okay.


A comprehensive "This is best when A, that when B. These are the tradeoffs. Never use C anymore" guide would be great


There's quite a bit of options floating around


Maybe I need to make one, then bear all the critique and make better versions until it's right 🙂

đź‘Ť 4

Just thinking out loud here. I see with Reagent one is expected to top-level def a cursor for use by any code. So one cursor instance can handle all comers. This has me thinking that cursor construction internally cache cursors and then check for and re-use a cursor with the same atom and path. And now I do not need to def my cursors: I just in-line deref (cursor atom path) and count on cached cursors being re-used. Would that work?


OMG. I just saw an in-line cursor in some example code. (a) Sweet! (b) Never mind!


Wait. Is that leaking cursors?


@U0PUGPSFR i usually declare my cursors as local state in my form-2 components


i don’t think there is a global cursor caching mechanism. some of the code relies on the component lifecycle management to keep a cursor alive for the right amount of time


This is encouraging: “Cursors are now cached, which should make them a bit easier to use. Previously, every instance of cursor had its own state. ” If I understand it correctly. Should be live-testing soon.


i took a look at the code. the reaction is stored as a property on the cursor object (take a look at the implementation of -deref on RCursor). it seems to me that this is more of a memoization than a real cache, but i may be misreading the code


Agreed. And that is why I said “encouraging”. 🙂 The win seems to be shared state, so I will get my own cursor but at a minimal memory cost.


you’d have to have an astounding number of mounted components for it to make any difference


We are not all working on five widget mobile apps 🙂


AFAIK :npm-deps is just automated way to generate :foreign-libs and calling into foreign libs needs externs, because they are not part of google closure build. When thinking about end-results - cljs-oops is equivalent to using externs (inferred or not). The difference is developer experience. The fundamental difference is that with cljs-oops you have to make the decision at each call site in contrast to making it once when writing externs (and then relying on it). This does not magically solve an issue of “knowing if extern should be used”. The problem with people confused about externs is IMO fundamental misunderstanding how google closure advanced mode works and how clojurescript compiler uses it - then they cannot confidently decide when/where to write externs or if the externs were already provided. cljs-oops makes this explicit for the price of deciding at each call site, but still cannot automatically solve the above question “is this code here subject to advanced mode munging?“. Inferred externs are a great promise to answer these questions automatically - for the (rather small) price of writing type hints.


As a side note. I think shadow-cljs has some automated way of externs inference. So maybe this could be automated even further.


@darwin one thing I would make more explicit in your comment: :npm-deps is more than just an automated :foreign-libs call: :npm-deps subjects the node module to advanced compilation and symbol munging whereas :foreign-libs does not. (to my great irritation this is still not clear in the documentation.) thus, with :foreign-libs you must use externs, externs inference, or string accessors. with :npm-deps, you don’t need any of those things and string accessors will actually break the code (and though externs are unnecessary the only thing they will do is inhibit some optimizations)


shadow-cljs starts with the same externs inference engine as the regular compiler, but because it is a build tool, it knows more about your libraries and your code. this is why it is able to turn inference warning on automatically instead of doing that janky file-by-file thing in the normal compiler


thheller may have fixed some bugs in it too, but i think it is mostly the same idea


@lee.justin.m thanks for the comment, I didn’t know that. but in that case I don’t understand how :npm-deps deals with node modules which are not closure-compiler compatible, it will just break?


Also, even with :npm-deps there are some reason why externs might be needed. React for example contains some code which breaks without externs because some parts of React access properties statically and some through strings, the first are optimized and second not.


For cljsjs and npm-deps we should in fact have different externs, as mentioned, the normal externs prevent some optimizations. But small part of externs are required for some libs (React).


Good news is that there is chance that React will provide those externs themselves because they use or will use Closure to build official React bundles and they need to solve the same problem.


It’s too bad closure doesn’t provide an “unexterns” that munges literal string property accesses


Hmm, that would be a bit hard, it can't be done compile time, as the strings might be dynamically generated. It would have to provide object with mapping from original name to munged name and then on runtime replace names, that would defeat the size savings from munging.


Ah I was assuming it was a string literal. Didn’t realize it was computed


Man I am lovin’ r/track and, next up, r/track. This is starting to feel very reminiscent of my own Cells/Matrix hacks.


well if you get some good example please submit a PR to that part of the /doc folder


That uses the Cells/Matrix hack I mentioned.