Fork me on GitHub
#fulcro
<
2018-02-24
>
tony.kay05:02:33

So, I would appreciate a community cross-check here: I’m planning to add support to the DOM function to detect and auto-convert clj maps (so we can stop using #js…it’ll be optional optimization to use it). I’ve benchmarked the change, and it won’t hurt a thing. I’m trying to decide the answer to this: “Should I throw an exception if it isn’t nil, an object?, or a map??” I’ve never written code in Fulcro that would violate that assumption, but it might be technically possible to pass something to react that would work as props, but whose constructor isn’t js/Object

thheller08:02:11

@tony.kay you can just look at the argument in the macro. if its something it can identify as a map/js-object/nil it does the optimization in the macro. if not it falls back to "interpreted" mode and does the conversion at runtime. I do this in my react element wrapper. https://github.com/thheller/shadow/blob/master/src/main/shadow/markup/react.clj#L147-L164

thheller08:02:39

do (dom/h1 {} ...), (dom/h1 nil ...) and (dom/h1 #js {} ...) are all "fast" and (dom/h1 foo ...) is slightly slower

thheller08:02:58

should probably expand the checks to go the fast path for string?

tony.kay16:02:19

@thheller So, you're right that that would cut overhead further, but the actualy measured overhead of the cond compared to everything else is soooo small I don't see it as being worth it. It wasn't even measurable in a statistically significant sense when compared to the overhead, for example, of React's VDOM->DOM stage. I think we're over-optimizing

tony.kay16:02:53

and string is a legal thing to give props...not sure what you mean by fast path for string?

tony.kay16:02:27

The one I did think of last night was undefined...that should be explicitly checked for in js since there are ways to end up with that instead of nil

thheller16:02:57

@tony.kay the cond is done at compile time not runtime

thheller16:02:03

so it does make a diff at runtime

thheller16:02:26

its a macro writing a macro

tony.kay16:02:28

If the cond is structure like this:

(cond 
   (or (nil? props) (object? props)) props
   (map? props) (clj->js props)
   ...)
(order is important) then it turns into an if (at compile time) that first checks for the "cheap stuff". nil compare is very cheap

tony.kay16:02:39

as is object?

tony.kay16:02:50

like in the single microsecond range

tony.kay16:02:30

once you're to map? then you're doing a bit more, but you're about to do a conversion which is heavy anyhow, and you already took the overhead of the creation of a persistent data structure

tony.kay16:02:18

I do know that cond is a macro, and that nil? , object?, and map? are not. I've read the source. I've done a lot of critical optimization in my time 🙂

thheller16:02:37

no you misunderstand

thheller16:02:46

the code I linked is a macro

tony.kay16:02:58

No, I understood that

tony.kay16:02:08

you're saying you can take that overhead at compile time

tony.kay16:02:20

I disagree that it is worth the added code and complexity

tony.kay16:02:36

I think it is over-optimization in light of the measured facts

thheller16:02:37

I measured it and found a pretty high performance benefit

tony.kay16:02:50

compared to a React VDOM diff?

thheller16:02:31

(dom/div {:onClick foo} ...) vs. (dom/div #js {:onClick foo} ...)

currentoor18:02:09

@thheller this is a really cool optimization, thanks for sharing!

currentoor18:02:18

but doesn’t it need to be recursive?

currentoor18:02:08

for something like this

(dom/div 
  {:style {:backgroundColor "red"}} ...)

currentoor18:02:05

or JSValue already recursive?

thheller18:02:05

JSValue is not recursive no. when did react add support for style objects? I didn't know it did. only ever used strings.

currentoor18:02:05

we’ve been using it for inline styles for quite a while 😅

currentoor18:02:59

shouldn’t be pretty straightforward to make a function that walks the data structure and wraps JSValue where appropriate

currentoor18:02:21

or perhaps style is the only nested case?

thheller18:02:22

yeah should be easy to add

currentoor19:02:31

i read the source for JSValue and all i saw was (deftype JSValue [val])

currentoor19:02:45

how did you figure out what it did? trial and error?

thheller19:02:02

I wrote shadow-cljs so I'm pretty familiar with the cljs.analyzer/compiler internals

currentoor19:02:53

if you don’t mind me asking, why does shadow-cljs come with all this react/dom API stuff? i thought it was a build tool (with fancy npm integration) + code reloading

currentoor19:02:28

actually i see it has a bunch of other utilities, i’m guessing it’s just your “tool bag” of sorts

thheller19:02:37

it doesn't. shadow is just a library I wrote for my stuff. since I suck at naming things I just use the shadow prefix for all my stuff.

currentoor19:02:16

a lot of good stuff in here!

currentoor19:02:08

have you seen this?

thheller19:02:42

I have. very different though.

currentoor19:02:40

how so? syntax sure, but hey both allow you to write style logic inline but both generate CSS to be inserted into the dom right?

currentoor19:02:44

perhaps i’m missing something

thheller19:02:01

yes they both deal with css.

currentoor19:02:09

anyway shadow-cljs looks awesome, thanks for doing that! i look forward to learning more about it in the future :thumbsup:

tony.kay16:02:40

oh yes, absolutely

thheller16:02:50

so and thats whats the macro does. nothing more

thheller16:02:56

it turns the first into the second

tony.kay16:02:05

oh, I didn't see that

tony.kay16:02:28

I didn't realize you could do that at compile time 😄

tony.kay16:02:53

ok, well, that is a hell of a useful thing 🙂

thheller16:02:25

I sort of compromised on one things because of react v16 which is really picky about arrays in arguments

thheller16:02:53

but still the conversion to a JS object is done in the macro

thheller16:02:00

IF the first arugment is a map

thheller16:02:16

for react v16 compat I generation a JS array and then call React.createElement.apply(nil, the-array)

thheller16:02:39

since React.createElement("div", nil, some-array) caused warnings

thheller16:02:27

JSValue is the trick. it is what the #js reader literal generates

thheller16:02:43

and the compiler will then turn JSValue into plain JS instead of emitting a CLJS object (eg. map)

tony.kay16:02:05

That makes sense. I had seen those before but kinda assumed they were for compiler internal use only

thheller16:02:49

should be fine. I don't see them going anywhere.

thheller16:02:44

what I meant by string? is that currently (dom/h1 "foo") will not use the optimized path but should

tony.kay16:02:07

oh, I see. You're making the props map optional

thheller16:02:21

yeah, more hiccup-ish

tony.kay16:02:59

ok, perhaps I should steal your code 😉

thheller16:02:36

ok with me 🙂

tony.kay16:02:40

Thanks, I think this will make a big difference in people's perceptions. That #js thing is pretty annoying, as is peppering nil about as well. Like yours much better.

thheller16:02:13

oh yeah the #js thing is horrible

thheller16:02:54

I'm still not 100% happy with the solution though

thheller16:02:05

I was thinking about bypassing React.createElement completely

thheller16:02:27

similar to that but if breaks if anyone is still using string refs

thheller16:02:48

so React.createElement it is for now

tony.kay16:02:19

and that is behind the scenes, so easy to do later or in another ns

thheller16:02:23

and then we are probably entering over-optimizing territory 😉

thheller16:02:51

but what annoys me is the React.createElement copies the arguments we pass it

thheller16:02:52

so that is super wasteful in my mind and I don't like waste 😉

thheller16:02:03

anyways I left it there it works ok enough

thheller16:02:20

better than all other solutions I found

wilkerlucio16:02:53

nice @thheller !! thanks for that approach, sounds awesome :)

tony.kay16:02:59

Also have to make that all work with SSR, since this all has to evaluate in clj as well.

thheller16:02:28

I totally gave up on SSR. It just never works for me.

tony.kay16:02:17

I personally lean that way as well, but many people have it as a required feature, at least for the top-level pages. Fulcro makes that pretty tractable, actually (as in I was surprised how easy it was to get it functioning when I wrote something non-trivial with it).

thheller16:02:36

well, what happens if the user wants to use some react component? can't do that in CLJ?

tony.kay16:02:20

Two approaches: you don't need it to "work" on the server, you just need it to "look ok", so a simple wrapper that outputs static content in clj is suffucient

thheller16:02:21

JS react component, not defsc or defui

tony.kay16:02:42

the other approach is to use Nashorn, but that one kinda sucks in overhead

tony.kay16:02:50

I prefer the former

thheller16:02:10

I don't buy the whole SSR is faster argument either. every benchmark I saw complete disregarded load time on the server

tony.kay16:02:14

it's not perfect, but it's perfectly practical

tony.kay16:02:31

no, not faster...I don't care about that either. SEOis the gotta-have

tony.kay16:02:41

search engines have to see HTML, not just a blob of js

thheller16:02:50

thats also not true anymore

thheller16:02:00

google indexes JS just fine

thheller16:02:20

if you stick to the rules that is

tony.kay16:02:21

you sound like me a year ago 😜

tony.kay16:02:04

I used to argue with people about this the same way...

thheller16:02:07

I validated a bunch and the only thing I found to not work is structured data

currentoor18:02:41

@thheller what do you mean by structured data?

thheller18:02:50

the google bot for this does not seem to register things that are added by javascript

thheller18:02:01

only things contained in the initial html

tony.kay16:02:47

I had not personally validated that google is now able to index js-generated pages

thheller16:02:46

ok I didn't test that

tony.kay16:02:56

it's a big rabbit hole

tony.kay16:02:24

but as a library author I don't want to limit my audience due to lack of a feature that is perceived as "necessary"

tony.kay16:02:09

esp. since the code for it is already there and working 🙂

thheller16:02:14

yeah I can understand that

sundarj20:02:47

@tony.kay @thheller this guy's stuff is what convinced me SSR is an exceedingly good idea (i used to be of the 'just serve a <script> tag' group): https://rauchg.com/2014/7-principles-of-rich-web-applications#server-rendered-pages-are-not-optional

sundarj20:02:52

it's not about SEO for me

sundarj20:02:52

and nowadays we have H2 push, which makes the argument stronger

tony.kay20:02:18

Thanks @sundarj. I agree that the speed is improved by SSR, by quite a bit in some cases, and my earlier recommend of using placeholders (shells in your article) for things that cannot be SSR’d is exactly the level of SSR I shoot for when doing it now.

thheller20:02:19

the only issue I have with this is the "fast server-side applications" assumption.

thheller20:02:40

what if I need to do some actual work, db queries that say take 500ms

tony.kay20:02:16

The shell solution works for that as well.. What you’re trying to do is give the user a feeling that something is there. It’s more about perception. Usability isn’t improved.

tony.kay20:02:33

until everything is “there”, nothing works anyhow

thheller20:02:54

and what I find most annoying is pages that look like they are finished but don't work because the JS hasn't finished yet

thheller20:02:59

much rather see a loading anim 😛

tony.kay20:02:13

well, I’d encourage there to be a loading anim in there 🙂

tony.kay20:02:20

because I very much agree with you

tony.kay20:02:42

I mean, just gmail’s loading bar is good enough for that matter

thheller20:02:08

exactly. I just want to know that something is happening. seeing the empty page without anything happening is bad

thheller20:02:18

seeing a logo alone buys you a second or so

thheller20:02:07

ssr is definitely useful for some pages just not universally

sundarj20:02:48

yeah, it depends on how much you can know at request time

sundarj21:02:18

sometimes you can know enough to render the above-the-fold content fully (only matters that what the user sees is working), other times not

sundarj21:02:48

but SSR is an important tool to have at your disposal

sundarj21:02:37

suppose it's like macros in that sense 😁