-
Notifications
You must be signed in to change notification settings - Fork 841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update DOM Expression Libraries and Remove all hacks #794
Conversation
It's really great to show the code that end users are most likely to (want to) write. The new code is cleaner and easier to read. The hacks were more verbose and not representative of what users want. 👍 |
Thanks for the change. Your removed List implementation is still interesting, and it would be nice if it becomes a first-class API somewhere. |
@aminya It used to be. Then I delegated it here and I finally now removed it. I thought that was the ideal solution at one point but while more optimal it is so specific here. Like what if there are dynamic children ie.. each row is a top-level conditional, or double nested arrays. There are just always places where the graph evaluates lazily so can't depend on full resolution at that point. This hooks in at the reactive phase but before we are connecting/diffing the real tree. My original original approach did a hook at the end of DOM rendering which worked better because everything was resolved. Check out |
@ryansolid quite impressive, 💯 ! |
👍 on removing "hacks." Since the PR talks about "all hacks" ... aren't there still a couple here? Should these have "Notes"?
2 feels like an optimization the Solid library should do internally -- Surplus used to, so I'm surprised Solid doesn't? Maybe it does and this is just vestigial? 1 bothers me a bit, as it's sort of a crypto-optimization. The code here "looks clean" but is actually doing a fairly advanced and impactful optimization. I think this is sort of a moral peril of the "looks clean" test -- it encourages submitters to hide the optimizations they're making. It would be better if submitters made it clear when they were doing optimizations vs a naive approach. |
@adamhaile You are correct there are those 2 things going on. So maybe all hacks is wrong. I guess I still have library specific tricks. Just removed the general can do with any library direct DOM ones. Hoisting prevents the compiler from incorrectly thinking the bindings are dynamic. I also have an explicit syntax for this using comments And the second is that while I group all attribute updates in the same computation in a template block, inserts get their own. So using So those are 2 very subtle optimizations both related to the reactivity system and giving the compiler hints. The first is rather inconsequential for the pure signal version but does have a cost for the proxies as you pointed out. The second actually has considerable savings. Although it's really more like giving hints to the compiler similar to Inferno's <tr className={selected ? 'danger' : null}>
<td className="col-md-1" $HasTextChildren>{id}</td>
<td className="col-md-4">
<a onClick={linkEvent(id, selectFunc)} $HasTextChildren>{label}</a>
</td>
<td className="col-md-1">
<a onClick={linkEvent(id, deleteFunc)}>
<span className="glyphicon glyphicon-remove" aria-hidden="true"/>
</a>
</td>
<td className="col-md-6"/>
</tr> Maybe there should be a note where the implementation uses compiler hints to optimize performance? Although it's pretty hard to crack down on. I did use internalization to hide the direct dom manipulation in the past and unless you looked closely you may have not noticed (I'm sure you did). I was happy to be rid of it as it wasn't generalizable. When I write demos I tend to use my knowledge of the reactive system. So this isn't the only place you will see |
@ryansolid It's been a while, so I forgot that the That's a pretty significant (and very non-obvious) optimization. How does Solid perform with a naive implementation, aka without the For full disclosure, at one point Surplus had the same optimizations, but I later removed them, as they made me uncomfortable. Instead, I worked on optimizing no-dependency and single-dependency computation construction in S, so that I didn't need the hacks any more. (That work was the When we diverge from a naive approach for performance, I think we should make those differences explicit in the code. I don't know what Inferno's I've said elsewhere that I'm not a fan of the flags idea, but if the benchmark does go down that path, seems like these are the kinds of things that should be noted. Maybe I hear you that one of the things I like about real DOM, fine-grained libraries is that they put you in the middle of the process rather than outside it. Unfortunately, I think that battle is lost. Clearest sign is that libraries which break the DOM's event bubbling model for performance are called clean, while those that work with event bubbling get flagged. |
Writing a comment seems reasonable. I personally put this all in the same category as what Inferno is doing. Only because it is non-obvious that it gives more information to the compiler to produce more optimal code. It's not like it escapes the declarative syntax. When the expression is dynamic it updates like anything else. It is a way of indicating the children are only text which I thought was pretty nice in that it doesn't introduce new syntax. Most libraries with JSX have a way of setting innerHTML as well. Same sort of thing. It is a declarative prop/attribute abstraction on a DOM API but that is what most data-bindings are. I think the event delegation is a losing battle. But I really see no problem with it as it is the way all libaries solve the problem internal or externally. Official docs for libraries like Knockout, and Vue direct you to doing explicit event delegation. This is the defacto way people approach solving this so I don't know. I have included event delegation internally for ease but I don't think that is a choice that needs to be made by the library. |
With this PR I remove all direct DOM manipulation. No
el.className = "danger"
anywhere. No clever DOM node caching using reactive memorization. Not even shortcutting the test by dirtying the data model with a selected on each row. All my libraries will be doing an inlineclass={selected() === id ? "danger" : ""}
or equivalent from now on.By doing so all my libraries will take a performance hit. But the front end of this benchmark has gotten a bit ridiculous with bending the expectation. I hope with this I can encourage others to remove shady not in the spirit of the test implementation pieces. But if not at least they can serve as an example.
That being said I'm looking forward to testing a new technique I've developed. Inspired by Recoil's
selectorFamily
, I've developed a new reactive primitive for propagating change conditionally. It creates a keyed signal that only updates when the key matches without creating additional computations.class={selected(id) ? "danger" : ""}
It is completely generic and has no knowledge of the DOM and can be leveraged like a normal signal in any data-binding or effect. It allows O(1) updates in these selection cases instead of O(n) and leverages the granularity to pull it off efficiently.
So far only Solid can leverage this technique though as I don't get the level of access I need in other libraries.