Fork me on GitHub
#clojure
<
2022-10-22
>
agorgl09:10:17

Hello there! I want to make a custom formatting with zprint for hiccup with below rules: • Every vector item is always on a new line • Except for attribute maps that should always start on the same line as the first keyword (tag) Also: • Each attribute map pair should always be on a new line • Each attribute pair should stay on the same line even if the attribute's value is too long The default :hiccup style of zprint almost accomplices these but there are some things I want to tweak. I've read zprint reference and understand the default :hiccup configuration at this point, but I would like some guidance. Given the example input:

[:div {:class "mr-5 lg:mr-0 lg:hidden"} [:a {:class "flex-none text-xl font-semibold dark:text-white" :href "#" :aria-label "Brand"} "Brand"]]
Formatting it with zprint '{:style :hiccup}' leads to:
[:div {:class "mr-5 lg:mr-0 lg:hidden"}
 [:a {:class "flex-none text-xl font-semibold dark:text-white",
      :href "#",
      :aria-label "Brand"}
  "Brand"]]
Which is perfect, exactly what I want. The first problem arises if I add a really short attribute pair, then this:
[:div {:class "mr-5 lg:mr-0 lg:hidden" :hello "there"} [:a {:class "flex-none text-xl font-semibold dark:text-white" :href "#" :aria-label "Brand"} "Brand"]]
becomes this:
[:div {:class "mr-5 lg:mr-0 lg:hidden", :hello "there"}
 [:a {:class "flex-none text-xl font-semibold dark:text-white",
      :href "#",
      :aria-label "Brand"}
  "Brand"]]
Note the :hello "there" stays on the same line as the first pair instead of going on its own. The second problem arises if I add a really long attribute value, then this:
[:div {:class "mr-5 lg:mr-0 lg:hidden"} [:a {:class "flex-none text-xl font-semibold dark:text-white heeeeeeeeeeeeeeeeeeeelloooooooooooooooooooooooooooooooooooooo" :href "#" :aria-label "Brand"} "Brand"]]
becomes this:
[:div {:class "mr-5 lg:mr-0 lg:hidden"}
 [:a
  {:class
     "flex-none text-xl font-semibold dark:text-white heeeeeeeeeeeeeeeeeeeelloooooooooooooooooooooooooooooooooooooo",
   :href "#",
   :aria-label "Brand"}
  "Brand"]]
In this case I would love it if not only the long attribute value stayed in the same line as :class like the other pairs, but also if the whole attribute map started in the same line as the :a keyword tag, like this:
[:div {:class "mr-5 lg:mr-0 lg:hidden"}
 [:a {:class "flex-none text-xl font-semibold dark:text-white heeeeeeeeeeeeeeeeeeeelloooooooooooooooooooooooooooooooooooooo",
      :href "#",
      :aria-label "Brand"}
  "Brand"]]
So far I extened the zprint's :style-map with a :hiccup-custom style that is a clone of the default :hiccup style from zprint's config.cljc Can somebody give me pointers on what configuration I should look at from there?

Mark Wardle11:10:43

Notwithstanding the caveats inherent in asking such a question (with no clearly correct answer), May I ask for some opinions on server side rendering? I have mostly used Apple’s WebObjects until discovering Clojure and since have been using clojurescript and Clojure to build SPAs (re-frame and then Fulcro), I always hit greater complexity when the application reached a certain size. However the degree of user interactivity and responsiveness possible with SPAs would be hard to lose. This has naturally led to reading about SSR approaches such as phoenix liveview, ripley, biff, htmx and ctmx. Are there other approaches I’ve missed here, and I was inclined to try to use a combination of rum and htmx natively rather than the batteries-included approach of biff. WO had its faults, but it actually had nice abstractions for the request-response lifecycle. Are there any other options I could consider?

mdiin15:10:42

I don’t know about other approaches, but htmx is really nice to work with. I’m building a server-side rendered app with it at the moment. What does rum bring to the table in combination with htmx?

Mark Wardle15:10:07

Rendering.... presumably you are using hiccup instead? And did you look at a wrapper such as ctmx or do you just use directly? Haven't found many examples so far.

mdiin15:10:58

Ha, wow, I always figured Rum was for SPAs. 🙈 But yes, you are right that I’m using hiccup for rendering.

mdiin15:10:45

So I guess my question should have been: Why rum instead of hiccup for rendering?

Mark Wardle15:10:35

It looks as if I might be able to share components and use them in SPA and SSR apps.... and I thought rum was faster than hiccup... but my bigger query related to whether there was some community momentum towards htmx or similar.... and how people were using it... wrappers / routing / etc.

mdiin15:10:16

I went a bit off topic, I just had to ask. 😉 In my app I have very little htmx wrapper code. I only have a single piece of middleware that detects whether a request was made by htmx or not. In case it was, I just return the rendered html snippet; in case it isn’t I return a full html page. I detect that using the HX-Request header I believe it is called. I think htmx is sufficiently lightweight to not need much wrapping, if any at all.

🤯 2
Mark Wardle15:10:10

That is really good to hear.

mdiin16:10:53

I feel like I need to add that my app is still in its infancy, so I might just not have seen the pieces of htmx that would warrant a wrapper.

Mark Wardle17:10:20

I've bet the farm on this now...

pppaul17:10:27

i've been using unpoly for my current project (2 years). server side rendering has issues too, but the less front end code, and less re-implementing basic HTML widgets, the better. I like using traditional templates so my web designer doesn't have to learn how to do clojure, and can just focus on html/css (this is also a problem with SPAs). currently my largest pages are ~300kb HTML, and updates to those pages are typically 300bytes or so.

pppaul17:10:50

htmx shouldn't limit you from using more sophisticated view layers, i hope.

Mark Wardle17:10:16

Thanks @U0LAJQLQ1 - are there any considerations for the use of unpoly with clojure?

pppaul17:10:00

no, i think it's similar to htmx, it's probably bit more opinionated. these are very light weight frameworks, they expect your server to be traditional, and you can do a little more work to upgrade traditional page responses with partial ones, and this task is going to be specific to your resource. I created components that use http headers to talk to my routes that do partial updates (partials are a pain in the ass with traditional templates, and i may mix in rum/hiccup in the future). these types of frameworks do make your server resources care more about http headers, in general. so, main consideration would be to make your front end be able to communicate the partial ID (i use headers for this), and then that header maps to a resource lookup, this lookup will be able to do a partial DB request & render partial HTML, however these are hierarchical in the worst case. worst case: your partial update changes a parent view, or a sibling view, or a global view. so worst case you are doing the react magic DOM diff on your backend. unless you find some glaring issues with htmx, i wouldn't consider unpoly. but you should look into it just to compare and contrast.

pppaul18:10:43

i didn't start doing partial updates until a few months ago, and just sent whole pages until then... it's convenient to be able to delay this, or optimise specific pages, or parts of pages, over others.

Mark Wardle19:10:44

That is really helpful thank you. Lots to consider.

rickmoynihan09:10:49

We hand rolled a solution to this about 3 or 4 years ago. Using reagent in the front end with fe/be shared views. We wrote a simple lisp machine server side renderer to support reagent component types (as native hiccup doesn’t support them), and use transit to communicate the view data between fe/be via inline data attributes — and then occaisional requests for components id’d in a way similar to @U0LAJQLQ1. We avoided using reframe because it wasn’t obvious how to make the singleton state atom work with ssr and the idea of having components… without for example rewriting reframe explicitly in cljc. The advantage was to get server side rendering on the initial render, and then have the front end take over. We abstract across reagent atoms by wrapping the data in a delay on the server side. The disadvantage is the complexity tax; and the need to do isomorphic routing… you can use reitit for this. In the end we also had some design changes that changed the information hierarchy such that we didn’t really benefit as much as we were hoping to… so I don’t think it was worth it in the end. At the time there weren’t really any solutions to this that I was aware of… except maybe fulcro; but iirc fulcro was being rewritten into fulcro 3 at the time. Anyway it’s not rocket science, but wrapping it up into a nice framework is hard…. I wouldn’t particularly recommend it… and would be interested in anything you find.

👍 1
Mark Wardle08:10:54

Really interesting thank you. I’m currently using Fulcro but I can see that I might be better with a la carte client side scripting rather than an SPA.

Jared Langson19:10:08

Having trouble using the https://github.com/clojure-goes-fast/clj-memory-meter. I've tried it with jdk11 and jdk17. When I call mm/measure (mm/measure [1]) I get the error "NoSuchFieldException jvm java.lang.Class.getDeclaredFieldInternal (Class.java:930)". Not sure what is wrong. I did add to project.clj

:jvm-opts ["-Djdk.attach.allowAttachSelf"]
per the github instructions. Any advice on how to get memory-meter working?

p-himik19:10:59

Hmm, worked just fine for me on JDK 11.0.16 on Ubuntu with clj -Sdeps '{:deps {com.clojure-goes-fast/clj-memory-meter {:mvn/version "0.2.1"}}}' -J-Djdk.attach.allowAttachSelf.

p-himik19:10:31

What is the stacktrace of that exception? And which JDK exactly are you using?

Jared Langson19:10:57

How do I get the stack trace? Right now I'm using ibm jdk 11.0.17 but I also tried with oracle openjkd 17.0.2

Jared Langson19:10:33

Maybe I am using it incorrectly? I'm trying to call mm/measure from inside a Cursive repl.

Jared Langson19:10:41

Also got this error message "(mm/measure [1]) NoSuchFieldException jvm java.lang.Class.getDeclaredFieldImpl (Class.java:-2)"

Jared Langson19:10:06

I think this is the stack trace

(try (mm/measure [1]) (catch Exception e (pst e)))
NoSuchFieldException jvm
	java.lang.Class.getDeclaredFieldImpl (Class.java:-2)
	java.lang.Class.getDeclaredFieldInternal (Class.java:930)
	java.lang.Class.getDeclaredField (Class.java:905)
	clj-memory-meter.core/get-self-pid (core.clj:100)
	clj-memory-meter.core/get-self-pid (core.clj:97)
	clj-memory-meter.core/fn--69 (core.clj:123)
	clj-memory-meter.core/fn--69 (core.clj:123)
	clojure.lang.Delay.deref (Delay.java:37)
	clojure.core/deref (core.clj:2228)
	clojure.core/deref (core.clj:2214)
	clj-memory-meter.core/meter-builder (core.clj:128)
	clj-memory-meter.core/meter-builder (core.clj:128)

p-himik19:10:29

I tried on JDK 11 from CLI and on JDK 18 from Cursive - both worked just fine, both OpenJDK. Do you get the exact same exception on both JDK versions? Are you 100% sure that Cursive uses the JDK version that you have specified?

Jared Langson19:10:21

I'm pretty sure it's using that JDK version.

p-himik19:10:47

When you start a Cursive REPL, it outputs the exact Java binary it uses as the very first line, at least on my end.

p-himik19:10:03

Ah, and which Clojure version do you use?

Jared Langson19:10:07

JDK17 stack trace

(try (mm/measure [1]) (catch Exception e (pst e)))
InaccessibleObjectException Unable to make field private final sun.management.VMManagement sun.management.RuntimeImpl.jvm accessible: module java.management does not "opens sun.management" to unnamed module @7f4f095d
	java.lang.reflect.AccessibleObject.checkCanSetAccessible (AccessibleObject.java:354)
	java.lang.reflect.AccessibleObject.checkCanSetAccessible (AccessibleObject.java:297)
	java.lang.reflect.Field.checkCanSetAccessible (Field.java:178)
	java.lang.reflect.Field.setAccessible (Field.java:172)
	clj-memory-meter.core/get-self-pid (core.clj:100)
	clj-memory-meter.core/get-self-pid (core.clj:97)
	clj-memory-meter.core/fn--69 (core.clj:123)
	clj-memory-meter.core/fn--69 (core.clj:123)
	clojure.lang.Delay.deref (Delay.java:37)
	clojure.core/deref (core.clj:2228)
	clojure.core/deref (core.clj:2214)
	clj-memory-meter.core/meter-builder (core.clj:128)
says jdk 17

p-himik19:10:32

Try the latest one or at least 1.10.

Jared Langson19:10:59

updating to clojure 1.11.1 fixed it

Jared Langson19:10:13

Why was version 1.8.0 causing it to not work?

p-himik19:10:05

For 1.10+ they have a different code path to get the current PID. Created https://github.com/clojure-goes-fast/clj-memory-meter/issues/9

👍 1