-
Notifications
You must be signed in to change notification settings - Fork 47k
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
[Fiber] Support SVG by adding a container stack #8417
Conversation
cb3e491
to
a755f74
Compare
@sebmarkbage This is sort of ready for review. Portals might be broken but I have a todo for that. |
Should work with portals too now. |
Note that I haven't tried unifying this with portals yet. I'm not sure if it's helpful or not. |
Any way we can split the container piece from the parent piece (that creates in beginWork)? The second one is a hot path and needs further evaluation. |
Removed the parent stack and moved initial insertions back into complete phase. We still need to create the instances in the begin phase so that we have access to root container instance in |
@@ -453,8 +497,15 @@ module.exports = function<T, P, I, TI, C>( | |||
case HostComponent: | |||
return updateHostComponent(current, workInProgress); | |||
case HostText: | |||
// Nothing to do here. This is terminal. We'll do the completion step | |||
// immediately after. | |||
const newText = workInProgress.pendingProps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically didn't have to move this one but did this for consistency.
Can move it back too.
// and use tagName (which is upper case for HTML DOM elements). Or we could | ||
// let the renderer "normalize" the fiber type so we don't have to read | ||
// the type from DOM. However we need to remember SVG is case-sensitive. | ||
var tag = domElement.tagName.toLowerCase(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is likely going to be an issue to do for every update. We might want to consider just banning non-lowercase uses instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can omit this change if it's better to discuss separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we already doing this in commitUpdate()
before my PR. So I don't see why doing this during mounting is worse. (We should fix both?)
The creation order is only needed if we want to read the namespace from the DOM. We don't have to design it that way. We can pass a flag or something instead. It might be more efficient than touching DOM accessor methods anyway and doesn't lock us in to this creation order. (Even though we might want that anyway.) Will review further today. The reason it is taking time is that I want to a deep review and consideration because this change is a compromise only for the purpose of letting users avoid switching renderers in the rare cases you switch modes. It is also very specific only to the DOM renderer. Anything that adds perf overhead or locks us into a particular creation phase seems like a big tradeoff. |
@@ -517,22 +517,22 @@ describe('ReactIncrementalSideEffects', () => { | |||
); | |||
} | |||
ReactNoop.render(<Foo tick={0} idx={0} />); | |||
ReactNoop.flushDeferredPri(40 + 25); | |||
ReactNoop.flushDeferredPri(40 + 20); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are all these taking more cycles?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less cycles, isn't it? I'm not sure. Should I look into this?
Otherwise, the parent gets skipped next time. We could probably create it later but this seems simpler.
I don't understand why they changed but probably related to us moving some work into begin phase?
Previously I was pushing nodes on the parent stack regardless of whether they were already in current or not. As a result, insertions during updates were duplicated, and nodes were added to existing parents before commit phase. Luckily we have a test that caught that.
I'm not 100% sure this is right but I can't get tests to fail.
I was confused by th HACK comment so I learned how DOM and SVG work with casing and tried to write a more descriptive comment. It also seems like passing fiber.type into finalizeInitialChildren() is a potential problem because DOM code assumes tag is lowercase. So I added a similar "hack" to finalizeInitialChildren() that is identical to the one we have prepareUpdate() so if we fix them later, we fix both.
We can address this later separately as it is a more hot path. This doesn't affect correctness of SVG container behavior.
This tests the "jump" reuse code path in particular.
I don’t understand this part. What case are you talking about? |
This way createInstance() depends on the innermost container only for reading the namespace.
I’m looking into tracking namespaces separately so that we don’t need to change when we create. |
While we might want to create instance in the begin phase, we shouldn't let DOM guide reconciler design. Instead, we are adding a new concept of "host context". In case of ReactDOMFiber, it's just the current namespace. We are keeping a stack of host context values, ignoring those that are referentially equal. The renderer receives the parent context and type, and can return a new context.
@sebmarkbage Should be ready for another review. Now only tracking the namespace, instances are created like before. |
I mean that we could also just make people write: return <div>{ReactDOM.createSVGPortal(
<svg>
<path />
</svg>
)}</div>; I'm merely making the point that this whole thing is mostly just a convenience. Any additional cost in file size and performance needs to take that tradeoff into account. But it seems like we can get it pretty small. |
} | ||
} | ||
if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the foreignObject
itself should be the SVG namespace but then get next namespace underneath it should be HTML.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add an assertion that the <svg />
tag itself has the SVG namespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shame on me!
It wasn't quite clear from the API which context was being returned by the renderer. Changed the API to specifically ask for child context, and thus to pop it before getting the current context. This fixes the case with <foreignObject> to which I intended to give SVG namespace.
This gave me an idea. Feel free to ignore if you're tired of exploring my whims :) and I can take a look myself.
|
I’ll try that. 😄 |
We create stacks lazily so that if portal doesn't contain <svg>s, we don't need to allocate. We also reuse the same object for portal host context state instead of creating a new one every time.
Closing as we decided to go with a cheaper approach. |
I added a test for SVG. For it to pass, we need to keep track of the closest host container (e.g.
svg
orforeignobject
) on the stack.Since we need the stack anyway, I also used it to append initial children in the begin phase instead of the complete phase. I am using two separate stacks: one for "container" nodes (such asChanged to use a single container stack per review comments. I can do the parent stack in a follow up.svg
andforeignobject
) and another one for any "new" nodes so that we can add children to them immediately.