Fork me on GitHub
#clojurescript
<
2021-02-03
>
Paul01:02:09

hi, is it possible to do cljs development without node? any good framework that doesn't have node dependency? any wasm based framework?

andy.fingerhut02:02:46

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.

andy.fingerhut02:02:32

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.

Paul04:02:44

thanks... i saw fulcro and reagent... seems to be popular but both need node.js...

Paul04:02:31

would be nice to have isomorphic clojure without node dependency... like blazor for c# or bolero for f#...

p-himik07:02:46

How does Reagent need node.js? I'm not familiar with Fulcro but I have doubts it requires it as well.

p-himik07:02:37

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.

Vincent Cantin05:02:46

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?

Vincent Cantin05:02:18

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.

Vincent Cantin06:02:38

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.

Vincent Cantin06:02:12

If the CLJS compiler can emit a list of fully qualified symbols which are dead code, that would be already very useful.

p-himik07:02:12

Is it tied to Reagent? If not, how would you determine what's a class and what's not?

p-himik07:02:19

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"])

Vincent Cantin07:02:21

It is not tied to reagent. My current idea is described in the Github issue, it is based on annotations (meta data)

p-himik07:02:36

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.

Vincent Cantin07:02:59

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])

Vincent Cantin07:02:30

The ^:hiccup is for convenience when using hiccup, but the same can be done using only ^:css

Vincent Cantin07:02:50

Ideally, if I can find a tool that rewrites a Clojure(Script) code base without the dead code, my problem is resolved.

p-himik07:02:15

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 whatsoever

👍 3
p-himik07:02:58

What 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?

Vincent Cantin07:02:19

I must be greedy 🙂

Vincent Cantin07:02:31

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.

p-himik07:02:07

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

p-himik07:02:26

You just cannot determine everything in compile time.

p-himik07:02:08

I mean, without putting some constraints on users. Like not composing strings and not using conditional classes.

Vincent Cantin07:02:15

oh no ... Maybe a literal reader would work then. #css "my-class" ?

p-himik07:02:48

Yes but some build tools don't support custom readers for CLJS.

Vincent Cantin07:02:25

As a last option, maybe a do-nothing macro can be used as a way to annotate things. (girouette/css "my-class")

p-himik07:02:00

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.

Vincent Cantin07:02:31

Thank you for all your feedback @U2FRKM4TW, that's very helpful.

👍 3
Vincent Cantin03:02:34

> Yes but some build tools don’t support custom readers for CLJS. @U2FRKM4TW Which tool did you have in mind?

p-himik03:02:27

Shadow-cljs

👍 3
p-himik03:02:25

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

Vincent Cantin05:02:25

Or is there any other tool which I could use to run such analyzis?

orestis08:02:52

Can I mark an argument in ClojureScript as deprecated, so that the compiler will pick it up and flag it?

p-himik08:02:02

I don't know the answer but I'm curious - what does "deprecating an argument" mean?

hkjels08:02:10

I think Hickey’s answer would be to create a new function and leave the old as is

hkjels08:02:54

although, phrased more eloquently

orestis08:02:11

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 😉

orestis08:02:37

I should have rephrased, the argument is in a :keys destructuring so it’s a little bit more involved.

p-himik08:02:26

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))

orestis08:02:58

Actually what about deprecating a var? Is there some metadata I could assign there?

p-himik09:02:09

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

p-himik09:02:16

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.

p-himik09:02:02

Interestingly, CLJS itself does not use the metadata but rather specifies the deprecation just in docstrings or sometimes even in regular comments.

Adam Helins09:02:46

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)

Vincent Cantin09:02:09

Can you share the posts, please?

thheller10:02:42

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 😛

Adam Helins11:02:18

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?

thheller11:02:33

that is the web components concern and where this fails entirely

thheller11:02:57

as far as react or alternatives are concerned web components are just DOM elements, just like any other div

thheller11:02:51

IMHO web components don't really solve any practical issues. guess thats why they aren't more popular still.

Adam Helins11:02:22

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).

thheller11:02:02

dunno what you mean by template. lit-html like cloning is about as fast as react and doesn't matter in practice

thheller11:02:02

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

Adam Helins11:02:10

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...

thheller11:02:06

something has to maintain those nodes and react rarely is the issue

thheller11:02:33

well react can be the issue. depends on how it is used I guess 😛

👍 3
Marcus11:02:21

Hi all! What would be the best approach to introduce ClojureScript in an existing vue.js application?

thheller11:02:22

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.

Marcus12:02:22

@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.

thheller12:02:07

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.

Marcus12:02:32

I found the glue library, but it seems abandoned

borkdude14:02:02

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?

borkdude16:02:59

I think in CLJS they count the keys but in clojure they count the keys + values, so it's probably the same threshold

thheller14:02:16

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

Alex Miller (Clojure team)14:02:08

hash maps are unordered and thus that ordering can change at any time (and may not be the same between CLJ and CLJS)

borkdude14:02:32

@alexmiller sure, I was just interested in the why, I realize this is just an impl detail one should never rely on

borkdude15:02:36

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

borkdude15:02:17

Quickly rewrote that to tuples instead

dgb2315:02:13

oh wait a second, does that mean that keys != keys of the same map?

dgb2315:02:37

map = map -> keys = keys?

dgb2315:02:14

(sorry that’s a beginner question)

Vincent Cantin15:02:43

the sequence of keys is compared, which is false when the keys are not in the same order.

dgb2315:02:06

so it has to be (if at all): map = map -> set of keys = set of keys

borkdude15:02:24

@denis.baudinot I think you can rely on keys always returning the same order with the same map, given the same version of clojure

Alex Miller (Clojure team)15:02:37

yes (for a particular instance of the map)

Mno15:02:02

if you turn it into a set, then yeah it should be true regardless of order, right?

👍 3
dgb2315:02:03

but if I create two maps independently I don’t have that property

borkdude15:02:20

So the question is, are there cases for which (= m1 m2) and (not= (keys m1) (keys m2))? I always assumed no

andy.fingerhut15:02:32

@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

borkdude15:02:55

Thanks! I kind of expected an insightful reply from you :)

andy.fingerhut15:02:25

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.

andy.fingerhut15:02:17

Well, maybe "spirited arguments", not sure they are all healthy 🙂

borkdude15:02:49

lol. in clojure we just take what we can get, I guess ;)

andy.fingerhut15:02:54

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".

Alex Miller (Clojure team)15:02:58

there is no guarantee for that

Alex Miller (Clojure team)15:02:17

if (identical? m1 m2) then (= (keys m1) (keys m2))

dgb2315:02:28

@borkdude you proved that this doesn’t always hold with your example above ( with range …)

borkdude15:02:02

@denis.baudinot No, those examples were about the ordering of the keys changing due to not basing the impl on arrays

borkdude15:02:48

but TIL that you can not rely on the keys property in general if it's not the same object

borkdude15:02:20

I don't think I've ever been bitten by this but good to know and it kind of makes sense too

Alex Miller (Clojure team)15:02:35

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)

Alex Miller (Clojure team)15:02:58

so (= m (zipmap (keys m) (vals m)))

dgb2315:02:02

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.

borkdude15:02:19

oooh right, (= m (zipmap (keys m) (vals m))), that is the property I was thinking of and I rely on this all the time

Alex Miller (Clojure team)15:02:20

well, disagree with "should"

dgb2315:02:36

that’s why I put it in quotes!

Alex Miller (Clojure team)15:02:41

that's not what it's defined to be

borkdude15:02:04

one reason it's not is probably performance: coercing a seq into a set isn't free

borkdude15:02:20

and also the above property wouldn't hold anymore

borkdude15:02:30

since sets are not ordered

dgb2315:02:43

oh right!

borkdude15:02:46

(the property (= m (zipmap (keys m) (vals m))) I mean)

dgb2315:02:00

good thing I’m not a language designer

Mno15:02:10

(yet)

☝️ 6
😄 6
Robert Mitchell15:02:27

Why would we expect that property to hold in the first place?

dgb2315:02:19

the transitive equality on keys or the zipmap one?

borkdude15:02:52

the zipmap one is important in postwalk-like code

borkdude15:02:51

It would be interesting to research the other (non-guaranteed) property using generative testing maybe. I leave this as an exercise for the reader.

Robert Mitchell15:02:35

The original one (= (range n) (keys (zipmap (range n) (range n)))) is what I was thinking of

borkdude15:02:33

@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.

👍 3
andy.fingerhut15:02:21

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

Robert Mitchell15:02:40

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

borkdude15:02:01

Thank you for being part of today's nerd snipe. https://xkcd.com/356/

dgb2315:02:56

well thanks I read the title text…..

dgb2315:02:33

no, i have to bookmark it and move on!

andy.fingerhut15:02:16

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

Takis_18:02:13

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?

p-himik18:02:04

> 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.

Takis_18:02:31

i do it like that in clojure, i use leinengein

Takis_18:02:28

i know that i can use a npm package from clojurescript , but how to use a single js file ?

Takis_18:02:44

i want a mixed project , with clojurescript and js files

p-himik18:02:56

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.

Takis_18:02:14

ok thank you,but from a js file ?

Takis_18:02:22

how to use a clojurescript file

p-himik18:02:32

Ah, you want to embed CLJS in JS?

Takis_18:02:54

i want a mixed project like i used to do in clojure

Takis_18:02:06

i had both .java and .clj files in 1 project

p-himik18:02:15

Do you plan on embedding JS code into CLJS one or CLJS code into JS one?

p-himik18:02:51

Right. In that case, you will have to use the ^:export metadata and access CLJS symbols via the resulting globals.

p-himik18:02:41

So use the first link to use JS from CLJS and the second to use CLJS from JS.

Takis_18:02:48

ok thank you for your help i am new i will try the first, use javascript from clojurescript and see

Takis_18:02:57

ok i go for it 🙂 thank you

p-himik18:02:19

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.

thheller21:02:19

.js code in shadow-cljs can use import { something } from "goog:some.cljs.namespace" to access CLJS code

thheller21:02:33

that would get (def something "foo") from that ns

thheller21:02:57

and CLJS code can just to required as linked above

thheller21:02:00

works both ways, just shouldn't be circular if you can avoid it

p-himik02:02:40

Even without ^:export?