Fork me on GitHub
#hyperfiddle
<
2023-02-16
>
reedho03:02:12

Hi All, long time no see, congrotulation for the release. ❤️

🙂 4
reedho03:02:40

I'm more on frontend currently but been watching this since months. Now that it is in public, i want to ask how to use tailwindcss for styling the ui?

Geoffrey Gaillard07:02:13

Install tailwind (follow their readme) Then add classes to your dom elements: (dom/div (dom/props {::dom/class ["bg-teal"]}))

reedho08:02:21

Latest i try to configure for its jit feature so e.g. only used classes sent to browser but still not satisfied with it. Hooking with shadow-cljs build pipeline would be awesome. Has some one put ground works on this matter?

Geoffrey Gaillard08:02:34

You want to use its JIT feature to scan your cljc code? To prune unused classes? What do you mean “only sent to the browser”? Do you also use tailwind classes on the server?

reedho08:02:23

Yes, i meant for former one, a working good example of tailwind.config.js

reedho08:02:07

About only sent to browser, i mean for development and production it works as exactly as how it works with node react js (e.g. vite) project.

Geoffrey Gaillard08:02:59

Understood. It should just work out of the box. A basic tailwind config would look like:

module.exports = {
  content: ["./src/**/*.{clj, cljs, cljc}"]
}
For a prod build you would scan the output javascript file rather than your sources. While it’s possible, it’s not clear if it’s Electric Clojure’s job to run the tailwind executable, like Remix does. What do you think?

reedho11:02:42

I'll retry the things again soon and will show more the findings.

teodorlu16:02:48

Hi! Electric is interesting. I'm trying to evaluate this from a REPL:

(e/client 123)
;; clojure.lang.ExceptionInfo: Invalid p/client in Clojure code block (use from Electric code only)
;; ...
Is there a quick way I can evaluate electric code from a REPL? (Sorry if this is obvious from some docs I haven't read yet)

Dustin Getz16:02:10

you need to use RCF

👀 2
Dustin Getz16:02:51

see photon_test.cljc

👍 2
Dustin Getz16:02:27

the client/server macros require you to have a clojurescript browser repl working, which is tricky (and i actually think we might have broken that recently, i am not sure if our tests pass in the cljs repl right now)

Dustin Getz16:02:21

Actually no - there is a special test entrypoint for Electric Clojure that has loopback client/server configuration, photon_test.cljc uses this iirc

teodorlu16:02:03

Does that mean hyperfiddle/rcf provides "magic/helpers/context" required to "choose where to send the expression for evaluation"? Whereas when I simply evaluate expressions, I don't provide that context?

Dustin Getz16:02:42

Electric is async, so you need async REPL helpers

👍 2
teodorlu16:02:14

Amazing, thanks. That gives me plenty to explore!

Dustin Getz16:02:21

e/run (p/run) is how you run an Electric expression at the repl, since the program is reactive it will stay running, until you kill the program through the dispose callback

teodorlu16:02:17

ah, right. I was wondering about that part. Why I needed all the other stuff apart from p/run.

Dustin Getz17:02:02

It works naked, rcf/tap becomes println outside of (tests)

Dustin Getz17:02:06

if you forget to dispose it's mostly harmless in dev

👍 2
Dustin Getz17:02:51

note the println 1 2 3

Dustin Getz17:02:55

in response to the swap!

teodorlu17:02:10

so here you are allowed to

(e/def x (e/watch !x))
outside of an async context because x itself becomes an async thing (signal?), but when you actually want to do something with it (print it), you need to provide an async context for that printing with p/run,
(p/run (println x))
?

Dustin Getz17:02:57

nothing actually runs until you call e/run

👍 2
Dustin Getz17:02:56

if you browse into the source, e/run essentially calls the Electric compiler and then boots the reactive program

👍 2
Dustin Getz17:02:12

e/def today doesn't actually do anything; it saves off the quoted program (s-exprs) as metadata for the compiler to deal with later when e/run is called

👍 2
Dustin Getz17:02:42

that will change, but helpful for now to understand for your mental model

👍 4
teodorlu17:02:46

ok, that makes sense. I'm probably asking lots of stupid questions here, never really worked with async code. One more question if you don't mind.

(def dispose (e/run (rcf/tap (e/client 123123))))
;; prints 123123

(def dispose (e/run (e/client (js/alert "stuff is happening"))))
;; Syntax error compiling at (src/shadow/user.cljc:13:14).
;; No such namespace: js
Can you help me understand why the second form is failing? I thought since I was able to evaluate client code for the first form, I should be able to for the second form too?

teodorlu17:02:52

to try to answer my own question, I'm guessing I'm missing setup. I've been reading the example app, which https://github.com/hyperfiddle/electric-starter-app/blob/b347af77b7c32788acd117777f9d0a1e256d875e/src/user.cljs#L7-L22.

xificurC17:02:06

e/run runs both the server and client parts on the same peer, in this case you're starting it from the clj REPL so on the JVM

teodorlu17:02:33

Right. I'm never giving it any information about a clojurescript environment.

teodorlu17:02:08

So I don't really want e/run if I want this example to run.

xificurC17:02:16

What you linked runs e/boot, which does the setup

👀 2
xificurC17:02:15

Well, you can e/run in the cljs repl to try js code

👍 2
xificurC17:02:20

You can also build a small entrypoint and let shadow recompile on save. You get similar instant feedback

👍 2
teodorlu17:02:54

I'm using the electric-starter-app example, and I already have recompiles on save. Is it possible for me to evaluate things in async environment setup by user.cljs? I see that there's a var reactor, can I use that?

xificurC17:02:35

Electric compiles your whole programs, there's no way to "poke holes" into it. Can you give an example of what you'd like to evaluate?

teodorlu17:02:19

I'd like to write (js/alert 123) and see that happen in a browser.

teodorlu17:02:44

this is really helpful for me, by the way, I think I'm getting closer to what I'm not understanding. > Electric compiles your whole programs, there's no way to "poke holes" into it. I suppose this is what I'm not understanding.

teodorlu17:02:17

Wait I have to make some change that gets picked up by the electric recompilation.

teodorlu17:02:48

there's otherwise no way to get information into the frontend. Does that sound right?

xificurC17:02:01

OK, your example is standard cljs code, so all you need for that to evaluate is a cljs repl

xificurC17:02:38

If you add the js/alert into your app then yes, the program will recompile and you'll see the alert

xificurC17:02:26

By whole programs I mean that electric does full program analysis. Evaluating (e/defn ...) in the REPL doesn't do much, it just saves the sexp for the compiler to analyze it later. So evaling an e/defn in the REPL won't change your running program, you have to save the file for the app to recompile. Does that make sense?

teodorlu17:02:28

yes, I think so. I assumed it worked more like a clojure.core/defn, redefining something tangible I could "just touch"

teodorlu17:02:42

I think I'm getting "hit by" not really getting when code is being run compile time, (like the e/defn) and when code is executed runtime (like (dom/on "keydown" (e/fn [e] ,,,))). Because inside the keydown event function I can add code that gets run on the frontend.

teodorlu18:02:52

Really appreciate you taking the time to explain, Peter and Dustin 🙌

🙂 2
xificurC18:02:38

Standard clojure rules apply otherwise, the electric function will run when you call it. A keydown handler will run when you press a key ;)

👍 2
Dustin Getz18:02:46

^:dev/always is a shadow-cljs directive to rebuild this file whenever shadow runs

👍 2
Dustin Getz18:02:30

L8, electric/boot is the compiler

👍 2
Dustin Getz18:02:10

@U3X7174KS you want to do a zoom call? Any of us are happy to give you a jump start. It also helps us figure out what FAQs people have and know what docs to write next

teodorlu22:02:39

I appreciate the offer, but a bit of async Q/A was exactly what I needed right now. Plus, it's soon midnight here in GMT+1!

teodorlu22:02:42

On docs: I think the talks you've given are a great motivation for why electric needs to exist. I want this to succeed. But then I'm asking myself ... just how can I use this to do something specific, where choosing Electic makes sense? Something like the https://reagent-project.github.io/ or the https://day8.github.io/re-frame/re-frame/, where I'm guided through tasks that get gradually more complex, and there's motivation for why I would want to do the things that appear in the guide. Perhaps that could tackle some of the questions I had above. What does it mean that it's a compiler? That it's all async? How does that give Electric leverage, and what kinds of limitations does it bring? (my kneejerk reaction right now, I might be wrong!)

👀 2
Dustin Getz23:02:13

ty for the feedback!

Dustin Getz23:02:46

Have you looked at the demos in the main repo?

Dustin Getz23:02:30

in main repo, run clj -A:dev -X user/main , demo code is in https://github.com/hyperfiddle/electric/tree/master/src-docs/user

👀 2
xificurC09:02:59

The reagent intro ends with todomvc, you can compare that to https://github.com/hyperfiddle/electric/blob/master/src-docs/user/demo_5_todomvc.cljc. Disregarding empty lines and diagnostics they come out largely the same LOC. So what's different? • less moving parts. No useEffect, useRef, ratoms. Larger codebases would also start using useState, useMemo, useContext and whatnot. Electric is just reactive clojure, not much new to learn. • the electric version has realtime sync. Open 2 tabs and see a change in 1 tab show up in the other. No additional code required. Now think how would you extend the reagent demo to do the same ◦ write the backend part separately ◦ pick a transport protocol ◦ update the reagent code to handle changes flowing in from the backend ▪︎ and this wouldn't be the same pretty code because now you need message handlers that mutate your UI or state. All of this could easily triple the code size and introduce so much coordination you now need to write, debug and maintain yourself! The toll on your reasoning of the code is huge too, because now you have to keep all these other things in your head when making a change.

👍 4
teodorlu15:02:41

As of 967f03df, I appear to be getting OutOfMemoryError Java heap space when running clj -A:dev -X user/main. Linux on a 16 GB thinkpad laptop. Happy to provide more details.

teodorlu15:02:07

It appears to be working fine when I set -Xmx1G.

:jvm-opts    ["-Xss2m"
              "-Xmx1G"
              ...
              ]

teodorlu16:02:00

> Have you looked at the demos in the main repo? I hadn't before you mentioned them. I've spent a bit of time with them now, and it's helpful. I find user.demo-4-webview especially interesting, model, view and controller on one page, all reactive.

👍 4
Dustin Getz16:02:59

@U3X7174KS I believe that's a compile-time error, -Xss2m fixes it for us (the compiler uses a lot of stack - presumably will be less when we optimize it soon). Please confirm that -Xmx1g is needed as well? That I have not seen before

teodorlu17:02:40

@U09K620SG this is weird - I'm seeing a bit of different behavior. Observations: 1. When I've already been able to compile the app once, I'm not able to reproduce any compile errors. But when I clj -A:dev -X user/main after a clean git clone or after I've removed untracked files (`git clean -fxd`), I see errors. 2. But after git clean -fxd, I see errors. (I need to run git clean -fxd between each time I test). a. Neither opt: StackoverflowError. b. Only -Xss2m: OutOfMemoryError Java heap space c. Only -Xmx1G: Working d. Both -Xss2m and -Xmx1G: Working. So on my system, -Xss2m doesn't appear to have any effect other than giving me different errors, whereas -Xmx1G appears to solve eveything.

👀 2
Dustin Getz17:02:10

Thanks, we will discuss as a team - please keep me posted if you encounter more issues

👍 2
pez22:02:45

Trying to follow along in this thread about how to use the REPL. Not quite getting it yet, but will keep trying. 😃 Btw, the link to photon_1_lang,clj above doesn't work. It's renamed: https://github.com/hyperfiddle/electric/blob/master/src-docs/user/electric/electric_1_lang.cljc (For anyone else finding this thread).

🙏 1
Dustin Getz22:02:13

@U0ETXRFEW i have a moment now do u want to zoom?

pez22:02:05

Does a huddle here work for you? I can zoom too, but just easier with huddle, and anyone can join easier.

vincent17:02:14

okay we kinda have to rethink our options now with multiplayer canvas as a stock component 😼

🙂 2
vincent17:02:12

I notice in the todo app we have TodoCreate

(e/defn TodoCreate []
  (e/client
    (InputSubmit. (e/fn [v]
                    (e/server
                      (e/discard
                        (d/transact! !conn [{:task/description v
                                             :task/status :active}])))))))
e/server makes sense and so does e/transact but why e/discard

xificurC18:02:28

The value from the e/server gets transferred to the client and the return value of the transaction is not serializable. e/discard just returns nil

xificurC18:02:51

Instead of discard you could write (e/server (d/transact! ...) nil)

vincent18:02:02

oh cool, so i kinda understnd -- do you mean serializable like if i wanted to inline the result of the call

Dustin Getz19:02:24

(d/transact! ...) returns like a connection or something as the return value, which can't cross from server to client

Dustin Getz19:02:36

you'll get a warning in the console "unserializable reference error" - it is harmless

Dustin Getz19:02:03

returning nil will send nil instead of trying (and failing) to send the reference, resolving the warning

Dustin Getz19:02:55

for example, (e/client (println (e/server (type 1)))) will try to send a java Class type to the client and print it at the browser console, which will fail to serialize and you'll get an "unserializable reference transfer" warning

vincent19:02:11

okay thanks, didnt mean to melt brain 😅 i appreciate the explanation

😁 2
vincent19:02:49

will have to think about it some more

xificurC20:02:33

no problem 🙂 Standard clojure evaluation rules apply, so (e/server 10) returns 10 to the client. In order to "return" it has to cross a wire in standard web app setup, so the value gets serialized (with transit). This means unserializable data is problematic, e.g. (e/server (Object.)). In this case you get a warning and the block will return nil. To signal you don't care about a return value you can use e/discard, which will return nil without the warning.

👍 6
🍪 2
vincent17:02:28

d/transact*

Max23:02:01

Someone in my network noted the similarities between Electric Clojure and http://opalang.org/. Was there any inspiration from that project?

👀 2
Geoffrey Gaillard07:02:26

We designed electric from first principles. We've seen Opa a long time ago IIRC. Electric is fundamentally different. We've seen other languages with client and server markers, but as far as we can tell, they are all doing RPC or message passing. Electric is based on a functional effect system.

xificurC08:02:16

looking at their https://github.com/MLstate/opalang/wiki/Hello%2C-chat they use a Network to broadcast messages between the clients. The clients then use an "on-message" callback to handle incoming messages. This is done in user_updatethat directly manipulates the DOM. Compare to our https://github.com/hyperfiddle/electric/blob/master/src-docs/user/demo_4_chat.cljc where we imitate a DB with an atom (could have used e.g. datascript as well) and attach a single impure operation to the inupt - transact on enter. Notice there's 0 ceremony around this, the app is declared and reactivity takes care of fulfilling that declaration. The execution model is different (messages vs. reactivity) and therefore the code is written differently as well (impure callbacks vs. pure reactive code).

2
Dustin Getz16:02:27

AFAIK none in that list have the key streaming network planner aspect, which is the critical thing that makes it work, but maybe am missing something, we didn't look too closely