Fork me on GitHub
#beginners
<
2023-02-04
>
joshcho00:02:07

How do you manage both frontend CLJS and backend CLJ in one project? I have a CLJS project (re-frame) already working, and I'd like to replace the backend with CLJ. I did lein new clj-backend, but I am unsure how to proceed from here. Do you keep them separated? How do you manage sharing utility code between frontend and backend? It's been a while since I've done web development.

phill00:02:40

As I recall, you just make your own server serve the same HTML and directory tree of (generated) JS, at the same relative URLs, as the tools' builtin server, but a different port. Then point your web browser to your new server port. Then refine the details of your project.clj or whatever: coordinate your CLJS-compiler and Clojure configurations so the CLJS compiler will put generated JS in a place that your Clojure webserver serves resources from, and such that the uberjar task will include the generated JS in the jar.

phill00:02:14

I noticed that Calva has a "start a project REPL..." option specially for all combinations of (Leiningen-etc) x (Figwheel-etc) which, at best, can start the Shadow CLJS loop, fling open a web-browser window, and start a Clojure REPL that might even be switchable to CLJS after the browser gets going.

joshcho02:02:33

What are some good examples of how to structure your CLJS/CLJ project? Kinda lost as to how to put the two repos together

valerauko09:02:23

i have an example for using frontend-backend shared routing with reitit, you could use that as reference https://github.com/valerauko/shared-routing

valerauko09:02:46

There are a lot of options. You can have your Clojure backend serve some static HTML as a resource (which then loads the CLJS build), or generate the HTML dynamically however you please... It's also possible to have something like nginx serve the static HTML and then CLJ only does JSON API.

phill13:02:23

@U6D1FU8F2 as you are accustomed to having 2 distinct repos - which some might consider the better / less cluttered way to do it anyway - it is a matter of making 1 project.clj that sort of fuses all the settings of the 2 distinct project.clj's, plus probably you have a conf file for Figwheel or Shadow; and fusing the src directories into 1 tree containing clj, cljs, and cljc. Naturally the cljs winds up in distinct namespaces because it serves distinct duties. You put the schemas into cljc files and require them from both the clj and cljs. And you may benefit from some editor features that are geared toward a Clojure+Figwheel or Clojure+Shadow kind of project.

simongray17:02:19

I just put all of my files in the same src/ directory or a subdirectory of that. If they contain shared code it goes into a cljc file, otherwise clj or cljs. The respective compilers only pick up the relevant files anyway. I just structure namespaces by topic like I would any other Clojure project.

noob.clj18:02:08

I am trying to understand sorted-map-by thingy (sorry not sure what it exactly is). So based on the documentation and examples (sorted-map-by >) will create an empty sorted map that sorts in reverse order and then it can be used like this (into (sorted-map-by >) {1 :a 2 :b 3 :c} ). But when I try to do something like below it fails:

(def rev-sort-map (sorted-map-by >))
(assoc rev-sort-map :a 1) => {:a 1} ;; works great.
(assoc rev-sort-map :a 1 :b 2) => ClassCastException ;; Fails with following exception
; Execution error (ClassCastException) at aoc-2016-clojure.day6/eval8090 (form-init1659342253529398798.clj:43).
; class clojure.lang.Keyword cannot be cast to class java.lang.Number (clojure.lang.Keyword is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')
Now if I do this:
(assoc {} :a 1 :b 2) => {:a 1, :b 2} ;; this works fine.
So I know I am not using assoc wrong. I feel like I am bit lost between into, sorted-map-by and assoc. And I haven’t even gotten into point where how to use sorted-map-by without let because I cannot have comparator without binding map to something. Is there some basic understanding I am missing, is there some blog/article or something that can clarify this a bit?

ghadi19:02:48

[comparator & keyvals]
  keyval => key val
  Returns a new sorted map with supplied mappings, using the supplied
  comparator.  If any keys are equal, they are handled as if by
  repeated uses of assoc.
the docstring specifies that this will parameterize a sorted-map by the given comparator. Sorted maps are sorted by the key (not the values), and comparator functions are called on those

ghadi19:02:33

Comparators have a whole official guide https://clojure.org/guides/comparators but tldr these are functions that return #{-1, 0, 1} not just boolean.

ghadi19:02:49

the default comparator in clojure is called compare

clojure.core/compare
([x y])
  Comparator. Returns a negative number, zero, or a positive number
  when x is logically 'less than', 'equal to', or 'greater than'
  y. Same as Java x.compareTo(y) except it also works for nil, and
  compares numbers and collections in a type-independent manner. x
  must implement Comparable 
and it is what you get on the other non-parameterized sorted-map constructor

ghadi19:02:24

I misled you when I said that < is a predicate but not a comparator. It is in fact passable as a comparator (see "boolean comparators" in the guide), but the fact that comparators compare map keys and not values were the source of your confusion.

ghadi19:02:54

> cannot compare keywords

noob.clj21:02:41

Okay so that makes sense. My example works once I switch to using compare or a function with compare :

(def rev-sort-map (sorted-map-by (fn [x y] (compare x y))))
(assoc rev-sort-map :a 1 :b 2)
This works. And it was actually failing because the key :a was being passed by comparing keywords with > fails. I get the same error using (< :a :b).

noob.clj21:02:41

Errors in Clojure are bit more cryptic than I am used to 🙂 but once I understand, the error message starts making sense. Thanks for your help 👍