ryansolid / babel-plugin-jsx-dom-expressions

A JSX to DOM plugin that wraps expressions for fine grained change detection
MIT License
60 stars 10 forks source link

Best fine grained rendering library #3

Closed mrjjwright closed 5 years ago

mrjjwright commented 5 years ago

Hi Ryan, I wanted to get your opinion on what is the most well tested but still highly performant library that could work well with fine grained reactive rendering. I was using stage0 but am recently worried about the lack of test coverage. I am considering with going with the runtime from Surplus or using this lib. Surplus hasn't been updated in a couple of months. Your libraries seem up to date and well tested but you also seem more focused on the JSX authoring story and actually I need zero JSX support as I am generating code from an editor. If I was to use your library without the JSX bits I assume I could just yank those out. Anyway, kind of a vague question and feel free to disregard if it doesn't interest you.

mrjjwright commented 5 years ago

I also took a look at ivi and some of those libs and I liked how well tested and performant they look but again most of that virtual dom based code doesn't make sense with my backend, which is completely based on S.js.

ryansolid commented 5 years ago

The challenge is that the cost of fine grained is the initial setup after which the performance is quite good. Surplus and Solid focus on JSX because the ease at which pre-compilation can happen. I made a HyperScript version that helps construct code without JSX but there is a performance hit and I haven't spent nearly as much time optimizing or profiling it. I can picture a better approach to do what you are looking for with string templating runtime compilation that would be a much smaller performance hit but I haven't written it. I believe early versions of Surplus used this approach, but change to focus completely on JSX.

There was a good several years that fine grained fell out of popularity. So there isn't much in between. It is possible to use fine grained with Virtual DOM as it's the approach MobX and Vue use but it comes at a cost. You could probably take something like ivi and wrap components in higher order computations but you trade the granularity a bit. I have no idea how much of a hit you take.

In that vein, your easiest option might be to take on a micro-rendering library that has explicit render/update mechanism and break into it computation components something like lighterhtml.

import S from 's-js';
import { render } from 'lighterhtml';

function component(fn) {
  return props => {
    const node = document.createElement('div');
    const renderFn = fn(props);
    S(() => render(node, renderFn));
    return node;
  }
}

// somewhere else
import { html } from 'lighterhtml'

const App = component(()  => {
  const name = S.data('John');
  setTimeout(() => name('Jake'), 1000);

  return () => html`<span>Hello, ${name()}</span>`;
})

I've never tried this, but I suspect it would work. I can't say whether Solid's HyperScript version or this would be more performant.

mrjjwright commented 5 years ago

Great info. lighterhtml is exactly what I was looking at yesterday and I was very impressed, especially with the fact that the author really cares about production code. That library is based on domdiff v2 which I could even use directly. I really like the ref collection mechanism based on TreeWalker that is in stage0 and I think I am going to keep it because it seems so performant. I like explicitly controlling the lifetime of dom nodes via S.js computations and the zero abstraction nature of stage0 helps with that. But again for the actual reconciliation function I might try domdiff. It will just take some experimentation.

might be to take on a micro-rendering library that has explicit render/update mechanism and break into it computation components

Exactly, and well said! Thanks for responding with a super helpful answer as usual.

ryansolid commented 5 years ago

Andrea Giammarchi (webreflection) does great work. I've been using his custom elements polyfill in production for 5 years. I use his augmentor library as my generic renderer wrapper for my webcomponent library. And before I moved to ivi's array reconciled I used his majin-buu.

DomDiff looks good. If you bypass the templating part of llighterhtml you will have more control. Just as with Stage0 be conscious of setting S.roots and disposal to ensure work is done efficiently. A large part why the reconciler in Stage0 looks so much smaller than the one in this repo is the importance of handling disposal logic in dynamic arrays with S.

I wish I had a better option for you now that abstracted these concerns especially since an approach with fine grained in mind would have considerably better performance. But it doesn't exist today. I will update when that changes or when I do some profiling of the fine grained non-JSX HyperScript approach already present in this library.

mrjjwright commented 5 years ago

Thank you for the heads up on the reconciling disposal! I am taking a look at what you have here now.

mrjjwright commented 5 years ago

Looks like I should highly consider using this, probably save myself a ton of work. I wouldn't need anything from hyperscript, I don't even need to manipulate strings or markup, every tag/attribute etc comes from cells (I know it's weird). But I do need a safe reconciliation algorithm that covers at least some of the edge cases with S.js related bugs I don't know about yet.

mrjjwright commented 5 years ago

One thing that could help me (and I have already received so much free and great help from you) would be a basic explanation of disposal issues with dynamic array reconcilation and S.js. As I understood it, S.js has automatic disposal under S.js roots and I do stay careful to ensure that I monitor that via the warning message that S.js gives when computations are created outside of a S.root. What is the basic problem there so I when I can read your code I can start to understand what you did? When I look through Surplus's runtime (https://github.com/adamhaile/surplus/blob/master/src/runtime/content.ts) I don't see any disposers or S.roots. In general, other than being more abstract in order to work with other reactive libs like mobx and vue, and being based on Babel, what are the big differences in the runtimes of this lib and Surplus in terms of fundamentals of how it would integrate with S.js?

mrjjwright commented 5 years ago

Fyi, the reason I don't need any kind of string/html/jsx support at all is because my app is source generating JS from the cells, similar to what this JSX compiler does. I could generate HTML or JSX, and then generate JS from that, but for right now the end generated JS suffices for diffs and checking logic. Which leads me to believe I could generate JS that directly works with your runtime. So I could generate calls towrap, insert, and other methods from the runtime instead of directly calling stage0 or dom calls. I think it would be a safer start for me.

ryansolid commented 5 years ago

Ok, well have you looked at my HyperScript mechanism? Look at createHyperScript. You could also generate it yourself ahead of time, but essentially that is what it does. I'm unsure of the overhead of some features like event delegation in a non-precompiled environment and I'm thinking of changing how that works but give it a try as is. You can look at solid/dom in the solid repo to see basically how it would work for S (there is nothing Solid specific in there). You could use that directly or just run the same code yourself without my additional helpers etc.

mrjjwright commented 5 years ago

I get the HyperScript version now! Awesome! Also Solid is so cool!

luwes commented 5 years ago

Thanks for the great plugin @ryansolid!

I had similar requirements @mrjjwright, I've been wanting to put this together ever since I discovered Surplus but not really keen on the JSX precompile step.

I hope you don't mind Ryan, I put together some pieces today that make your code with some adaptions (and flow's removed) + S.js work with template literals.

Check it out here, https://github.com/luwes/sinuous

ryansolid commented 5 years ago

Yeah that's cool. I put this(HyperScript) together mostly just to support a few people with this need but it isn't something I've had the bandwidth to take forward. I do think there is some potential with Tagged Template Literals or other string based Template to do something more similar to what Solid's JSX compiler does rather than how HyperScript works(the solution I have today) but that is a much bigger project.

I realized if I offered HyperScript I could support things like Tagged Template Literals(Example Here) or HyperScript Helpers. Mostly Solid is about the approach to managing state and the Renderer as long as fine grained can be swapped. Solid is like an opinionated version of S.

So you can use Solid with Surplus (as long as you don't use ES6 Proxies since Surplus will optimized out the computations) and S with this Babel Plugin or HyperScript extension.

The real question is maybe I just have my modules broken up all wrong since technically the runtime used by both is independent of the Babel Plugin or the HyperScript. I guess the Control Flow is the most opinionated thing I'm doing in the runtime and that comes at a cost in size.

In any case mostly I'm excited that people are exploring Fine Grained patterns again. That's what I've been after and why I created this plugin. I wanted any would be S, MobX, Knockout, whatever to be able to use the latest techniques for rendering (JSX, HyperScript, Tagged Template Literals). Creating a new VDOM library is easy with the tools out there and I felt there was a real gap here. So I'm happy if this even just stands as a reference.

luwes commented 5 years ago

Interesting, would that be more performant or smaller in size than the hyperscript solution? It is fairly small already.

Solid looks really good! The ES6 proxy does make it less ideal for production I think since it's not completely polyfillable. A support matrix on the readme would be great for a lot of people in the same boat as me. I've been looking for a tiny view library solution for a while that supports all >2% browsers. The reason I started Swiss (https://github.com/luwes/swiss). It's similar to SkateJS but no classes.

🙂 I was indeed a bit surprised to find that logic in this plugin (babel-plugin-jsx-dom-expressions)... oh this is meant to run in the browser! Haha, I just checked npm-name dom-expressions, saw you took care of it. Awesome!

are you on Twitter by the way? I can mention your work next time

the example has a glitch it seems, showing an error https://codepen.io/ryansolid/pen/GzQNWB?editors=0010

ryansolid commented 5 years ago

Does the example still have an issue? I saw an issue after I posted the link here. htm was compiling down to h(tag, attrs, ...children) and my h function was set up for h(tag, attrs, children). To support multiExpression children I need special logic otherwise shortcuts for single child expressions can mess with their neighbours which was what was happening there. Essentially when the list was empty it cleared the whole UI.

Currently I don't see any errors. It is possible for localStorage to get in a weird state. All my codepen todo examples share the same localStorage(Solid, HyperScript, Tagged Template Literals) so if the other ones have an error that could be it.

Yeah I did split off the runtime logic after the discussion here. I realized that perhaps atleast setting it up so I could look at different templating technology was a good start here. Dom Expressions runtime is actually core technology piece here that matters. And this cleaned up the dependencies nicely. It was something I'd been thinking about for a while but wasn't sure exactly what I wanted to do. I really started this from the JSX because I was focused on the best performance and the easiest to precompile. I mean this opens up a lot of paths like even Tagged Template precompilation in Babel to the same output as the JSX.

The Tagged Template Version I'm thinking of probably wouldn't be smaller in code than the HyperScript version but it would be able to better optimize the runtime execution. HyperScript version can't look ahead. It basically just runs a bunch of functions to link things up (document.createElement, r.wrap, r.insert.. so on). If you have 1000 rows it just runs that code over and over. The same techniques I use to split the dynamic and static parts of the templates in JSX could be applied to string templates. It could take the hit the first time but the next 999 times could be just cloning a template and executing the dynamic part. Thinking about it while there is some parsing challenges it might not be as hard as I was originally thinking.

As for Twitter, I mostly got off Social Media more than a decade back which has been a huge wall for me in promotion I realize now. It's like I'm starting at a deficit. Currently I don't but I think it is probably good time I remedy that.

luwes commented 5 years ago

Ah on my work computer it seems to work, all good!

That could be useful indeed, I guess that's one of the reasons for the flow controls too. I know lit-element has something similar with a repeat directive.

Then for our use case it's not a requirement to have these optimizations. We barely repeat any elements.

Twitter definitely can help promote your library! If you happen to join again, I'm @luwes ;)

ryansolid commented 5 years ago

I finally published the comparison I've been meaning to do for ages comparing the rendering techniques. This is probably of interest to anyone who reads this thread. https://medium.com/@ryansolid/the-fastest-way-to-render-the-dom-e3b226b15ca3.