-
Notifications
You must be signed in to change notification settings - Fork 79
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
Remove parent
as 1st mandatory argument for patch
#89
Comments
Actually hitting the same road block just this morning 😛 I'm working on a localized stateful component using picodom and the assumption that // this._self - previous vdom node
// this.render() - abstracted render fn
// this.ref - outer DOM node of the component
const temp = document.createElement('div')
temp.appendChild(this.ref.cloneNode(true))
patch(temp, this._self, (this._self = this.render(this.props, this.state)))
const next = temp.childNodes[0]
this.ref.parentNode.replaceChild(next, this.ref)
this.ref = next Not ideal, though I haven't run any benchmarks to really understand the performance impact here. It may be negligible in the grand scheme of things. Would it be possible to expose a subset of the |
Thanks, @JSteunou and @estrattonbailey for bringing this up. Do you have any proposed alternatives? 🤔 |
Looking into it, seems like a fun challenge. I've pulled down this repo and linked it to my current project but my experience writing VDOM reconcilers is... well it's zero, lol. Will report back if I have a suggestion. |
I really like |
Can you be more exhaustive? What are the types of those arguments you wrote there? Where is the container? |
no container, The framework above is in charge of mounting, before or after patch 1st call. |
@jorgebucaran I submitted a PR over at #91 introducing a |
@JSteunou In hyperapp/picodom "terminology", nodes refer to virtual nodes and elements to svg/html elements (Element, HTMLElement, etc.) I know there is a So, anyway, do you mean this? patch(node: Element, newVNode: VNode): Element |
@estrattonbailey Thanks! I left you a comment there. 🎉 |
I think this kinda comes back to the issue I mentioned in my last comment here - the assumption with In my own experimental VDOM implementation, I used a signature like:
That is, the update operation is with a single parent but a range of VNodes. The "single parent to single child" relationship assumption made by I'm honestly starting to wonder if element-level life-cycle events can allow for class-based components at all? Take this trivial example:
The problem here is that I'm not even sure if "single parent / multiple children" addresses that - it probably doesn't? It's making my head spin. I'm close to giving up and just using Preact 🙄 I don't like the React component model at all, but it does work, but I'm sort of running out of steam - and I have real projects I need to build... |
@mindplay-dk And you want stateful components? |
@mindplay-dk yep! I think we're trying to solve the same puzzle. As you mentioned here:
Prior to the proposal of #91 which introduces private references to the actual DOM nodes via And yep, as you mention here, creating a callable I put together a stateful component POC here to demonstrate #91. I haven't tried to figure out an implementation as a |
@jorgebucaran yes I did mean patch(node: Element, newVNode: VNode): Element @mindplay-dk I really like your idea. I know being forced to start any React component with a single root node was not ideal for some people. In any case the main idea is to avoid useless Element, or 2 singles Element in a row (single parent, single child, then true content) Do you think it will possible to write a patch function with this signature? patch(nodes: Array<Element>, newVNodes: Array<VNode>): Array<Element> maybe getting parent from 1st Element when existing or old Elements are given |
@JSteunou |
Sure, but I wouldn't - you'd have to diff VNodes against DOM elements, which is going to be a lot more complex (and likely slower) than diffing the three properties of the simple VNode model. Though maybe there's a way around that. What if you attached I think I'd swap the order of the arguments then.
That is, with the Yeah, could work?! Requires a rewrite basically, but you've been wanting to do that anyway ;-) |
@jorgebucaran I dont know where it is :) ATM I'm just thinking about the API for the user. I really like to think API 1st and hide the complexity to the user under the hood ;) @mindplay-dk Yep, reversing the arguments might be better if the Element can be optional for the 1st patch call. |
I agree, but I also don't recommend using Picodom to make SPAs without a state management or framework layer. That's not to say I don't want to improve the API, but the goal of this project is for users to make their own custom Hyperapp-like view library/framework. |
Yep, I was thinking API users, not necessary final user on top of the FW on top of picodom. Even for those it should be as easy as pie |
IMO we should not prioritize easy over simple and correct - this is especially bad in APIs, where making something easier often means it's less flexible and unable to support certain uses. If somebody (me!) wants to use vanilla Picodom, that's "my problem" - and I could easily change my mind about certain things in my own architecture from project to project, so I'd strongly prefer the API isn't optimized for any imagined use-case or opinions about architecture. It's a library: it should do just exactly what it needs to do, and whatever that means for the API is fine by me. |
(this is merely a philosophical rant, I'm not disagreeing with anything that was said 😆) |
I was thinking about the idea of attaching the entire VNode to the actual DOM element - and I'm pretty sure this would solve the problem with deferred When we create elements, adding the VNode property will identify them as elements we created - when we iterate over elements, simply ignore any elements that don't have the VNode property, so the property doubles as a flag that tells us which elements we're managing, as well as maintaining state between calls. When a removed element is found, if it's removal is deferred, just remove it's VNode property, and it'll stay in the DOM wherever it is, since we won't even consider it as one of our managed elements. Also, when we attach the VNode, we can also attach it's child index in the list of VNode children - which removes the need to count of consider element indexes or worry about changes in the DOM at all. This will even make it possible to inject custom siblings via I'm pretty sure this approach will actually be even simpler than what we're doing now, too 😄 Man, I wish I had time to work on this right now... |
This is good. |
@mindplay-dk HyperHTML does attach the Array from template string to the node Element I think |
Attaching anything to the DOM is perhaps not ideal, but it would work as proof of concept - if needed, we could then use a jQuery-style data-strategy, where we assign a unique ID to an attribute of DOM elements, keep the actual data in an internal map, and remove garbage entries when we remove the DOM elements. |
I've started now three times and ended up breaking everything, so now I'm trying to do this in very small increments - here's the first little step towards maintaining current VDom state in a master...mindplay-dk:vdom-context It's only breaking three tests, but I'm out of time - please feel free to take a stab at this :-) (for now it's ignoring the |
After several more attempts, I think I've reached some important conclusions. My biggest a-ha so far, is I think I've worked out the right signature of the inner patch function, which I'm now calling
I know, that looks like a lot of arguments, but let me explain: The function will be called for every visited In order to create and position all nodes, the function needs both the The function will completely update the state of the I'm passing in the list of Note that child iteration needs to be in backwards order (last child first) because we use the Here's pseudo-code of the inner function:
The "list of managed children" is built by looping over DOM elements and checking for the injected To "destroy" here means removing the target element from active life-cycle management - actual element destruction is immediate only if there is no deferred removal hook, same as now, but we'll flag elements as removed, e.g. by injecting a Because I'm fairly sure about this by now, but it's basically a rewrite, and it'll break all the tests, so this is basically a start over from scratch situation. I want to use Typescript and Rewriting this will take at least a few full days, and I don't know when I'll have that kind of time, which is why I'm dumping the explanation and pseudo-code here for now, in case somebody else wants to take a stab at an implementation 🙄 |
@mindplay-dk would your proposal break this? import { State, View} from './helpers';
import { patch } from 'picodom';
let node: any;
export function render(view: View, state: State) {
patch(node, (node = view(state)));
} It is right that I would like to have some possibility when I use router and change the path that the parent element runs oncreate instead of onupdate but also right that I wouldn't like to change this algorism: private build(configRouters: Array<Routers>, parentPath?: string) {
configRouters.forEach((router, index) => {
if(parentPath) router.path = `${parentPath}${router.path}`;
if(router.alias) {
this.router.on(router.path, {
as: router.alias, uses: (params, query) => {
this.setPatch(router, params, query);
}
}, router.hooks);
} else {
this.router.on(router.path, (params, query) => {
this.setPatch(router, params, query);
}, router.hooks);
}
if(router.routers) this.build(router.routers, router.path);
});
}
private setPatch(route: Routers, params: Object, query: string) {
const page = route.page;
const state: State = {};
Object.assign(state, page.state);
state.params = params;
state.query = query;
state.defaultProps = route.defaultProps || this.defaultProps;
render(page.view, state);
} |
@dancespiele this change will make it very easy to offer a meaningful |
I've abandoned my original plans since proposing #91, but I've stayed subscribed to this thread because I find it interesting, lol. On that note, my goal there was twofold:
As you can see in here, JorgeBucaran expressed some concern with mutating the node structure with private fields. I don't share that concern, but I am probably inexperienced in this area so I defer. The same pattern could definitely be achieved using @mindplay-dk's proposal of an internal map. I had considered this when I was poking around #91, but was concerned about reliably creating unique indices to associate VDOM nodes to DOM nodes in the map, as well as the additional lookup and memory allocation. Attaching it to the VDOM node via At any rate, my 2¢ is that there should be a separate It's been fun to see this discussion unfold :) |
What I'm proposing is the opposite: inject the vNode into the DOM element. This makes the DOM the "source of truth", and removes the need to pass in / maintain the old vNode state yourself, allows for deferred removal, obviates the reordering issue - it even allows you to manually move/destroy managed DOM nodes, for example during drag-and-drop operations, and a simple call to update your mounted node afterwards will put everything back in working order. And as said, there's a way around injecting vDom nodes into DOM elements, too - by using a reserved DOM attribute to store a global identifier for an element registry, a'la
Absolutely, but it's not really possible right now, since you can't designate an update of single, existing element - because, as you pointed out in your first comment, What I'm proposing mitigates this, but almost necessitates the addition of a |
I'm going to work on this today. I have trouble figuring out what the top-level The problem is the difference between creating new elements vs patching existing elements - the old The new function should patch an individual element directly, replace it as needed, etc. - I can't think of a meaningful way to do that with a single function, since the required input for creation and update are substantially different, e.g. minimally:
Because creation is more or less meaningless if you can't specify where in the DOM you want things created. I will go ahead and work on the internal function, keeping the exported function and the current parent/child constraint still in place, but I'm really looking for ideas and input here... |
Alright, guys - here's the fruit of today's labor: https://bitbucket.org/mindplaydk/veedom/src It's a rough implementation of the pseudo-code I posted above. I based off of my own "Picodom rewrite", as this had tests and a working My aim right now is to make it work: I am putting zero effort into keeping the code small or free of duplication, it's deliberately very verbose, unoptimized, and commented. This will need at least another full day of work to fix bugs and optimize - it isn't passing the tests yet, and I'm out of time for today, but it feels like a pretty good start :-) |
Added a quick build script displaying the minified/gzipped output size, and a bit to my surprise, it's only 1172 bytes, having made no attempts to remove duplication or make it smaller 😃 Anyways, that's all I've got time for today. |
To be fair, it still doesn't have |
@mindplay-dk the advertising for view library is 1k if it is more this would be a misleading advertising ;) |
I've said that before, but my personal goal is to have something that is simple and useful - I'm only counting bytes to get an idea of whether it's still small, the impact of making a change, and so on. 1K or 2K is besides the point for me :-) |
I was kidding :). I would like collaborate but I will be busy the next two weeks, after that I will test everything and also try to give some idea. @mindplay-dk good job. |
if it actually works ;-) |
Sorry, guys, but I'm going to drop this rewrite for now - I simply can't dedicate the time and attention this needs, and it's a constant distraction. Being able to work on this for a day or so every two weeks is a futile effort - I can't recall what I was doing, and basically have to start over every time. I'm hoping somebody else will pick up where I left off - the idea of the inner function If somebody wants to pick up work on this, please note that the inner loop over child VNodes is completely unfinished - it of course will need two index variables, I somehow thought this would be possible with just one, but that part of the pseudo-code was totally naive and unfinished. I had a working implementation earlier here, but I can't figure out how to merge these concepts. I'd be happy to answer any questions. |
This is it, folks! import { h, patch } from "patchdom"
document.body.appendChild(
patch( h("h1", {}, "Hello") )
) The patch function now returns an element, if none is given, allowing you to append it to a container if that's what you want. If you pass an existing element instead, patchdom will patch that element (attempting to recycle any existing markup), instead of using it as a container like in the past. |
👏 👏 that was exactly the idea ! No need for a wontfix label, this is fixed ! :) |
🎉🤘😄🤘🎉 |
I am writing docs now. |
Having
parent
as first argument works very well when the all app is designed and wrote with picodom but not that much when used in a mixed libs app. (yes it happens in real world)You cannot then just render / create a component to get its main node and then append it manually to a parent node of your choice at the time you choose.
To get around this you have to create a fake parent with
document.createElement
but that add a useless layer.I'm also not a big fan of that kind of dependency injection and lake of separation which introduce a dependence between the child and its parent, I do prefer the other way, parent knowing its children, but I think this is just a matter of taste.
The text was updated successfully, but these errors were encountered: