This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-09-14
Channels
- # ai (3)
- # babashka (45)
- # beginners (81)
- # biff (26)
- # calva (10)
- # cider (5)
- # clj-kondo (55)
- # cljfx (6)
- # clojure (125)
- # clojure-berlin (1)
- # clojure-europe (37)
- # clojure-italy (7)
- # clojure-nl (3)
- # clojure-norway (79)
- # clojure-uk (1)
- # clojurescript (63)
- # clojutre (1)
- # conjure (5)
- # cursive (37)
- # data-science (1)
- # datalevin (4)
- # datomic (28)
- # eastwood (6)
- # fulcro (26)
- # graphql (20)
- # honeysql (6)
- # humbleui (4)
- # jobs-discuss (5)
- # kaocha (1)
- # leiningen (8)
- # missionary (5)
- # nbb (4)
- # observability (3)
- # off-topic (7)
- # pathom (8)
- # podcasts-discuss (1)
- # rewrite-clj (18)
- # ring (6)
- # sci (23)
- # scittle (9)
- # shadow-cljs (49)
- # squint (10)
- # testing (11)
- # xtdb (17)
Is anybody using GitHub actions to run their compiled cljs tests? I am trying to figure out the best way to set up a test runner that first compiles the code and then runs the tests with node.
If anybody is looking for a solution in future I got GitHub actions CI running the shadow-cljs tests: https://github.com/chr15m/sitefox/blob/main/.github/workflows/tests.yml
Any folks here who have experience with both use of JavaScript promises and also re-frame? With promises I was used to being able to chain several HTTP calls that depend on each other being completed in order using promises. For example, I might want to wait to fetch resources X, Y, and Z before using their return values to place an HTTP request to resource A. I might use Promise.all() to wait for resources X, Y, and Z to be fetched: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all While learning re-frame I saw that dispatch-sync could be used to dispatch events synchronously and I thought that perhaps I could use this function to replace my previous usage of promises. Turns out that the documentation generally discourages using dispatch-sync except in specific cases. How do you program your re-frame app so that you can enforce that certains groups HTTP calls complete in a specific sequence?
With promises, you don't wait, you never wait. You schedule work. "Do this, .then
do that, .then
something else."
In re-frame, there's nothing built-in. But there are at least 2 libraries that provide an effect for HTTP requests. Using their code as an inspiration, you can easily write your own effect that support issuing multiple HTTP requests and then dispatching a new event once all those requests are completed.
So it would be something like
(rf/reg-fx ::fetch-all ...)
(rf/reg-event-fx ::button-clicked
(fn [_ _]
{::fetch-all {:urls [url-x, url-y, url-z], :on-success [::-then-fetch-a], ...}}))
(rf/reg-event-fx ::-then-fetch-a
(fn [_ [_ result-x result-y result-z]]
{::fetch-all {:urls [url-a], ...}}))
As an alternative, you can use one of those 2 aforementioned libraries to just issue the requests, and the waiting for all 3 resources will be done at the re-frame level. But it's a messy solution where you have to count successful requests in your app-db.@U03SF5VUF6Y, in my opinion this is a bit of a limitation on the re-frame framework. I think the best you can do is stuff this into a reg-fx
. I can be more specific if you like. Also you may want to consider taking advantage of some clojurescript constructs for concurrency instead of promises. I realize what you asked for was a re-frame specific answer -- this is not that. I have had some luck in attempting to get https://github.com/day8/re-frame-http-fx to do the job, but you will be forcing the issue to some extent.
Thanks p-himik and unbalanced. I will take a look at using reg-fx. I was hoping for something that would let me compose dispatch
calls together in a way similar to how I am used to working with promises rather than having to create a specific handler for each case where I want to ensure that I can schedule some asynchronous work and then only schedule additional work once the previous set of tasks has completed.
Yeah, not possible since dispatch
has no return value.
You're supposed to compose actions from within (either by hard-coding stuff or by adjusting relevant event handlers so they accept follow-up event vectors), not from the outside (like it's done with promises).
There is a way to compose actions from the outside by basically peeking into re-frame's machinery. But I wouldn't recommend even looking in that direction.
@U03SF5VUF6Y the other part of the puzzle is using this library: https://github.com/day8/re-frame-async-flow-fx this will accomplish "wait until I've seen dispatches X
, Y
, and Z
before performing action Foo
. It's not perfect because you also need to specify X-fail|Y-fail|Z-fail
and then Foo-fail
. This also doesn't exactly easily cover usecases like pagination. However, it is a coherent framework for colating data from known, non-dynamic sources.
And personally at this point is when I stopped using re-frame altogether and just switch to reagent + core.async.
That library is exactly what I was referring to in the "wouldn't recommend even looking in that direction". :D
hahahaha
By searching for its name in #re-frame you can find plenty of discussions and descriptions of good reasons not to use it.
Cool, taking a look at core.async now.
If you end up going down that route, Eric Normand has several really friendly video courses on it.
That being said -- core.async is not as friendly to debug as promises.
I will tell you that the punchline is,
1. do some async work with core.async
2. update your re-frame.db/app-db
or some reagent/atom
with the result of the core.async work.
3. your page will update accordingly.
Updating the page state is your "exit plan" from core.async, otherwise you'll be like, how do I ever get out of this? Because one you start down the core.async pathway it seems like everything needs to be a core.async function and you don't really want that. want to work in sync-land as much as possible. /opinion
Good thread. Can it be moved/linked to #re-frame?
@U032EFN0T33, a link to this thread has been shared to #re-frame Thanks!
Curious if anyone has figured out a good solution for dynamic runtime dependency injection in ClojureScript that allows for extensible polymorphic behavior. Consider this scenario:
(defn save-doc! [doc]
(if (implements? proto.doc-manager/IDoc doc)
(proto.doc-manager/-save-doc! doc)
(throw (js/Error. (str "Doesn't support IDoc: " doc)))))
where
(defprotocol IDoc
(-save-doc! [this] [manager parent]))
and each doc can implement
(defrecord SomeDoc [doc-data]
IDoc
(-save-doc! [this]
...))
buuuuttt... this behavior should be different if it's in a web worker or the main thread.
So the classic way to do this is
(defrecord SomeDoc [doc-data save-doc-manager]
IDoc
(-save-doc! [this]
(-save-doc! save-doc-manager doc-data)))
then at runtime based on feature detection you can reify
the correct save-doc-manager
. However, extrapolated, this amount of code repetition for various doc types gets absolutely obnoxious when you have lots of different attributes.
In Clojure, the extend
function does wonders because you can do this programmatically. However, in ClojureScript we don't have this available so it's pretty tricky, even with macros.
Curious if anyone has a clean solution for this?the problem is that there are no interfaces in JavaScript so covering a bunch of types is not really practical.
if you have a bunch of docs types, one trick that might work is to set the prototype on a record w/ no methods.
then you could modify the prototype at runtime and all those types would get the change you need
No interfaces in javascript :scrunched-up-face: whatttt
yeah taking a second to wrap my head around that technique
Modifying the prototype doesn't allow you to get "abstract base class"-like behavior, though, right? Only modify the signatures? Still no "default" behavior, I'm assuming?
if you’re just looking for default behavior you can use extend-type default
then in the implementation check what you need to check and call the right implementation
(defprotocol IFoo
(foo [this] [this that]))
(let [foo-do (reify IFoo (foo [this] "this"))]
(println (foo foo-do))
(println (foo (specify! foo-do IFoo (foo [this] "that"))))
(println (foo foo-do)))
this is the idea right?
and I did end up going the extend-type
route in the current implementation, it's still a little messy.
(defmacro extend-types [types & specs]
(let [impls (parse-impls specs)]
(list* 'do
(map
(fn [protocol]
(let [spec (get impls protocol)
next-specs (list* 'extend-protocol protocol
(mapcat
(fn [type]
(conj spec type))
types))]
next-specs))
(keys impls)))))
and then using that to extend a lot of the behavior. Not perfect but does save some boiler-plate.I actually considered doing something insane like loading up all the code into a self-hosted interpreter in a webworker and shipping over extend-type code literals, getting back the resulting code as a string, and then dynamically loading it into the calling thread via script but at that point I assumed I was starting to lose my mind
(extend-type default IFoo (foo … (if (satisfies IDoc? x) (specify! (.-prototype x) IDefault …) …)))
ohhhhhhh very clever
Okay so, if I were to draw this out into a more complete example, something like this (?):
(defprotocol IDoc
(save-doc! [this]))
(defrecord Doc [])
(defmulti specify-runtime-behavior! :runtime)
(defmethod specify-runtime-behavior! :main
[runtime-opts]
(specify! (.-prototype Doc)
IDoc
(save-doc! [this] "sent to webworker!")))
(defmethod specify-runtime-behavior! :webworker
[runtime-opts]
(specify! (.-prototype Doc)
IDoc
(save-doc! [this] "saved to database!")))
(defn -main []
(let [runtime-opts (detect-features!)]
(specify-runtime-behavior! runtime-opts)))
Wasn't totally following the "configure on first call" technique but I'm loving the specify->protocol technique.word, thanks!
(and I'm pretty sure that's by design because I know inheritance is the 👿 and everything)
Could you please use a 🧵 ? The yesterday's discussion went for well over an hour and spans multiple screens - hard to participate in channels where it happens.
I'm doing the React tutorial and I suspect I'm doing something dumb with repeatedly
but I cannot get these click handlers to work
(defn square []
(let [state (r/atom "")]
[:button {:class-name "square"
:on-click #(reset! state "X")}
@state]))
(defn board []
(let [status (r/atom "Next player: X")
render-square (fn [] (square))]
[:div
[:div {:class-name "status"} @status]
(doall (map (fn [_] [:div {:class-name "board-row"}
(repeatedly 3 render-square)]) (range 1 4)))]))
Otherwise, your state gets erased on every re-render of those components.
Use a form-2 or -3 component, or reagent.core/with-let
.
Hi again! To follow up on my recent post (and hopefully to provide more context/useful information this time 😛 )...
It's no longer possible to develop Chrome extensions in ClojureScript (suitable for publishing). At least, the wonderful shadow-cljs + https://github.com/binaryage/chromex/tree/master/examples/shadow combo no longer works, as the https://github.com/thheller/shadow-cljs/blob/49fb078b834e64f63122e3a8ad3ddcd1f4485969/src/main/shadow/build/targets/chrome_extension.clj build code that https://github.com/binaryage/chromex/blob/27609a7025466d8b9bfbb28baa061ea8f51fb807/examples/shadow/shadow-cljs.edn#L4 generates Manifest V2 extensions only (is my understanding?), and https://developer.chrome.com/docs/extensions/mv2/publish_app/.
Fair enough... as Chrome extensions were always a pretty niche use case and building/packaging them is an undocumented feature of shadow. Still, it would be amazing to be able to continue developing Chrome extensions in cljs!
At https://nette.io/ we've been working on an extension that gives you fancy bookmarking, keeps track of research trails as you surf the web, lets you capture media, etc—it’s pretty cool 😉—integrated with the main Nette app, so porting it to plain JavaScript (say) would be a not entirely trivial thing.
We’re now looking into publishing to the Web Store, so we're trying to tackle the migration from V2 -> V3. None of us are browser extension experts — so we'd be really grateful for help from people that know a bit more about:
• Chrome extensions in general, and
• shadow('s building of them) in particular!
I put together a minimal, rather hacky example of kinda sorta migrating the https://github.com/binaryage/chromex/tree/master/examples/shadow using a build hook to patch the generated manifest.json
... plus a few more tweaks. The README lists the changes made and why, as well as a few questions that came up along the way. (And each change is also implicitly a question: 'Is there a better way to do this?' 😛 ) You can find it here if you're interested: https://github.com/nette-io/hacky-chromex-shadow-manifest-v2-to-v3-migration.
It seems to us that https://github.com/thheller/shadow-cljs/blob/49fb078b834e64f63122e3a8ad3ddcd1f4485969/src/main/shadow/build/targets/chrome_extension.clj is where the relevant magic happens? In which case a patch there would mostly solve things, maybe. Unless we've missed something?
Cc: @U051V5LLP, you might be interested in these, uh, developments since the last thread. :))
("my recent post"/"the last thread": https://clojurians.slack.com/archives/C03S1L9DN/p1662560058268889)
And of course, cc: @U05224H0W and @U08E3BBST! 🙂
if you want changes in shadow-cljs please take this to a shadow-cljs issue. and ideally just make a list of changes that need to happen. I can easily add them, I just don't have the time to figure out what those changes are.
if you are considering writing a patch please create a new target as a copy of the old chrome-extension. this is likely going to break stuff for v2, just in case anyone is still using that
Is #scittle an option?
Hmm, alrighty! Cheers @U05224H0W... a list of changes or a patch/new target incoming. Once we've worked through all the details of our own migration. :))
> Is #scittle an option? Say more @U8VE0UBBR... :thinking_face: My experience with Scittle is pretty minimal. Can you require cljs libs outside of the https://babashka.org/scittle/?
@U018D6NKRA4 - no, I'm afraid not. I missed that this was your requirement
Ah, no worries @U8VE0UBBR :))
Does anyone understand the trick being employed when this happens?
(ns foo.core
(:require some-npm-dep))
;; how can a namespace also be an object??
(println (some-npm-dep. "new object"))
This ties into the code splitting conversation from yesterday -- basically I'm trying to replicate that behavior with a clojurescript namespace.
I've moved all of my npm deps into external dependencies and setup externs appropriately.
Now I'm loading, let's say, react
, via
// src/js/components/react-deps.js
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Holdable, defineHold } from 'react-touch';
window.ReactTouch = {};
window.ReactTouch.Holdable = Holdable;
window.ReactTouch.defineHold = defineHold;
which gets compiled via webpack and loaded in index.html
via
<script src="js/compiled/dist/react_deps.bundle.js" type="text/javascript"></script>
I've then provided
(ns cljsjs.react)
(def react js/React))
which allows reagent
to be satisfied when it asks for react
here: https://github.com/reagent-project/reagent/blob/master/src/reagent/core.cljs#L4
However, the behavior does not quite line up as it does with true cljsjs
files, since react is not an object. When I tried to do the same trick with pouchdb
,
Use of undeclared Var app.lib.data.pouch/pouchdb
6 pouchdb ))
7
8
9
10 (defn make-local-pouch []
11 (pouchdb. "local" #js {:skip_setup true
^---
12 :adapter "memory"}))
13
14 #_(.. pouchdb
15 (plugin (.. js/window -pmemory))
16 (plugin (.. js/window -auth -default))
(again, the point of all this rigamaroll is trying to get the cljs_base.js
size appropriately small using the appropriate tools to split out the npm deps)
Because this almost lets me get the same compiler benefits as compiling the npm deps directly in advanced compilation -- I just need to figure out how to get a clojurescript namespace to behave as an object and I will not have to change any of my existing code in order to satisfy the new build requirements (yay!).
Of course if I do have to make some code changes that's not the end of the world.(technique lifted and extrapolated from this comment: https://cljdoc.org/d/reagent/reagent/1.1.1/doc/frequently-asked-questions/reagent-doesn-t-work-after-updating-dependencies)
also I swear this is my last question