This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-04-28
Channels
- # aleph (3)
- # babashka (66)
- # beginners (96)
- # calva (45)
- # clj-kondo (28)
- # clojure (30)
- # clojure-dev (2)
- # clojure-europe (20)
- # clojure-germany (22)
- # clojure-norway (4)
- # clojurescript (176)
- # clojutre (1)
- # cursive (23)
- # datalog (6)
- # datomic (7)
- # docker (3)
- # emacs (3)
- # exercism (4)
- # figwheel-main (5)
- # fulcro (8)
- # gratitude (9)
- # hyperfiddle (8)
- # introduce-yourself (2)
- # jobs (2)
- # malli (4)
- # membrane (3)
- # off-topic (17)
- # polylith (3)
- # portal (2)
- # re-frame (27)
- # reitit (3)
- # releases (1)
- # remote-jobs (1)
- # shadow-cljs (152)
- # spacemacs (8)
- # tools-deps (15)
- # vscode (1)
- # xtdb (24)
ClojureScript consumption of JS libraries has come a long way, and is really great now. It's very powerful to be able to depend on node packages, which is now possible without needing to rely on CLJSJS or similar thanks to Webpack integration and Externs Inference.
I propose that ClojureScript lean in to extern inference by changing:
1. :infer-externs
compiler option default to true
2. When no type hint is provided, create an extern anyway
3. Introduce *infer-externs*
bound to true
by default as a way to opt-out of (2)
Motivation:
The current situation is that it is challenging to create a reliable advanced
or even simple
optimization build. One must
a. Make sure that you turn on externs inference or source externs from somewhere.
b. Find and annotate lots of code like:
(.then (fn [^js/object response]
(conn/transact-entities! "calendar" [(.-result response)])))
If I miss the ^js/object
, my application will break unexpectedly at the worst time; deployment. I won't notice it in development. Tests wont catch it because tests can't be run with advanced builds.
c. Practically it is impossible for me to find all the places where I need to add a ^js/object
so I must call (set! *warn-on-infer* true)
at the top of every ClojureScript file (if I forget, I'll probably be O.K. for a while until I do some interop in that file, then broken). This sets up a frustrating cycle where the compiler is essentially prompting me to scurry around adding ^js/object
to places, but only if I ask it to let me know about them. I feel like I'm working for the compiler, not the other way around, and the result is ugly (concretion of noise in my codebase).
d. In some rare circumstance it is better to use a different hint from ^js/object
, but I'd much rather have the onus be on me to decide when that is necessary.
What do you think?shadow-cljs has introduced the default :infer-externs :auto
many many years ago for this. it basically sets *warn-on-infer*
to true
for your source files (defined as files not being in .jar files, aka libraries)
this works while supressing most noise. defaulting to true for everything gets very noisy for cases you can't do much about
Maybe an interop encapsulation function could help to reduce the pain. This would also reduce the amount of places to be annotated.
Hi Thomas, I agree that shadow-cljs greatly improves over the default compiler experience, and in reality people who want an advanced build should use shadow instead. FWIW I'd love it even more if code like:
(fn [x] (.-foo x))
needed no ^js
annotation at all.
didn't mean to suggest that everyone should be using shadow-cljs. if someone wants to use behavior it has as a reference and try to make a patch for CLJS proper that might get accepted
(fn [x] (.-foo x))
the issue with this is that if it was the default to never rename anything unknown :advanced
would perform much worse. I don't have any numbers and I would indeed prefer an option for that, just to make :advanced
a little more forgiving. However that would likely need to happen in the closure compiler, not in CLJS.
I'm not talking about never renaming anything, I'm talking only about user written object access code 🙂
For example: (fn [x] (.-foo x))
name munging is never going to be correct unless x is known. If x is not typed, why name munge it? There are only a limited number of uses of .-
in a user program, name munging only breaks the code. So users have to either be very mindful of this, or don't use .-
use goog.object or aget or something else. It just seems like a very weird choice to me.
if EVERY place in your code uses .-foo
it is entirely safe to be renamed, regardless of knowing the type
the problem only arises if some places use alternate methods (eg. goog.object) or are not part of the renaming process (eg. npm deps)
yes, the "safe" way is to never rename anything unknown but that also leads to :advanced
being much worse
Hi guys! I'm trying to use the library cljs-time. So I'm including
[com.andrewmcveigh/cljs-time "0.5.2"]
in the lein project and restarting repl.
Now when i add it to the namespace
(ns growth-clojure-library.time.time
(:use [cljs-time.core]))
It says
Execution error (FileNotFoundException) at growth-clojure-library.time.time/eval1553$loading (time.cljc:1).
Could not locate cljs_time/core__init.class, cljs_time/core.clj or cljs_time/core.cljc on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.
Am i missing something here? Thank you!Looks like you're trying to use a cljs library in a clj project. Maybe try clj-time?
[clj-time "0.15.2"]
@U7RJTCH6J Basically I'm creating a library for both clojure and clojurescript. I'm already using clj-time to handle the clojure case and i wanna use cljs-time to handle the clojurescript case. Like there is a way to tell whether the library is being invoked in clojure project or a clojurescript project and so i make the choice between clj-time and cljs-time dynamically.
The short answer is reader conditionals, https://clojure.org/guides/reader_conditionals.
Sure i'll dig into that. In the meantime I wanted to ask, how do i create and package a clojurescript library? Is there a guide for that?
That's a good question. I'm not sure what guides are available for deploying a clojurescript library. I'm also not sure if there's any special considerations for deploying a cljs or cljc library, but maybe someone else might have a good recommendation.
I think it's basically the same process as deploying a clj library, https://github.com/clojars/clojars-web/wiki/Tutorial
This commit tells the story: https://github.com/magnars/realize/commit/a4acc97ef6de2b38259d13cc71adf14187af21c1
@U07FCNURX right i did that actually. But after realizing that it's strictly a front end library i switched things to clojurescript
Can i ask u guys about this error? First time i include dependencies in deps.edn format. Does anything look wrong in this photo?
wrap :dependencies
in a vector - it is a vector of 2-tuples (vectors with 2 elements)
Roger that! Thank you!
That's a good question. I'm not sure what guides are available for deploying a clojurescript library. I'm also not sure if there's any special considerations for deploying a cljs or cljc library, but maybe someone else might have a good recommendation.
And, just in case - if you need to check whether something is a string, there's string?
. But it will work only for primitive strings and not string objects, so it's pretty much the reverse of (instance? js/String ...)
.
With CLJS we usually require
libraries / modules. I have the need to integrate with a couple of simple Class definitions is xyz.js ... how can I access that without making a fake library? [ I'll do that if I have to obviously ]
if it is Closure style JS it just works, if it isn't you need to do the usual external library song and dance
I am using nbb
and there I can just do this style
(:require ["signature$default" :as signature])
(new signature "API_KEY "API_SECRET")
no fuss ... except I had no idea WTF $default
was until @U04V15CAJ mentioned it
Hahaha funny you bring that up, I learnt of $default
yesterday watching a youtube where borkdude just casually meantioned it 🙂
yes, $foo
also works, it's just that default
is a common name for the default exported value (and has special meaning within import
syntax but CLJS isn't dealing with that part)
finding out how to call JS things is always a new adventure
@timothypratley I think you need to clarify some expectations. If you're sourcing stuff from node_modules
for browser :target :bundle
already does most of these things anyway
Hi David,
Yes I am sourcing stuff from node_modules
for browser, and no :target :bundle
does not.
https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/closure.clj#L2558-L2565
fwiw, it would be good to hear more of what your problems are because this stuff is pretty old now and at least in the projects that I currently work on we haven't hit advanced compilation issues in the entire time and that's w/ devs who are not ClojureScript experts doing most of the lifting
said another way, the only thing it does is #1 😜
sure, I'm happy to share
Here's a sample of the "fallout effect":
Let's start with one 🙂
why are you hinting browser stuff when Google Closure provides externs for standard JS browser interfaces?
If you pick a specific one I can expand the context and explain why I needed to add the type hint.
O.K. so a simple Event type example is:
[ui/button {:icon (str "circle" (when circle " outline"))
:style {:padding 0}
:size "tiny"
:basic true
:circular true
:on-click (fn [^js/Event ev]
(.stopPropagation ev)
(rm/record! context id {:circle (not circle)}))}]
Without hinting it, the compiler will produce a warning.
What is your definition of "wrong"? The above code works in advanced mode.
Right.
The issue is, if I don't set warn-on-infer, I'll definitely miss they cases where I must type hint
And those are harder to predict because I'll only see them in production if I regression test the app.
Or maybe I'm not understanding what you are saying. If I'm just doing it wrong, that would be grreat to know.
Interop can (and does) happen anywhere, not just in namespaces that require js.
For example the local storage stuff
the interop is contained in a ns
but other things look at the things it produces.
I'm assuming you are implying "Why not only warn-on-infer in namespaces that require a node module" << which might not be what you mean.
"js requires are poisoned", really getting the hot takes now hahahaha 🙂
So I want to see your cases where actually need the hint which lead you down this unfortunate path
Is this a better bottom level example?
It wont work without type hinting.
I agree completely.
all direct calls and all direct return values should also be be poisoned (if something is missing we should fix that)
so rather than doing what you're suggested I think it would be better to figure out how to do the right thing without setting warnings
:thumbsup:
what I'm trying to suggest is that you don't need warnings, that's what :bundle :target
was designed to solve which came later and was informed by :advanced
issues in practice
it is possible we could better handle callbacks / promises coming from poisoned requires - but that needs thought
I appreciate that. Following your advice above does not work in practice for me because of the "something is wrong" part; For example doing exactly what you describe above results in: main.js:35235 Uncaught TypeError: $G__19485$jscomp$1$$.$useDeviceLanguage$ is not a function ^^ Usually not printed so nicely,, I have to add compiler options
:pseudo-names true
:pretty-print true
i.e.: oh the call (.useDeviceLanguage)
broke
damn..
why? I don't know
If I type hint it, it works
we have so many tests for externs inference, and when people take the time report specific cases it's a huge help
FWIW I do try to report bugs, often I don't know what is a bug or not
If you tell me this is a bug, I believe you of course
but when I encountered it I thought it was because I didn't type hint it
I didn't recognize it as a bug.
all subsequence method invokes or property access on that the poisoned value is also poisoned
just for reference if I replace the :refer with :as and do fauth/getAuth is behaves the same (just tested)
if you're not doing that, then you're probably just writing Closure JS + ClojureScript and why do you care?
I have a question about tap> forwarding to js/setTimeout. This maybe is abusing tap>
but I thought it would be useful to have a with-tap
function that would create an (atom [])
, add a tap fn to conj results onto the atom, execute a function (presumably with calls to tap>), remove the added tap, and return the results of each call to tap inside the function along with the original results of the function. I thought this might be a useful alternative over println in a repl environment so that the debug log & final result could be inspected as a single return value. However, since tap>
is executing using js/setTimeout, it appears that the atom is not filled with values until after with-tap
finishes. One idea I had was to rebind *exec-tap-fn*
using a synchronous implementation, but was having difficulty doing that in a cljc file. If I just use a single global add-tap
call and don't remove-tap
after that will also work, but thought it might be helpful to limit the scope of the tap function. I suppose I could also write a replacement function for tap>
that is a no-op if it's not wrapped in with-tap, but was hoping to re-use tap>
(defn with-tap [f]
(let [tap-log (atom [])
tap-f (fn [v] (swap! tap-log conj v))
_ (add-tap tap-f)
result (f)]
(remove-tap tap-f)
{:log @tap-log :result result}))
(comment (with-tap (fn [] (tap> 1) (tap> 2) 3))) ;; expecting => {:log [1 2] :result 3}, but instead getting {:log [] :result 3}
Has anyone else experimented with local, temporary tap handlers and had any luck, or is this just going against the grain?Not sure if this is useful, but you could try something like:
(with-redefs [tap> println]
(tap> ::hi))
especially because you only care about sync tapsThanks, I think just rebinding cljs.core/*exec-tap-fn*
in a #?(:cljs)
block to make it synchronous is good enough, though rebinding tap>
as the swap command might also work. I explicitly do not want to use println though as that means the output is separated from the data.
The main advantage of the with-redefs
is that I think you will run into the same problem with tap>
in clj :thinking_face:
Yea I guess:
(defn with-tap-log [f]
(let [tap-log (atom [])
result (with-redefs [tap> (fn [v] (swap! tap-log conj v) true)]
(f))]
{:log @tap-log :result result}))
Is about as good as it gets.Is anyone familiar with the error surrounding Fulcro 2. I’m using Fulcro 3 and trying to incorporate Nubank Workspaces, but keep getting this. I’ve heard it’s due to CLJS no longer using the library, but don’t know how to work around it.
The required namespace “goog.debug.Logger.Level” is not available, it was required by “fulcro/logging.cljc”
{org.clojure/clojurescript {:mvn/version "1.11.4"}
thheller/shadow-cljs {:mvn/version "2.18.0"}
nubank/workspaces {:mvn/version "1.1.2"}}
Any ideas appreciated.
the fulcro version workspaces uses it outdated and only works with older CLJS versions
I think this is about to be an ignorant question. Fulcro 3 uses a completely different java style namespace convention, so there shouldn’t be any collision hyjinx right?
I got no dice on adding Fulcro 2. But I appreciate the idea. I think my clojurescript dep might need to be updated manually. Does that make sense?
no, that is the source of the problem. fulcro2 is old and outdated. it used to rely on an older clojurescript version
workspaces seems to be outdated as well, so your only option is downgrading shadow-cljs and cljs
Well, it was nice of you to help. But I think my adventures with Workspaces will come to an end before it started. Cheers!
In the docs, it explicitly calls for Fulcro 2 namespaces to run the Fulcro specific features.
I know some people are having success with storybook on shadow. That may be a more beaten path for that workflow. As always, you’re a great help.
you can use com.github.awkay/workspaces {:mvn/version "1.0.3"}
follow the template here https://github.com/fulcrologic/fulcro-template/blob/master/shadow-cljs.edn
I had the same issue with workspaces and this solved it
Hi, I have a question about Bidi router - I'm trying to use regex to match routes, however behavior is really weird, depending on whether i use capturing groups or not, it matches everything until the last character, while in others it matches only the last character. Anyone have experience with this?
found a solution, according to this post (https://github.com/juxt/bidi/issues/141) it adds some extra regex to make it "safe" so the solution was to add an extra pair of brackets around regex with an empty string and now it works - all explained in the link above.
not strictly clojurescript related, but is there a way to get Promises return values with the xhrio client?
Not sure what you mean. But if you have a promise and you need to do something with a resolved value, you have to use .then
or some wrapper that will use .then
itself. There's no way around it except for writing JS code that uses await
.
not exactly what I meant, my bad. I mean that XhrIo.send()
use callbacks and I'd like to use promises instead.
Ah, wrap it in a promise then. :)
Create a promise manually, send the request in its executor function, and resolve the promise in the send
callback.
I'm not sure if this already supported, but in my case has to do with whether i'm using vanilla cljs, or shadow, or if has to do with my IDE/LSP or lein/deps, etc, but I was wondering:
Is it possible to Go To Definition
for node_module dependencies' source code in the same way that I've heard that one can for Java/jar ones in clj?
In principle it's possible, but I'm not aware of any IDEs that provide the capability.

Which IDE do you use?
off topic, @U037TPXKBGS can you get Java/jar ones in clj?
in calva?