-
Notifications
You must be signed in to change notification settings - Fork 129
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
Issue with webcomponents that have HTML Comment elements #337
Comments
Our system works by walking known nodes, I don't don't know if there is anything to be done here. I'm open to suggestions but if something changes the shape of the HTML from what the JSX outputs we aren't going to know where things are. |
Yes, we can do something. The fatal flaw in I am not trying to be far-fetched, what I mean is that as long as we have references to nodes, and as long as "others" do not mess up with "our" stuff(nodes), then we can keep doing our thing without breaking anything. |
Hope that was clear, let me know if wasnt |
We can address this here along with similar cases. The necessary change also shouldn't have that much impact on perf as we just need to flip it to make the node "ours". |
That would be nice, I only identify these two lines that could become problematic. In theory, it should be a matter of saving in dom-expressions/packages/dom-expressions/src/client.js Lines 479 to 480 in a1d24a1
|
Yeah performance is a big deal here. Otherwise everything would be multi. By assuming we render all children shortcuts a bunch of operations. That being said I believe this issue is different than the moving nodes in the issue. |
Just had an idea, the original code: if (current !== "" && typeof current === "string")
current = parent.firstChild.data = value;
else
current = parent.textContent = value; maybe could become if (current instanceof Text)
current.data = value;
else {
if (parent.childNodes.length === 0) {
parent.textContent = value;
current = parent.firstChild
} else {
current = document.createTextNode(value)
parent.appendChild(current)
}
} assuming
performance: This should in theory perform very similar, the I am not sure of the penalty of The penalty in Do you have a way to measure this possible change? It may be worthy to not break third party code that slightly changes the tree. |
@mdynnl what do you think? |
Yeah, this is what I had in mind with my initial understanding but something obvious was missing. Then I realized if we go this way, we would also have to handle other cases of This case is so common and would have a huge impact on perf 😆. Anything that renders a dynamic sole child gets basically deopts to the case where something comes after. (i.e The latter is also how this could be solved from consuming side by appending |
If I understand correctly, the proposed code
It could possibly may be slower here, but we can check there if
In
All of that, if I am not missing something, I am missing something? 🤔 |
Yeah, it's that For example, it would be like this and we'd also need to follow up in a couple of places as well. if (current instanceof Text)
current.data = value;
else {
if (current && current.length) {
for (let i = current.length - 1; i >= 0; i--) current[i].remove();
}
current = document.createTextNode(value)
parent.appendChild(current)
} Just for the idea, I'm going to open a PR for this so we have a place to point people to. |
Nice, thank you, I was about to open a PR too, I am strictly speaking of this condition, it cannot be an array there } else {
if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;
} |
OK, so just for documenting here, mdynnl explained to me, that the issue is when <div>{show() ? 1 : [1, 2]}</div> I missed that when they first explained it. Now understanding that, some assumptions go out of the window, but also this isn't a fairly common case. So maybe we can do something about it. @mdynnl will be working on a PR. One could probably make the case that if you need to touch the dom then wrap your things, but that's heavily annoying. I think as long as we can keep the happy path mostly untouched or not impacted in considerable ways, then fixing this will be super welcome for when solid is used with third party stuff. Like in there's no reason for this to happen other than performance. |
Ok, this weekend, I think I finally got around to the scope of what Ryan said. This assumption that we render all children is for these cases specifically as a fast path, as described in #337 (comment). I still think we should at least give a way to opt out this though e.g The opt out does exist today by appending empty text node before/after the children right under a native(or custom) element. https://playground.solidjs.com/anonymous/4933161a-2c9c-4abd-b70f-43e44a22f009 For ionic solid, that would be https://github.com/ionic-solidjs/ionic-solidjs/blob/791995e07ff93702cdd1fde5989194e561ef2d9c/packages/core/src/components/generated/ion-accordion.tsx#L26 for accordion. |
This is what I've got so far and it does pass this issue and a few other cases too, but continuing this basically leads to implementing the same logic as multi case. diff --git a/packages/dom-expressions/src/client.js b/packages/dom-expressions/src/client.js
index e4e6460a..633d70ac 100644
--- a/packages/dom-expressions/src/client.js
+++ b/packages/dom-expressions/src/client.js
@@ -475,13 +475,26 @@ function insertExpression(parent, value, current, marker, unwrapArray) {
} else node = document.createTextNode(value);
current = cleanChildren(parent, current, marker, node);
} else {
- if (current !== "" && typeof current === "string") {
- current = parent.firstChild.data = value;
- } else current = parent.textContent = value;
+ if (current && Array.isArray(current) && current.length) {
+ for (let i = current.length - 1; i--;) current[i].remove();
+ current = current[0];
+ }
+ if (current && current.nodeType === 3) {
+ current.data !== value && (current.data = value)
+ } else {
+ current && current.remove();
+ current = document.createTextNode(value);
+ parent.appendChild(current);
+ }
}
} else if (value == null || t === "boolean") {
if (hydrating) return current;
- current = cleanChildren(parent, current, marker);
+ if (multi) {
+ current = cleanChildren(parent, current, marker);
+ } else {
+ current && current.remove()
+ current = value
+ }
} else if (t === "function") {
effect(() => {
let v = value();
@@ -513,8 +526,8 @@ function insertExpression(parent, value, current, marker, unwrapArray) {
appendNodes(parent, array, marker);
} else reconcileArrays(parent, current, array);
} else {
- current && cleanChildren(parent);
- appendNodes(parent, array);
+ appendNodes(parent, array, current);
+ current && current.remove();
}
current = array;
} else if (value.nodeType) {
@@ -524,7 +537,7 @@ function insertExpression(parent, value, current, marker, unwrapArray) {
cleanChildren(parent, current, null, value);
} else if (current == null || current === "" || !parent.firstChild) {
parent.appendChild(value);
- } else parent.replaceChild(value, parent.firstChild);
+ } else parent.replaceChild(value, current);
current = value;
} else if ("_DX_DEV_") console.warn(`Unrecognized value. Skipped inserting`, value); |
@mdynnl can you send it as a pr please, would be nice to fix this. I kind of suspect we can still keep the happy path, for example this } else {
current && current.remove();
current = document.createTextNode(value);
parent.appendChild(current);
} becoming to somewhat the equivalent of what we had before } else {
if( parent.childNodes.length < 2 ){
parent.textContent = value
current = parent.firstChild
} else {
current && current.remove();
current = document.createTextNode(value);
parent.appendChild(current);
}
} |
Some webcomponents (like Ionic's ion-label) generate additional comment children
For those, the code frragment
dom-expressions/packages/dom-expressions/src/client.js
Line 428 in 8d2d82f
is going to try and update the comment rather than the actual text content.
A repro that isn't very minimal: https://stackblitz.com/edit/github-pyx5ga?file=package.json,app%2Fclient.tsx,vite.config.js,index.html,app%2Fionic-css.ts
You will notice that
<IonLabel />
generates empty comment elements, which in turn cause SolidJS to update them if the only child of the component is a stringThe text was updated successfully, but these errors were encountered: