-
Notifications
You must be signed in to change notification settings - Fork 780
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
Support roots with existing children (#128) #140
Conversation
Does this also work when the third-party element vanishes or another one gets added at runtime? |
Codecov Report
@@ Coverage Diff @@
## master #140 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 4 4
Lines 163 165 +2
Branches 40 40
=====================================
+ Hits 163 165 +2
Continue to review full report at Codecov.
|
@dodekeract I don't think so, my bad... |
Also it would be nice to expose somehow internal |
@ngryman Awesome! I still need to run this thru the benchmarks however. Patch is heavily used and we don't want worse performance just to have the ability to insert an app in document.body right? :) |
@jbucaran I don't think this issue is related to |
accessing childNodes and using indexOf in a hot function is gonna be a perf killer...although doing it only for root might be ok. |
@dodekeract Do you have an failing example to back that up? |
@dodekeract I see what you mean. The issue is embedding the app in an element that is not empty. Why would anyone do that anyway? |
@jbucaran Only relevant for |
@dodekeract What do other libraries do btw? @ngryman Do we have to run this code on every patch or just once? |
@jbucaran React just decided to not support |
@dodekeract If I remember well React warns the user about using |
@ngryman Then why is the code inside patch? Maybe outside render, and change render to take an index argument too? The router... |
@jbucaran Because I need the actual DOM element which is created inside |
@ngryman Is that new? I remember that this was one of the few things I disliked when starting with react. Could have changed "recently". |
JSX as well as Hyperx don't allow you to do stuff like this:
Why should the vDOM engine allow you to embed an app in a container with children already in it? |
First of all some terminology, let's define the Here is an unelegant solution, but it highlights what could be done: treat |
@jbucaran That's a different problem. Any |
@jbucaran If you agree with this I could factorize this and add associated tests. |
@ngryman Go for it and let's see! 🙏 |
@jbucaran Here is the idea. After refactorization, we end up with just 1 more I just need to add one more test to check if it works well when mutating the root node outside |
I'm on the fence about this feature. First gut instinct is that it is unnecessary as there are current easy workarounds (e.g. creating a placeholder element inside of the targeted root element and targeting that instead). And I think for the most part, people coming from other view libraries are expecting mounting a DOM tree to completely replace the contents. |
Can you mention at least one library that works like this? |
domvm can replace/absorb (and will clear) a target placeholder via if you support absorbing a pre-existing root or placeholder, then it's reasonable to expect content to be replaced. however, i think that most people would expect the app's root node to be appended to the container they specify, at least by default. |
@jbucaran Yes: React. Which is arguably the most familiar to masses. Here's my opinion: hyperapp is not a full fledged vdom implementation. After trying other vdom implementations it was decided that it would be best to keep the footprint small and implement a custom vdom. Should this vdom have all of the capabilities of another implementation? If so, we should probably just be using another library, which has smart people maintaining it and interested in solving those domain problems. In my eyes this project is more about the elm architecture than it is about giving people options. If anything, being restrictive on these types of edges cases is probably better than being open, as it coerces people into doing one thing consistently. If this feature request is to easier enable people to mount components into a DOM tree without blowing away the existing DOM, then my suggestion is to just make a placeholder element at end (or beginning, or wherever you want, this technique is actually more flexible) and render into that. It seems to be a perfectly acceptable solution. |
@farism tldr I understand your opinion and would totally agree with you if the cost of this feature was actually high. But it's not.
In fact many factors are involved:
I've taken the liberty to check what I think this feature is OK with. The only remaining item would potentially be the impact of this feature, which I agree with you is pretty low if we consider the current usage.
Still, you have 5/6 items checked meaning that you have a pretty good score on this feature. And it's actually 4 loc... I see a huge benefit for a tiny cost here.
Well I don't expect this at all tbh. In fact, what is the benefit for the user to do so? I don't see it, I only see a benefit for the library author that don't want to mess with this. Sorry if it was veeeery long, but it's an interesting discussion because it raises the question: is |
I haven't dug too deeply into the code around this issue so I am not entirely sure this is relevant but all the talk of document fragments got me thinking about
const frag = template => {
const range = document.createRange();
range.selectNode(document.body);
return range.createContextualFragment(template.trim());
};
const model = { name: 'Joe Bloggs' }
const view = (model) =>`
<img src='https://unsplash.it/200'
<h1>Welcome ${model.name}</h1>
<button>Click Me</button>
`
const node = frag(view(model))
document.body.appendChild(node) |
@ngryman I found a simpler way (maybe). One way to do it:
To calculate the index I am using: function getElementIndex(element) {
for (var i = 0; element = element.previousSibling; i++);
return i
} I'm using previousSibling and not previousElementSibling, since we want to account for text nodes too. That's it. Seems to work. Now I'm trying to see how to cache the index, so we don't have to run getElementIndex on every render. This should be okay. |
@lukejacksonn I didn't know @jbucaran Yes right, that would remove a bunch of |
@ngryman I think I might have ended up with something similar to that. And I was able to get rid of the index too. Now, I'm just passing the parent and the first child of the parent to patch. I cache the first child inside The only changes are that both render and patch return an element (the one we cache inside for (var i = 0; element = element.previousSibling; i++); Also, the default app.root is now |
@jbucaran Sounds promising 👍 From what you describe, I just want ot make sure you cover:
I think we would need your code to discuss this 🤓 |
@ngryman There's one use case that won't work for sure, if your root element changes. See the example below. app({
model: 0,
actions: {
add: model => model + 1
},
view: (model, actions) => {
var el
if (model === 0) {
el = <h1 onClick={actions.add}>Small</h1>
} else if (model === 1) {
el = <h2 onClick={actions.add}>Smaller</h2>
} else {
el = <h3>Tiny</h3>
}
// return el // BAD
return <code>{el}</code> // OK
}
}) Not sure if an easy solution is possible. But I'm not too worried about it.
|
@jbucaran I think if there is no relatively easy solution that works in all cases we shouldn't consider it. Having apps that sometimes break for users with browser addons vs having to use a |
@dodekeract |
@dodekeract @ngryman Wait, wait my bad. We are caching an element, not an index. We calculate the index only once, inside load, and then get Even if app.root has elements appended (or inserted at the top) we start patching from the cached first child. tl;dr it's okay. |
@ngryman Like I said above, it works, but I found it fails in a strange way when the root node is an SVG element and the body is modified. 😓 Would you like to continue working on this feature? Perhaps using my idea as a base and as a different PR. |
@jbucaran Sure! I won't be able before this WE thought. Can you provide your code on some branch so I can start from here? |
@ngryman Sure thing! Would you mind joining the Slack room? 🙏 |
I've been trying to catch up with what's going on here... Am I correct in understanding the following:
|
@zaceno The code to play with is on this gist. |
@zaceno Yes this implementation works, or at least worked ( @jbucaran wanted to explore another way to do so in the provided gist. I'm going to patch this PR given the idea of @jbucaran to return the element from patch. |
And here it is 😄 |
@ngryman Is it necessary to return the element at the end of BTW, how did you fix the issue I posted as a gif on Slack? 🤔 |
@jbucaran Well it's only necessary when we I don't see your codepen link in Slack history, you told me about a svg clock if I remember. That said, I added 2 additional tests that covers all additional use cases I can see. But I would gladly validate if it works with your svg clock if you want. |
@ngryman Cool, that would be this one http://codepen.io/jbucaran/pen/PWMBLp But really, the problem has to do with what I wrote on Slack. This may happen after keyed dom, so maybe we can close this PR and create a new one. I don't know if it's my browser or GitHub, but it takes a while to load this thread every time I come here. How's your experience? |
Here is the working version with my PR applied: http://codepen.io/ngryman/pen/mWxoRo. |
Or merge it? 😂 |
@ngryman The code works, but it breaks when you try to edit the HTML on the dev console, which is how I am testing an imaginary browser extension that can modify document.body. The svg was where I first found the problem, but even the simplest example can repro the issue. |
@jbucaran Can you reproduce it with programmatic calls? I'm not sure how devtools handle live edits exactly. |
Thank you @ngryman! 🤘 🤘 |
Thanx a ton @ngryman. I was playing with your pull request with the svg clock. I noticed I could insert the clock into this structure and update the <h1 id='testTitle'>
<p>Hello</p>
</h1> However, inserting the clock into a tag with only text and trying to update the textContent replaced the clock. Which is what I would expect: <h1 id='testTitle'>Hello</h1> 👍 |
Here it is :)