Fork me on GitHub
#clojurescript
<
2019-05-27
>
Roman Liutikov08:05:28

(js/web3.eth.Contract. param1 param2)

rickmoynihan09:05:46

Does anyone here know how reacts DomServer works? It looks to me like it just walks the component tree and renders them as html elements. Does it have any special behaviours beyond this though, that would be hard to recreate? In particular does it do anything funky like render special attributes into the DOM of components? The background is that I’m implementing SSR from clojure code that serialises a component DOM from reagent flavoured hiccup, and I’m hydrating those components client side with reagent. It appears to me to work for my simple case, but a colleague is concerned that it might not work on more complex cases… mainly based on the assertion that react DOM server is a lot of code. I’ve also checked in chromes web developer tools, and it looks like when I rehydrate my component client side there is no superfluous DOM render made… i.e. it looks to me like providing the component state is the same as it was on the server that it diffs the DOM properly and doesn’t cause a re-render.

dominicm09:05:37

I think it might add some special attributes to tell react something with ids

rickmoynihan09:05:10

Yeah… I borrowed my SSR rendering code from here: https://yogthos.net/posts/2015-11-24-Serverside-Reagent.html and it seems to work with reagent and a recent react.

rickmoynihan09:05:29

it puts a data-reactid into each component

dominicm09:05:49

Ah right :)

rickmoynihan09:05:05

though I don’t think that’s necessary these days

rickmoynihan09:05:19

I’m cautious of rum because it’s on an ancient version of react. And I think react has itself changed to do less over time here… but tracing the history, and what it actually does at any point is quite hard.

Roman Liutikov09:05:27

React’s SSR haven’t changed much as well, Rum produces HTML string that is fully compatible with react-dom/server output

Roman Liutikov09:05:25

Note that here I’m talking only about serialization, you’ll still have to take care of proper execution of lifecycle methods etc

Roman Liutikov09:05:45

This one produces HTML string which is incompatible with newer React versions, React’s SSR no longer requires data-reactid attributes

rickmoynihan09:05:16

Yeah I think data-reactid is no longer required… My question is really what is required, and in what way is the above not compatible? As it seems to work for my simple example.

Roman Liutikov09:05:26

From brief overview it doesn’t seem to call componentWillMount which should be called when rendering on server

rickmoynihan09:05:15

why is that important? I shouldn’t need to care about what is called on the server; only that I generate the right output, no?

rickmoynihan09:05:16

(my knowledge of reactjs is tiny though)

Roman Liutikov09:05:57

you might have data fetching in componentWillMount or basically any side effect that might be called when component is about to render

rickmoynihan09:05:58

it looks like componentWillMount is deprecated

Roman Liutikov09:05:02

Reagent is still using it

Roman Liutikov09:05:45

And it’s not really deprecated , it’s considered to be unsafe in concurrent mode. Reagent won’t work in that new rendering mode anyway since it’s relying on multiple unsafe APIs

rickmoynihan09:05:45

hmmm interesting 👀

rickmoynihan09:05:29

ok, backing up a bit… requirements wise my ideal solution would be to: 1. Have a way to do SSR rendering of cljc components rendered via a clojure backend. 2. Allow selective clientside interop with pure reactjs components… accepting that these component trees will not be SSRable without stubbing the js components. I’m thinking in the few cases we’d do this, this could be done with spinners etc. 3. It would be useful to run a recent react version. 4. For interop reasons it would be convenient to be as close to react as possible, using the new react hooks API ( https://juxt.pro/blog/posts/react-hooks-raw.html ) where possible; but reagentified hiccup instead of jsx in cljc components h/t @dominicm

rickmoynihan09:05:21

So I’m wondering if I’m asking the impossible; it feels like it should be doable.

rickmoynihan10:05:01

So looking at the above some more, would I not just need a way to stub hooks with mocks/no-ops on the server side? Excepting perhaps an implementation of useState, that returns the initial state?

cgrand15:05:04

Hmmm, ok I’ve got this weird behavior:

=> (let [a (to-array [:original])
         v (apply vector a)]
     (aset a 0 :modified)
     (= :original (nth v 0)))
false
apply vector when passed an array behaves like vec and uses the array as the PV backing array.

🪲 8
cgrand15:05:48

In Clojure, this returns true

rickmoynihan15:05:26

wow that is weird

rickmoynihan15:05:48

v is not so immutable after all

lilactown15:05:56

@rickmoynihan the rum library has a CLJ hiccup-to-html-string feature that is compatible with React’s SSR

lilactown15:05:25

I heard they have tests that compare it to React/renderToString

lilactown15:05:55

hooks throws a bit of a wrench, you have to decide how to handle that kind of stuff

rickmoynihan15:05:22

@lilactown: you mean for SSR in clj?

rickmoynihan15:05:45

yeah — I’ve been looking into that

rickmoynihan15:05:11

do you not just need to shim useState, and essentially no-op the other hooks?

lilactown15:05:38

well, maybe 😅 I haven’t thought about it very hard

lilactown15:05:48

but it might be useful to fire effects etc. while SSR. who knows

rickmoynihan15:05:03

hehe me neither… only just get my head into react

rickmoynihan15:05:53

what kind of effects would you fire SSR? The context is pretty different in the server, is it not?

lilactown15:05:26

hmm. not sure

lilactown15:05:01

the other thing I’m thinking is, if we don’t want hooks to be used in Clojure then maybe it’s better to not stub them

rickmoynihan15:05:57

yeah definitely… I was thinking the same, that most if there is no serverside analog would be stubbed out by a reader conditional :thumbsup:

lilactown15:05:18

the great benefit of CLJC is reader conditionals. perhaps it’s better to be explicit?

lilactown15:05:12

one of the downsides I’ve experienced using Node.js for SSR is things sometimes work, sometimes not depending if you’re on the server vs browser which can confuse people

rickmoynihan15:05:09

@lilactown: you’re the author of hx right? What changes would be needed to it to make it work with ssr?

lilactown15:05:44

Just need to write the interpreter

lilactown15:05:20

honestly it’s been on my plate long enough and people have asked for it, I should just do it

lilactown15:05:49

at first I was resistant but I’ve come around to it

lilactown15:05:11

@roman01la convinced me 😜

lilactown15:05:22

I’m just trying not to computer as much this week so might not get to it. if you’re interested in contributing what you’re doing to hx, I’d be interested in reviewing it!

rickmoynihan16:05:24

Well I might be interested — but I know next to nothing about react or the clojurescript ecosystem around it

rickmoynihan16:05:39

so just trying to figure it out

rickmoynihan16:05:32

@lilactown when you say interpreter, what do you mean? An interpreter for writing dom nodes?

lilactown16:05:08

by interpreter I mean the thing that takes hiccup like [my-component {:style {:color “green”}} “foo”] and turns it into react-compatible HTML <div style=“color: green”>foo</div>

lilactown16:05:30

it interprets hiccup into HTML

rickmoynihan16:05:30

:thumbsup: yeah

rickmoynihan16:05:29

It feels very much like the lisp curse that every clj(s|c) react library has to pretty much write their own one of those

lilactown16:05:36

my original vision for hx was to be a general lib for doing hiccup conversion

rickmoynihan16:05:38

I’ve been looking at hicada today, and it turns out it doesn’t do the ssr case of this… and reagent implements one that is pure cljs only etc…

lilactown16:05:53

but that’s too great a feat for me :P and I ended up taking it in a different direction

lilactown16:05:51

everyone has their own little differences they want to implement, too

lilactown16:05:15

e.g. hx doesn’t support the [:div#some-id] or [:div.some-class] syntax

lilactown16:05:38

uix has a neat way of extending props processing to custom attributes, so you can implement things like a :css prop that will create a CSS class for you using a CSS-in-JS lib

rickmoynihan16:05:20

I’m not sure those small differences are the reasons people do it… it seems mainly like it’s because of fundamental issue’s with other libraries; but I could be wrong

lilactown16:05:59

I’m not sure either. it’s also just kind of fun

lilactown16:05:49

writing parsers / interpreters for something like hiccup is such a good programming problem in that it’s a fairly contained, understood problem

lilactown16:05:03

so, the Lisp curse 😂

rickmoynihan16:05:56

it’s fun to implement 🙂 but as a user there’s a paradox of choice — or worse, you pick up one assuming they’re more or less equivalent to each other… and then run into a limitation, like not running in clj

rickmoynihan16:05:15

Anyway do we not just need one of these that works well enough in all the appropriate contexts? It seems like if we had that we can just use raw react, and compose libraries together rather than build big frameworks that wrap react and fail to keep up with it.

lilactown16:05:58

yes, but inevitably people want more and there’s an ease to building it all into one library

lilactown16:05:36

like I said, hx started as just a hiccup library but I found it too annoying to use just functions as React components, so I wrote the defnc macro

rickmoynihan16:05:06

what does that macro do exactly?

dominicm16:05:10

In PHP, they run a node server and send code to it for doing server side rendering. I think you could do that in clojure using nashorn.

lilactown16:05:23

@dominicm I’ve thought about that too

lilactown16:05:55

haven’t experimented but I fear for the amount of complexity that involves 😬 but it’s an unfounded fear

rickmoynihan16:05:58

Yeah I’ve thought about it too; but I’m not sure it’s a good option.

rickmoynihan16:05:15

also nashorn is dead

dominicm22:05:50

J2V8 is probably fine too :)

rickmoynihan22:05:32

never tried it but JNI fills me with fear

dominicm16:05:26

J2V8 hides it

dominicm16:05:34

Never had problems with it

rickmoynihan16:05:36

so you’d probably need to use GraalVM

lilactown16:05:37

@rickmoynihan defnc basically takes your component like this:

(defnc MyComponent [{:keys [foo children]}]
  (if foo
    [:div “foo” children]
    [:div “not foo” children]))
and turns it into:
(defn MyComponent [props]
  (let [{:keys [foo children]} (props->clj props)]
    (hx.hiccup/parse (if foo
                       [:div “foo” children]
                       [:div “not foo” children]))))

lilactown16:05:55

it turns the React props object into a map, and parses your hiccup

lilactown16:05:59

that’s pretty much it

lilactown16:05:16

there’s some other little features but those are the primary concerns

rickmoynihan16:05:47

ok so nothing fancy — seems totally reasonable too

lilactown16:05:40

there are some opportunities tho and I have more ideas! 😂

lilactown16:05:09

for instance, Dan Abromov is working on some new hot loading features in React itself that a macro like defnc could plug into so that your component didn’t lose it’s state when your code is reloaded

rickmoynihan16:05:16

One thing that I think might be problematic from clojurescript, is not having the linter

rickmoynihan16:05:27

and being able to enforce usage on hooks

lilactown16:05:31

well, that’s another thing that the macro can do

rickmoynihan16:05:32

so perhaps a solution to that

rickmoynihan16:05:40

yes my point exactly

rickmoynihan16:05:59

though I think it’s the reverse

rickmoynihan16:05:10

i.e. I think you need to enforce that it’s not used outside of the macro

rickmoynihan16:05:31

but that should be doable too

lilactown16:05:46

eh, I think it’s better to let people fail there perhaps. but help people avoid putting hooks inside of a loop/`map`/etc.

lilactown16:05:04

or have an actual linter take care of that if we ever get the level of adoption necessary

lilactown16:05:51

there’s not a good way to enforce use of hooks outside a macro without making things really weird for interop

rickmoynihan16:05:25

I recon you could do it easily enough

lilactown16:05:44

what’s your idea?

rickmoynihan16:05:13

not sure yet… was thinking you could do something like 1. force all hooks to be wrapped with a special function that performs the check. 2. have the macro walk the syntax tree looking for symbols that ref vars which have that metadata on them, and have them pass a special arg to the wrapper that indicates the call is allowed. Though thinking about it this might not work… as cljs doesn’t have vars… but then clj does and clj implements the macro…. hmmm…

rickmoynihan16:05:21

eitherway I recon you do something

rickmoynihan16:05:01

one thing you can actually do is inspect the stack, and check the call is made from the right stackframe… i.e. that your macro is the immediate parent to the call.

rickmoynihan16:05:26

though that might be brittle

lilactown16:05:01

right, so React already does a lot of that

lilactown16:05:13

you just don’t get that at compile-time

lilactown17:05:34

in order to do it at compile-time, and ensure that all hooks are only called inside a defnc macro, you would have to do a pass on the entire app

lilactown17:05:38

which is basically a linter

lilactown17:05:02

you would have to inspect the entire call-graph of a hook function

lilactown17:05:32

because you also have to take care of custom hooks:

;; is this a hook, a component, or an error?
(defn foo []
  (useState “foo”))

rickmoynihan17:05:31

yeah was going to say… unless you never import hooks, and have your macro present them to the user as a binding, which you can enforce is never leaked. Though it wouldn’t work for custom ones.

rickmoynihan17:05:10

I recon it’s not worth it 🙂