@joelittlejohn see https://swannodette.github.io/2014/12/17/whats-in-a-var/ which should give some relevant info. not sure if there's any better docs elsewhere
Hello everyone,
I have a CLJS leiningen project that uses shadow-cljs
, I am testing how environment variables would work if I used them for my use-case. Reading the docs I might end up with some solution using --config-merge
as recommended, but I just want to understand the environment var interaction, and if it's even possible to use them.
lein project.clj
shadow excerpt:
...
:shadow-cljs {:nrepl {:port 8777}
:builds {:app {:target :browser
...
:dev {...}
:release {:closure-defines {some.config/API_URL #shadow/env ["API_URL" :default "some default"]}
...
Now if I just set API_URL to a value, it works and it's great, but I've got to conform to my company's (fairly common) DevOps setup where they have dev/qa/prod environments running in Docker containers. Variables like the URL here in this example is controlled / injected into Docker containers (I don't control this, but it probably ends up looking something like: docker run -d [other configs] -e SOME_VAR=SOME_VALUE some-artifact
)
I did above based on how I (mis)understood https://shadow-cljs.github.io/docs/UsersGuide.html#shadow-env
I get this error: No reader function for tag shadow/env
, which looks like I've forgotten to install or configure something properly, what am I doing wrong? Perhaps this isn't supposed (or can't?) be used in the lein project file in this way?
And finally, am I stupid? I'm so snowed into this environment variable line of thinking I no longer know if there's a better way to solve this problem for CLJS.Shadow-cljs does not register shadow/env
as a global reader tag. It can be used only within shadow-cljs.edn
.
Apart from that, using :closure-defines
in your case will work if and only if you release the app with the very same API_URL
env var as the one you will use during runtime. :closure-defines
are compile time constants, not run time. If you need to move the same compiled app between environments, you cannot use :closure-defines
.
To solve a similar task in my apps, I:
- Ship frontend and backend together (I know that some people separate them for some reason I can't quite comprehend)
- Make backend serve index.html
with the relevant env vars' values embedded there within the <meta>
tags
- Read those tags' values in CLJS code so the values can be changed in run time
That's good information, it seems I might be approaching this problem from the wrong angle after all. I will check if the <meta>
approach can work for me as well, thanks again @p-himik!
Suppose Iām running clojurescript tests using my browserās javascript runtime, there is some async component Iām testing. Iād like my test to wait (āon the main threadā because that is what the test uses) for an event to finish. Is there a way to do this? core.async has channels but I donāt think you can wait on the main thread for them
You can't wait in a JavaScript at all. Well, at least in a browser, but I'm pretty sure you can't wait in NodeJS either. I think the test runner must have some async support for you to be able to properly run async tests.
javascript is single threaded so waiting/blocking the main thread would deadlock you since no other work would ever happen
Yeah, I believe the cljs.test/async
is the right answer but Iām not sure the libraries Iām using macros are āplaying well togetherā
Agreed, but I more mean waiting on the main isolate (?) if Iām using the term correctly.
(i.e. javascript does have mechanisms to model work concurrently)
depends on what async mechanism the code uses. if you get a promise you can .then
it and use cljs.test/async
as the others mentioned
Yeah, i think cljs.test/async
is the right answer
I think it sets a timeout so that anything from core.async
to js/Promise
should work
Even wrapping a Promise
in my own monad worked https://github.com/nilern/taksi/blob/master/test/taksi/core_test.cljc#L87
Those examples might help. It turns out the library uses bindings which gets āresetā when using go blocks, so itās still not straightforward the best approach
Also, if curious it is related to <https://github.com/day8/re-frame-test/blob/master/src/day8/re_frame/test.cljc>
. I believe the wait-for
api only wants nested wait-for
to handle synchronization, given the implementation it wonāt work nicely if you try using some other async control flow
Is there any way to create a second copy of the window.cljs object?
(Basically, I have code generated with compile-str
, that I would like to run in a different instance of clojurescript, if that makes any sense.
Like, if I run compile-str
on (println "42")
, I get:
"cljs.core.println.call(null,\"42\");\n\n//# sourceURL=UNTITLED/cljs.js\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVU5USVRMRUQvY2xqcy5qcyIsInNvdXJjZXMiOlsiY2xqcy5jbGpzIl0sImxpbmVDb3VudCI6NCwibWFwcGluZ3MiOiJBQUFBLDRCQUFBLDVCQUFDQSIsIm5hbWVzIjpbImNsanMuY29yZS9wcmludGxuIl0sInNvdXJjZXNDb250ZW50IjpbIihwcmludGxuIFwiNDJcIikiXX0="
I'd like to then that javascript string and eval it in a different context.And so the question is where I can get a (new) copy of the cljs
javascript object.
I don't think it's possible, or at least feasible in the way you describe. If your compiled code can be run in a web worker, each worker has its own global scope. Perhaps, that would be enough. Perhaps iframes could also help here, but they're rather hard to work with and it might be straight impossible due to security reasons.
Oh, I have a whole different window
scope.
Like, imagine if I compiled in one tab, and then ran it in another tab.
(Or at least in this case wanted to run it*)
Then why do you need a copy? Why not just send the compiled JS code there and eval it?
the self-hosted code really should eval in the context it is compiled in if you want support for macros and such. if that is not so important you can just eval all the sources the build needs in the new context
the default CLJS node-repl is implemented that way. it just loads a basic JS file that sets up the socket connection and takes very simple commands. the CLJ side then just basically sends the JS output over there to eval
you could do the same principle from window -> window or window -> worker
https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/repl/node_repl.js
@thheller I mostly need the cljs
runtime for stuff like println
.