Fork me on GitHub
#clojurescript
<
2024-03-06
>
evocatus13:03:38

What are the most popular/mature web frameworks now?

p-himik13:03:56

"Web frameworks" makes it sound as if you're looking for something fullstack. Not that many fullstack CLJS solutions. If you're just looking at the frontend, then still Reagent or one of the React wrappers. Wouldn't call them frameworks though - just libraries.

👍 1
evocatus13:03:28

I'm not looking for Django, no

evocatus13:03:00

They are called "frameworks" in awesome-clojurescript for some reason

p-himik13:03:21

:man-shrugging: People have different opinions on what to call a framework or a library. React calls itself a library. Any wrapper that doesn't add too much would then also be a library. Re-frame is probably a framework, and calls itself as such, but it's just Reagent + storage and it's very flexible, doesn't give off common framework vibes.

👍 1
thomas14:03:06

never used it myself though...

p-himik14:03:11

It's not CLJS, it's both CLJ and CLJS. It also uses HTMX on the frontend, which definitely can't be called mature.

👍 1
valerauko14:03:14

my cljs stack is shadow-cljs + shadow-css + re-frame the end

1
valerauko14:03:40

shadow-css isn't "mature" but for what i do it's feature complete and works well enough

p-himik14:03:01

I myself use shadow-cljs and re-frame. But the first is a tool and I treat the second one as a library. :) A paramount, in my case, but still a library.

valerauko14:03:29

I don't think calling re-frame a framework would be too much of a stretch. I recall a discussion from the other day where someone said that you can switch libraries with relative ease but not so much frameworks. In that sense re-frame is just so all-encompassing that it'd be really tough to replace

wevrem14:03:20

I heard once, think it was on Clojure-related podcast, “You call a library, a framework calls you.” I know, that’s tangential to the original question. I use shadow-cljs as a build tool, like @U2FRKM4TW, and reagent and re-frame. My CSS is lovingly hand-coded.

wevrem14:03:54

But I’m very interested for my next project trying out something like helix or uix.

valerauko14:03:28

I don't think frameworks are inherently bad. I have had way too much horrible experiences with a "mature" framework called Ruby on Rails, but then re-frame is a joyride where my biggest issue ever is how I organize stuff

martinklepsch11:03:48

I've been using Hono from Clojurescript lately and am enjoying that experience quite a bit!

Braden Shepherdson15:03:43

I'm working on a library that wants to abstract over many data sources (databases, APIs, etc.) using a generic CRUD API and basic query data structures. that part is fine! but an important point is that it should work on both CLJ and CLJS, and that some data sources are async. since blocking is not an option in (CL)JS, I had picked up Promesa as an ergonomic way to handle async things that's cross-platform. it's working, but the pain point I'm having is around writing tests for async things in a CLJC-friendly way. blocking is out of the question, and cljs.test has its async macro that has no counterpart in CLJ. I've tried to use https://github.com/rwstauner/cljc-test-async but I'm still having difficulties. I'm fine with digging in and trying to wrap or improve Promesa or cljs-test-async. but am I missing a whole different approach that would be better?

Braden Shepherdson15:03:11

if I am to continue on this path, my best guess at a path to success here is that Promesa's own tests set an alternative executor in a fixture, for CLJ tests. I think that would solve the issues I'm having with tests running off-thread, and their results not getting reported properly. if that does solve my issues then I think the executor thing should be a promesa.test helper upstream, plus updating cljs-test-async with some new helpers to make it easier to use.

thheller15:03:55

what is the actual hurdle though?

thheller15:03:28

I mean creating an async macro that also works in CLJ should be simple as you have the option to block?

Braden Shepherdson15:03:53

sure, that part is straightforward. the main pain point seems to be CLJ tests running off-thread, which I understand is how the default executor for promesa in CLJ works. since they're running off thread, they're missing some bindings like *test-out* and the clojure.test/report functionality. I was looking at the https://github.com/funcool/promesa/blob/master/test/promesa/tests/core_test.cljc#L87 for guidance, but they have separate CLJ and CLJS code, and in CLJ the test expectations are running on-thread after the async bit is done.

thheller15:03:22

pretty sure that assessment is the exact opposite

thheller15:03:30

clojure.test doesn't use any threads at all, but it does use bindings, which in CLJ are thread local

thheller15:03:12

so if a fn is called in another thread it won't have those bindings, unless using bound-fn

Braden Shepherdson15:03:44

ah, sorry, I think I phrased that poorly. what I meant to say is: clojure.test expects the tests to all run in the same thread, since it relies on several (thread-local) bindings like clojure.test/*test-out*. the pain point is that the default executor for promesa in CLJ runs promise-y things in a separate thread, so they're invisible to the test.

thheller15:03:08

I don't have a clue about anything promesa, so can't say anything about that

thheller15:03:58

but yes, clojure.test expects the tests to run in single thread, or I guess with bound-fn that doesn't really matter as long as it finishes within the bounds of the test

thheller15:03:10

async testing is messy, so might be best to make those tests clj/cljs specific

thheller15:03:41

probably less headaches than trying to make a tests that works on both platforms

Braden Shepherdson15:03:07

😢 it's really unfortunate to land there when the API I'm testing is identical between the platforms. but yes, async testing is a disaster. async anything is a disaster and JS not supporting blocking (or even pretending to) is a nightmare. async/await is an improvement but it's still "contagious".

lilactown16:03:28

basically an async-test macro that in CLJ is just a do and in CLJS it's a (async done (go ~@body (done)) (or a p/do for promesa could be done)

Braden Shepherdson17:03:25

Interesting... That looks plausible. Do you think core.async would be a better pick for a library like this? I've always found it kind of obtuse, but maybe it's finally time to learn it properly.

lilactown19:03:35

i think it's no better or worse than promesa

lilactown19:03:48

at least for CLJS, for the purposes you're saying

lilactown19:03:25

I'd probably pick promesa today for a lib since it interops a lot better with native promises

Braden Shepherdson18:03:23

I ended up doing something similar to @U4YGF4NGM’s suggestion to make this work: • I defined an async macro that's transparent in CLJ and wraps with (cljs.test/async done# (p/finally (p/do ~@body) (fn [_#] (done#)))) on CLJS.

Braden Shepherdson18:03:54

• and added a clojure.test/assert-expr/`cljs.test/assert-expr` for p= that unwraps the values properly. ◦ in CLJ it wraps the two values with checks for being a promise, and derefs them if so; that blocks and it's a vanilla =. ◦ in CLJS it rewrites them into (p/let [exp# ~exp, act# ~act] (test/is (= exp# act#))). • that carries a restriction that the (is (p= ...)) need to be top-level , only nested in testing. anything more would break the promise wrapping in (p/do ...)

Braden Shepherdson18:03:18

it's tempting to rebuild this so that my async macro does something clever like postwalking the entire form looking for arbitrarily-deep p= calls and hoisting their values into a top-level p/let. I think that's practical, but it's more tricky and might break things. the above restriction is not too painful in my use-case, and it's much less painful than writing almost-identical tests in CLJ and CLJS.