hi, is it possible to do cljs development without node? any good framework that doesn't have node dependency? any wasm based framework?
I am far from an expert on this topic, but you can do cljs development using a web browser of your choice to execute the JavaScript code output by the cljs compiler, without Node.
If you mean development running on a host, not a web browser, there are multiple choices of JavaScript runtime environments besides Node that can be used, but I have no idea if that is practical in terms of libraries and host access they provide.
thanks... i saw fulcro and reagent... seems to be popular but both need node.js...
would be nice to have isomorphic clojure without node dependency... like blazor for c# or bolero for f#...
Hello. I would like to run a macro which analyzes the body of each def
-ined expression in a CLJS program via my own macro. Is there a way to tweak the Clojurescript compiler to redefine def
during compilation without modifying the source code to be analyzed?
> Yes but some build tools don’t support custom readers for CLJS. @p-himik Which tool did you have in mind?
Shadow-cljs
Couldn't find it in the documentation but here's a reference from the relevant issue: https://github.com/thheller/shadow-cljs/issues/272#issuecomment-386865049
Or is there any other tool which I could use to run such analyzis?
What I want to do is to find the CSS classes used in a codebase. I would be happy to skip the CSS classes on render functions which would be removed by dead-code elimination, that’s why I am thinking about using the CLJS compiler.
Here is the related Github issue: https://github.com/green-coder/girouette/issues/28 I might try to solve it using clj-rewrite at first, and see later what I can do w.r.t. dead code elimination.
If the CLJS compiler can emit a list of fully qualified symbols which are dead code, that would be already very useful.
How does Reagent need node.js? I'm not familiar with Fulcro but I have doubts it requires it as well.
I have two projects that use Reagent with CLJS on the frontend and CLJ on the backend. Node.js is only used to install NPM dependencies. There's no trace of it in the runtime.
Is it tied to Reagent? If not, how would you determine what's a class and what's not?
Also, just in case - it's impossible to determine all classes during compilation time. Even some code as simple as this one will make it impossible:
(def view [class]
[:div {:class class} "Hello"])
It is not tied to reagent. My current idea is described in the Github issue, it is based on annotations (meta data)
But it annotates Hiccup. And in particular, it seems to use the extension that Reagent has introduced on top of Hiccup - in particular, being able to pass a collection to :class
.
In your example, the annotation should be added at every place the user types the name of a class.
(let [my-classes ^:css ["class1" "class2"]]
[view my-classes])
The ^:hiccup
is for convenience when using hiccup, but the same can be done using only ^:css
Ideally, if I can find a tool that rewrites a Clojure(Script) code base without the dead code, my problem is resolved.
Ah, I see. Still, (str "color-" (if blue? "blue" "red"))
won't work. :)
In any case, you don't need to create a custom macro for def
at all. Especially given that such metadata can be outside def
:
(let [classes ^:css ["class1" "class2"]]
(def x [] [:div {:class classes}]))
You can do it with a custom compilation step that just reads the CLJS/CLJC files as EDN and finds all elements with the right metadata.
Two issues that I can see right now though:
- You cannot put metadata on keywords and strings. So users will have to wrap all single-keyword or single-string classes in a vector
- In the issue you mention ^:css (conj ["ml-2"] :py-2)
- how would that even work? Assuming conj
can be any function whatsoeverWhat do you mean by "rewriting CLJ(S) code"? What exactly do you need to rewrite? Is it only for the second item in the "Nice to have" section in that issue?
I must be greedy 🙂
As long as the user can write the metadata in front of the strings (which is the case), we can possibly have a tool which parses and finds it from the source code.
I don't want to dissuade you, but I genuinely think that what you want is unachievable.
cljs.user=> (def a ^:hello "world")
Metadata can only be applied to IMetas
You just cannot determine everything in compile time.
I mean, without putting some constraints on users. Like not composing strings and not using conditional classes.
oh no ...
Maybe a literal reader would work then. #css "my-class"
?
Yes but some build tools don't support custom readers for CLJS.
As a last option, maybe a do-nothing macro can be used as a way to annotate things. (girouette/css "my-class")
Yes, I was in the process of writing about that. :) This way, it will even be possible to create class names dynamically if a user makes sure that the function/macro is used for every class name that will be used in runtime. Still, you won't be able to use DCE.
Thank you for all your feedback @p-himik, that's very helpful.
You mean a function that parses a JSON string or a function that parses the result of JSON.parse?
The latter.
I wouldn't implement my own JSON.parse
- it's highly optimized in browsers, you just can't get on that level of performance from the JS level.
Can I mark an argument in ClojureScript as deprecated, so that the compiler will pick it up and flag it?
I don't know the answer but I'm curious - what does "deprecating an argument" mean?
I think Hickey’s answer would be to create a new function and leave the old as is
although, phrased more eloquently
Well that argument doesn’t hold well in an internal codebase 😄 Ideally I’m looking for a good “find all references” tool and the compiler is at least exhaustive 😉
I should have rephrased, the argument is in a :keys
destructuring so it’s a little bit more involved.
Actually what about deprecating a var
? Is there some metadata I could assign there?
Ah, OK, I see now. So technically it's not an argument at all, it's just a result of destructuring. :) It's just not possible. The compiler will not be able to do anything in this scenario, for example:
(let [args {:a 1}]
(my-fn args))
It's possible for functions:
cljs.user=> (defn ^:deprecated x [])
#'cljs.user/x
cljs.user=> (x)
WARNING: cljs.user/x is deprecated at line 1 <cljs repl>
nil
And protocols:
cljs.user=> (defprotocol ^:deprecated P)
false
cljs.user=> (defrecord R [] P)
WARNING: Protocol P is deprecated at line 1 <cljs repl>
cljs.user/R
Can't find anything else in the CLJS code.Interestingly, CLJS itself does not use the metadata but rather specifies the deprecation just in docstrings or sometimes even in regular comments.
I found a couple posts about creating web components in CLJS. I am a bit surprised that they are not more popular, is there any particular reason? Looks like an interesting match for React given that CLJS could compile hiccup to efficient web components eschewing "a lot" of vdom diffing. (@thheller that was occuring to me while having a quick look at Arborist)
Can you share the posts, please?
I suppose he meant https://clojureverse.org/t/shadow-arborist-exploring-a-cljs-world-without-react-preview/4278 or rather the more current https://github.com/thheller/shadow-experiments
as for web components they are kind of tedious to work with and don't fit well into the whole REPL/hot-reload world we live in now. They do solve some issues but in turn create others, basically its a mess 😛
Indeed, all that. @thheller I admit I haven't toyed properly with web components so I am very naive (and should do so before starting an involving conversation). The issue I see with a react-based workflow is how to hot-reload a webcomp whose template has changed. Is there any other obvious issue?
that is the web components concern and where this fails entirely
as far as react or alternatives are concerned web components are just DOM elements, just like any other div
IMHO web components don't really solve any practical issues. guess thats why they aren't more popular still.
Right, but one solution could be at dev time to generate a new tag for a web component everytime its template has changed, forcing react to re-render the whole thing (hence the updated template). Absolutely no idea how practical that would be though. But at production, I can envision interesting optimizations (eg. fast native cloning Vs creating each element of a template from JS ; React maintaining only one virtual node since a whole template is indeed seen as a single element).
dunno what you mean by template. lit-html like cloning is about as fast as react and doesn't matter in practice
dunno I'm done with react and wouldn't use web components as an alternative either. my stuff is sort of inbetween all of it and I like it:P
Working with <template>
But yeah, your work is really interesting (although I didn't have much time to read it in greater details). It's just it makes me wonder about how some ideas (such as maintaining several DOM nodes under one "virtual" node) could be applied to React, assuming the world is not ready to let it go.
Who knows, maybe if I find time some day to actually mess with this...
something has to maintain those nodes and react rarely is the issue
well react can be the issue. depends on how it is used I guess 😛
Hi all! What would be the best approach to introduce ClojureScript in an existing vue.js application?
what should the CLJS parts handle? if you want the "view" code in CLJS then that really can't be done without replacing vue entirely.
@thheller the long term goal would be to replace vue.js entirely. But as this is not doable at once, there would need to be some kind of interoperation I think.
yeah thats difficult as most of vue is compiler based. it is possible but not sure anyone has done it before or wrote a library to make it easier.
I found the glue library, but it seems abandoned
I found an interesting implementation detail difference about Clojure and CLJS. In Clojure:
user=> (defn foo [n] (= (range n) (keys (zipmap (range n) (range n)))))
#'user/foo
user=> (foo 8)
true
user=> (foo 9)
false
This shows that hash-maps have a different ordering than array-based maps (the threshold is at 8 kvs).
But in CLJS foo
returns true
for up to 32
. So is there a magic number 64 somewhere in CLJS or is this due to how something in JS works differently?my guess is that it is related to how numbers are hashed in CLJS. the array map threshold is 8 as well. probably same as https://clojure.atlassian.net/browse/CLJS-3297
hash maps are unordered and thus that ordering can change at any time (and may not be the same between CLJ and CLJS)
@alexmiller sure, I was just interested in the why, I realize this is just an impl detail one should never rely on
I remember one project where someone built a datomic query lib on top of hash-maps. And then we upgraded to clojure 1.6 and suddenly things became slow for queries with more than 8 clauses, :P
Quickly rewrote that to tuples instead
oh wait a second, does that mean that keys != keys of the same map?
map = map -> keys = keys?
(sorry that’s a beginner question)
the sequence of keys is compared, which is false when the keys are not in the same order.
so it has to be (if at all): map = map -> set of keys = set of keys
@denis.baudinot I think you can rely on keys always returning the same order with the same map, given the same version of clojure
yes (for a particular instance of the map)
if you turn it into a set, then yeah it should be true regardless of order, right?
but if I create two maps independently I don’t have that property
So the question is, are there cases for which (= m1 m2)
and (not= (keys m1) (keys m2))
? I always assumed no
there is no guarantee for that
if (identical? m1 m2)
then (= (keys m1) (keys m2))
@borkdude you proved that this doesn’t always hold with your example above ( with range …)
@denis.baudinot No, those examples were about the ordering of the keys changing due to not basing the impl on arrays
right!
but TIL that you can not rely on the keys property in general if it's not the same object
I don't think I've ever been bitten by this but good to know and it kind of makes sense too
the only guarantee with keys is that the order of (seq m)
(keys m)
and (vals m)
is the same (they are all in seq order for an instance)
so (= m (zipmap (keys m) (vals m)))
well keys
“should” return a set, because the keys of a map is a set. But I’m sure there are reasons for that it doesn’t.
oooh right, (= m (zipmap (keys m) (vals m)))
, that is the property I was thinking of and I rely on this all the time
well, disagree with "should"
that’s why I put it in quotes!
that's not what it's defined to be
one reason it's not is probably performance: coercing a seq into a set isn't free
and also the above property wouldn't hold anymore
since sets are not ordered
oh right!
(the property (= m (zipmap (keys m) (vals m)))
I mean)
good thing I’m not a language designer
(yet)
Why would we expect that property to hold in the first place?
the transitive equality on keys or the zipmap one?
the zipmap one is important in postwalk-like code
It would be interesting to research the other (non-guaranteed) property using generative testing maybe. I leave this as an exercise for the reader.
@borkdude There are such maps. See this article for a REPL session that shows how to construct one in Clojure. hash is different in cljs, so while there should be similar examples for cljs, it won't be exactly the same example: https://github.com/jafingerhut/thalia/blob/master/doc/other-topics/referential-transparency.md#example-3-interaction-between-clojures-hash-sets--and-seq
The original one (= (range n) (keys (zipmap (range n) (range n))))
is what I was thinking of
@robert.mitchell36 That wasn't a property that should hold at any time, but an implementation detail I noticed that was different for a certain n
in Clojure and CLJS. The n
in CLJS is much higher and I wondered why.
cljs hash on integers today is (hash i) -> i for a lot of 'small' integer (probably up to 2^32 or so). That is not true in Clojure since 1.6.0
Thanks! I kind of expected an insightful reply from you :)
Thank you for being part of today's nerd snipe. https://xkcd.com/356/
It turns out that Haskell developers have healthy arguments over whether hash maps should even have a function that lists the keys in a particular order, because it violates the property (x = y) implies (toList x) == (toList y) where x,y are hash maps.
It used to be that (hash i) -> i in Clojure 1.5.1 and earlier, but that leads to many hash collisions on short vectors of integers, e.g. grid coordinates with integer x and y. I created this issue recently with REPL examples in Clojure 1.5.1, 1.6.0, current cljs, and proposed patched cljs, showing the scenario and difference in hashing behavior. https://clojure.atlassian.net/jira/software/c/projects/CLJS/issues/CLJS-3297
well thanks I read the title text…..
Well, maybe "spirited arguments", not sure they are all healthy 🙂
lol. in clojure we just take what we can get, I guess ;)
I think the argument comes down to "useful to have, despite violating that useful property" versus "really, really important never to violate that property, and willing to give up useful functions in order not to violate it".
no, i have to bookmark it and move on!
Looks like it’s (hash n) -> (Math/floor n) for all numbers < (2^31 - 1), which has the interesting consequence that (hash 1.5) = (hash 1): https://github.com/clojure/clojurescript/blob/fa4b8d853be08120cb864782e4ea48826b9d757e/src/main/cljs/cljs/core.cljs#L1012
I didn’t find anything that definitely explains the difference, but I did find that the hashmap threshold is different in clj & cljs (not sure if that has any impact at all): https://github.com/clojure/clojure/blob/140ed11e905de46331de705e955c50c0ef79095b/src/jvm/clojure/lang/PersistentArrayMap.java#L33 https://github.com/clojure/clojurescript/blob/fa4b8d853be08120cb864782e4ea48826b9d757e/src/main/cljs/cljs/core.cljs#L7005
I think in CLJS they count the keys but in clojure they count the keys + values, so it's probably the same threshold
Hello,in Clojure we seperate Java/Clojure sources in different directories and say where to find each sources to leinengein,but they are part of the same project. For a project Clojurescript/Nodejs mixed how it works? They have to be separate projects,and be like npm depedencies? Or can be made as 1 project also?
> in Clojure we seperate Java/Clojure sources in different directories I don't. :) Frankly, I don't see any point in separating files by language/extension. Same with JS - I'm pretty sure at least shadow-cljs allows you to import a JS file from anywhere within the project. Probably other build tools as well.
i do it like that in clojure, i use leinengein
i know that i can use a npm package from clojurescript , but how to use a single js file ?
i want a mixed project , with clojurescript and js files
It may be just a requirement of lein or one of its plugins. Or maybe you can just specify the same path for both keys, I don't know. I don't use lein, I can't really tell. Just add that JS file along with the CLJS files and require it, something like:
(ns my.proj.cljs-ns
(:require ["my/proj/js-file.js" :as js-file]))
Whether it will work or not depends on your build tool.Ah, right - some details here: https://shadow-cljs.github.io/docs/UsersGuide.html#classpath-js
ok thank you,but from a js file ?
how to use a clojurescript file
Ah, you want to embed CLJS in JS?
i want a mixed project like i used to do in clojure
i had both .java and .clj files in 1 project
Do you plan on embedding JS code into CLJS one or CLJS code into JS one?
both
Or both?
Right. In that case, you will have to use the ^:export
metadata and access CLJS symbols via the resulting globals.
An example can be seen here: https://shadow-cljs.github.io/docs/UsersGuide.html#_working_with_optimizations
So use the first link to use JS from CLJS and the second to use CLJS from JS.
ok thank you for your help i am new i will try the first, use javascript from clojurescript and see
ok i go for it 🙂 thank you
Alternatively, create your internal APIs in such a way so that you can simply use JS from CLJS and just pass all the relevant CLJS objects directly to the JS functions. No problem.
.js
code in shadow-cljs can use import { something } from "goog:some.cljs.namespace"
to access CLJS code
that would get (def something "foo")
from that ns
and CLJS code can just to required as linked above
works both ways, just shouldn't be circular if you can avoid it