Fork me on GitHub
#clojurescript
<
2019-11-04
>
martinklepsch13:11:14

I have an atom that can be set from the console via an ^:exported function and a when clause using that atom:

(when @twilio-log-level
  {:logLevel @twilio-log-level})
Now at compile time @twilio-log-level is nil and so that branch is dead-code-eliminated. I could probably work around this somehow and just get on with it but I’m also curious how other people implement these kinds of debug toggles in production builds?

thheller13:11:25

are you sure that is actually DCE'd? last time I checked Closure didn't understand deref/atoms at all and just left them alone?

darwin13:11:48

@martinklepsch I my experience, safest is to use an extra classpath entry which is included only in dev mode, and then call code from it dynamically from behind closure define test, e.g. goog/DEBUG

dnolen14:11:36

@martinklepsch as @thheller has said, I'm a bit skeptical

martinklepsch14:11:43

@darwin This is about runtime toggles not compile time so I think classpath/goog-define don’t help

darwin14:11:11

no, GCC understands closure defines at compile time

darwin14:11:59

ah, you mean your case is about runtime, right?

darwin14:11:07

I’m sorry, now I understand what is the problem. I didn’t read it properly. I also don’t believe the code was DCE’d.

martinklepsch14:11:20

I think you’re right and I diagnosed DCE poorly. What I tried before was this and it did behave somewhat unexpectedly:

(def ^:export twilio-log-level nil)
;; ... within a bigger form:
(when twilio-log-level
  (js/console.log "asdf")
  {:logLevel twilio-log-level})

martinklepsch14:11:34

What happened is that I get

$goog$exportSymbol$$("x.twilio_log_level", null);
but also the when call is compiled as:
$cljs$core$truth_$$(null)
probably a ^boolean hint would then DCE this code. The intention here was that the var could be set from the JS console but because the externed var isn’t used that plan doesn’t work out. I found this unexpected for a moment because I expected the compiled code to still use the externed variable.

martinklepsch14:11:24

I guess I expected the compiler to emit

$cljs$core$truth_$$(x.twilio_log_level)
or something like that

darwin14:11:28

what about exercising setting and restoring the value from your own code? for example as a side-effect of your ns, this would keep it and not DCE’d it

martinklepsch14:11:23

yeah, that’s the workaround. Or using js/x.twilio_log_level to explicitly get the externed reference

martinklepsch14:11:18

But I thought this was interesting and maybe something that could be discussed, i.e. “Should externed vars always be referenced through their externed name in emitted code?”

darwin14:11:52

Interesting. I see your point.

mfikes14:11:35

1) Exporting only sets up a pointer to the real var, which is subject to the usual minification. (Let’s say you externed a function that is heavily used in your code, you’d usually still want all of those references to be minified) 2) I think the GCC compiler is seeing that nothing changes the value of twilio-log-level between the time it is established in the code and then used farther down in the text. (Assuming your when is inside a top-level thing that is evaluated, as opposed to being inside a function definition.) (This is independent of exporting.)

martinklepsch14:11:49

@U04VDQDDY on 2: the when is inside a multimethod

mfikes15:11:31

Ahh, right. It wouldn’t matter if it is top-level or in a fn, or multimethod. Nothing is changing its value, so it gets inlined.

4
mfikes15:11:46

Maybe to get what you want @martinklepsch you would do as before (define an atom, and dereference it in your code), but instead export a special function that can change the value of that atom from JavaScript.

martinklepsch15:11:52

Yeah, that’s what I’m doing now. Still thought it was kind of interesting that the “intuitive way” (by whatever definition) didn’t work

mfikes15:11:46

Yeah, the gotcha seems to surround the notion that exported vars are really just pointers, and if you set them, you are making them point at something else. (I don’t think Google’s docs are really clear on this subject.)

darwin15:11:25

is this future-proof? GCC might get smarter over time and understand cljs atoms, and you could end up in the same situation, no?

mfikes15:11:35

Maybe they are better phrased as unminified names that are intialized with copies of the minimized name value.

mfikes15:11:48

(And not “pointers”)

mfikes15:11:28

With the atom approach:

(def my-atom (atom nil))
(defn ^:export setit [x] (reset! my-atom x))
(,,, @my-atom)

darwin15:11:03

do you really need atom? wouldn’t ^:dynamic def and set! suffice?

mfikes15:11:16

(Just a plain var being set! would work too.)

ccann17:11:34

is this legal clojurescript syntax?

(form.props.stripe.handleCardSetup client_secret)
vs e.g.
(-> (.. form -props -stripe)
       (.handleCardSetup client_secret))

lilactown17:11:02

it might work but you should prefer using the latter style

ccann17:11:51

OK, tempting to use though, given that it’s shorter

lilactown18:11:11

(.. form -props -stripe handleCardSetup client_secret) should work as well I think?

lilactown18:11:37

yep in: (.. form -props -stripe handleCardSetup client_secret) out: cljs.user.form.props.stripe.handleCardSetup().client_secret();

ccann18:11:01

Kind of a noob at clojurescript, how’d you check that?

lilactown18:11:21

note that the code it shows will change substantially after optimizations, but it gives you an idea of what the code emitted by the CLJS compiler will be before it goes into the Closure compiler to be optimized and bundled together

dabrazhe18:11:56

Hi. I am getting this error when using cljs on node.js. I guess i need to add XMLHttpRequest as a dependency, but how?

ReferenceError: XMLHttpRequest is not defined
    at goog.net.DefaultXmlHttpFactory.createInstance 

dnolen18:11:19

@dennisa you need to find a shim

dnolen18:11:29

but probably you should just not try to use XHR in Node.js

dabrazhe18:11:23

I guess you are right ) It appears to be a part of libs like cljs-http

dabrazhe18:11:17

I there are better suited http/s client libraries to use with nodejs, please let me know

dnolen18:11:20

I would just use Node HTTP stuff

dnolen18:11:55

there's probably a CLJS library that abstracts over browser/Node.js but I'm not aware of a specific one

dabrazhe18:11:25

that's fine, thanks, will have a look

borkdude19:11:34

I'm making a small lib that tests for the absence of test assertions. I'm trying to hook into the report multi-method but somehow nothing happens. I've tried sticking printlns in there, but it didn't work

borkdude19:11:44

What could I be doing wrong?

borkdude19:11:50

small repro:

ClojureScript 1.10.520
cljs.user=> (require '[clojure.test :as t])
nil
cljs.user=> (defmethod t/report [::default :begin-test-var] [_] (println "hello"))
#object[cljs.core.MultiFn]
cljs.user=> (t/deftest foo)
#'cljs.user/foo
cljs.user=> (t/run-tests)

Testing cljs.user

Ran 1 tests containing 0 assertions.
0 failures, 0 errors.

dnolen19:11:07

@borkdude also possible there's a bug? I don't remember if we have tons of tests around the reporting aspect

borkdude20:11:41

could be! if anyone wants to help: https://github.com/borkdude/naw

borkdude20:11:02

@dnolen ah: ::t/default

mruzekw23:11:18

If I want to write a macro just for CLJS that uses a cljs.core fn, how would I do that?

mruzekw23:11:52

Right now I have macros.clj and macros.cljs, but it says clj->js doesn’t exist

mruzekw23:11:48

Looks like I have to bind it myself? macros.cljs

(def clj->js cljs.core/clj->js)

thheller23:11:24

you don't need that no. how did you write the macro?

bfabry23:11:57

sounds like you're using plain-quote instead of syntax-quote fwiw

mruzekw23:11:42

macros.clj

(ns myapp.backend.macros
  (:require [clojure.tools.macro :as macro]))

(defmacro deflambda
  [name & body]
  (let [[name [bindings & body]] (macro/name-with-attributes name body)]
   `(def ~(vary-meta name assoc :export true)
       (fn ~bindings
         (p-resolve (clj->js ~@body))))))

bfabry23:11:46

you're using clj->js in macros.clj? shouldn't that be in macros.cljs?

thheller23:11:55

@mruzekw use (cljs.core/clj->js ~@body)

mruzekw23:11:10

From what I’ve read I can’t define a macro in a cljs file

mruzekw23:11:27

Ah, okay, that seemed to work without warning 🙂

thheller23:11:53

since the stuff is living in clojure it'll try to resolve clj->js and won't find it in clojure.core. so it'll turn it into myapp.backend.macros/clj->js

thheller23:11:11

syntax-quote is weird like that 😉

mruzekw23:11:14

Yep, that’s exactly what it was trying

mruzekw23:11:42

So symbol resolution is local -> clojure.core -> local?

thheller23:11:07

not quite. the ns-form controls this basically. all the names from clojure.core are just automatically added

thheller23:11:30

thats why you don't need to qualify assoc and such

mruzekw23:11:03

Right, makes sense. Was just confused about using cljs.core fns

thheller23:11:25

yeah, confuses me all the time too 😉

mruzekw23:11:36

But since it’s a macro, it defers execution and uses the full qualified name, so as long as it’s used in cljs, it should be good

mruzekw23:11:44

Am I correct?

thheller23:11:03

not quite, there is no execution

mruzekw23:11:19

Well, I just mean it prints out the forms to execute

mruzekw23:11:26

Not lazy eval

thheller23:11:55

it just returns the modified form as data

thheller23:11:06

the compiler then processes that form and compiles it

mruzekw23:11:21

Why is it that we have to go through the clj compiler?

thheller23:11:25

but you are returning just data (a list of stuff)

mruzekw23:11:57

Could we use a self-hosted compiler without shipping the compiler?

thheller23:11:44

now I'm confused. we are using the CLJS compiler which is written in CLJ. but we are not using the CLJ compiler

mruzekw23:11:27

haha, I just mean, could we not write our macros in pure CLJS if the CLJS compiler (java or self-hosted) did a compilation step?

mruzekw23:11:11

Like step one) expand macros, step two) transpile cljs to js

thheller23:11:39

the self-hosted compiler can do that yes but the minimum size of your build is somewhere around 5mb or so

thheller23:11:47

not actually usable for most projects

mruzekw23:11:54

Hmm, why would that be?

thheller23:11:38

no :advanced optimizations for one. also need to keep all the analyzer metadata for compilation

thheller23:11:38

the compiler itself isn't that large but all the limitations and requirements it brings make everything else larger

mruzekw23:11:59

Hmm, okay. I’ll have to look into this more

mruzekw23:11:07

Does lumo not change anything?

thheller23:11:32

lumo is using the self-hosted compiler yes

mruzekw23:11:36

Okay, I don’t think I’m fully grokking why just yet, but I can look into over time

mruzekw23:11:42

Thanks for your help!