-
Notifications
You must be signed in to change notification settings - Fork 128
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
RFC: Vision for Layout Conformance/Parity #540
base: main
Are you sure you want to change the base?
Conversation
This adds a document outlining our long-term vision for adding new layout features to React Native, while moving the ecosystem towards web compliant behavior. It is a long-term, relatively ambitious plan, that we intend to make concrete progress on over the next several months. Feedback on what parts folks agree with, want, or don't want, are welcome.
…conformance-capabilities-vision.md
…for-layout-conformance-parity.md
<View style={calculateStyle()} />; | ||
|
||
// After babel transform | ||
<View style={StyleSheet.compose(calculateStyle(), {_applyOrigin: 456})} />; |
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 would let the runtime provide a trace of where the native is that passed an invalid style, but it wouldn't provide the definition site, is that right?
For example, in the Facebook app we have a layer of indirection called like that internally renders a <View style={StyleSheet.compose(styles.view, props.childStyles)} />
.
And then all the products call <FacebookButton childStyles={styles.invalidStyle} />
. Would those all blame to the lines in <FacebookButton>
or the callsites in product code? For debuggability, is there a way to mark the product callsite?
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 think it depends on how we would decide to handle compound (multiple in array), or composed styles. The style of a ShadowNode can come from multiple contributing sources.
For the simpler case where there is a single style, the scheme in the example is to embed a single style-application-site and a single style-creation-site. Though if we associate one shadownode to a single style creation site, there is lossiness as styles are composed (some style takes precedence). So the right model might be to instead allow associating n
style creation sites per style application site.
We should have react-native style system like tailwindcss which make the JSX code much more easier to maintain. |
Co-authored-by: Fabio M. Costa <[email protected]>
3. **Box-sizing:** While not strictly related to Yoga, react-native offers a box-sizing similar, but reportedly not quite in line with `box-sizing: 'border-box'`. More investigation is needed into the exact behavior. | ||
4. **Two-pass flexible item resolution:** Yoga [deviates](https://github.com/facebook/yoga/blob/5dd33acc912711c599250e3533b07c732de503ee/yoga/Yoga.cpp#L2365) from the W3C spec on how the lengths of flexible items are resolved, implementing a simpler/more performant algorithm. | ||
5. **Content-based minimum size:** Yoga intentionally [does not](https://github.com/facebook/yoga/blob/5dd33acc912711c599250e3533b07c732de503ee/yoga/Yoga.cpp#L2662-L2665) assign a minimum size to flexible items in deviation from the flexbox spec, for performance reasons. | ||
6. **Min/Max in flexible items:** At least according to [in-code documentation](https://github.com/facebook/yoga/blob/5dd33acc912711c599250e3533b07c732de503ee/yoga/Yoga.cpp#L2666-L2667), Yoga does not respect min/max size constraints on the main axis for flexible items. Because of a lack of support this is unlikely to affect existing RN code, but affects code ported from web. |
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.
Interesting - I was wondering how it only used two layout passes. In the spec with min/max sizes, it's between 1 to N iterations to properly size the items
9. **Viewport based units `(vmin, vh, vw, etc)`:** React Native + Yoga does not support units derived from viewport/root node size. | ||
10. **Font based units `(em, rem, etc)`:** React Native + Yoga does not support units derived from the size of the current font. Configurable `fontSize` in React Native is currently confined to text components, and will not cascade. Its pixel value would then be scaled by `fontSize` if set on a Text component or by system default font if unset on Text, or on a View. | ||
11. **Percentages in more places:** Some existing functionality like gap is implemented with support for pixels, but are missing support for percentage. | ||
12. **`min-content`:** As part of not supporting content-based minimum sizing, the `min-content` keyword is not supported. |
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 suspect this isn't worth adding unless we add min/max sizing for flex basis. It's almost always used for min/max sizes - I've literally never seen it used outside of those two properties
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.
It would probably make sense to add support for max-content
and fit-content
if this is being added.
1. **`display: grid`:** CSS Grid is highly used in responsive designs on the modern web, allowing some layouts which are hard or not possible to express with Flexbox. It is one of the most requested additions, but has one of the highest implementation cost of the listed examples. | ||
2. **`display: inline/block/table/ruby`:** Yoga treats text as inline, but other nodes act as `display: flex`. The listed display modes could help compatibility, but Flexbox has superseded much of their usage. | ||
3. **`display: contents`:** Useful for composing boxes which should not alter child layout (e.g. embedding event handlers)`.` | ||
4. **`position: fixed/sticky`:** React native does not support `position: fixed` or `position: sticky`. React Native’s closest API is `stickyHeaderIndices`, which allows top-level ScrollView items to be sticky. |
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 think these will always be code on top of a scrollview - rather than something yoga does. Not to say we can't make the API look the same as browsers, and get RN to handle these styles
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.
Yeah, this one is more a layer after Yoga, and the implementation probably isn't in Yoga. But deals with "layout" in the sense of positioning.
Thanks for this write-up! It's really comprehensive, and I definitely learned a lot from reading it |
|
||
## Mitigating bloat to heap usage | ||
|
||
Yoga internally represents a stylesheet as a packed structure of all possible properties. This structure is already 192 bytes, scaling with each additional Yoga Node. As we add more styles, this could expand. Doubling the number of styles on a tree of 20,000 nodes would increase memory consumption by almost 4MB, a huge amount for some of the devices React Native targets. We should measure, and potentially switch YGStyle to a data structure which is pay-for-play in number of properties. This shift would be performance sensitive, but theoretically quick data structures exist (e.g. a bitfield header with a small-vector for larger values). |
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 wonder if we got RN to handle the cascading of margin, padding etc. properties, if we could reduce this by a little bit
RN already stores each value (each direction + horizontal + vertical + all) in an array, then applies each to the yoga node - https://github.com/facebook/react-native/blob/a9bed8e75d9b9613c5fcb69436a2d3af763f456d/React/Views/RCTShadowView.m#L99
We could literally just do the resolution here and only support the directional ones on the yoga node
That'd be 3 properties across each margin, padding, and border (9) + gap to give a total of 10 properties - a reduction of 40 bytes if I'm not mistaken
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 did something a bit similar for aliased props in Fabric recently facebook/react-native#35342. But I discovered the Fabric design for ShadowNode property storage runs into the same issue as YGStyle, and there are even two separate copies of YGStyle (one in ShadowNode props and the other on the Yoga node) so we are IIRC at 1.2KB of empty props per-ShadowNode already.
I think fixing that up in Fabric is a goal some folks are taking up next half, and it could deliver even bigger wins than just YGStyle itself, though YGStyle currently takes up a big portion of per-shadownode memory usage.
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.
If the cascade was completely handled on the RN side, so we could also just use direct access on the properties without having these helpers to compute margins etc. - and might get a slight perf boost too
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.
If the cascade was completely handled on the RN side, so we could also just use direct access on the properties without having these helpers to compute margins etc
Taffy is moving in this direction. We now have a trait (interface) which abstracts over node/style storage, and the layout algorithms depend only on that trait and not any specific storage implementation.
We should measure, and potentially switch YGStyle to a data structure which is pay-for-play in number of properties.
We're also looking at doing this. Bevy-ui (which uses Taffy for layout) has a PR implementing this for their style layer even though they currently have to reconstitute an entire Style
struct when passing styles to Taffy. The size problem also gets quite a bit worse with CSS Grid support, and we're hoping to support arbitrary pluggable layout modes which would of course make a single Style
struct unworkable.
|
||
React Native, and much of the rest of the OSS ecosystem rely on Yoga. Yoga has been in maintenance for the past few years, with the originally owning team not engaging with the community over the last few years. Yoga has had contributors who file issues, and have submit PRs fixing conformance issues (e.g. fixing stale measurements), and adding whole new features (e.g. `gap`/`rowGap`/`columnGap`). | ||
|
||
Beyond adding new capabilities directly, we have a long road to enable a technical and social environment which allows contributions to Yoga. We need to fixup the tools used by OSS to build changes against Yoga, and be meaningfully responsive to incoming changes and issues. This is a huge undertaking, that we think is worth it not just for React Native, but the broader community using Yoga. This was compared against several alternatives: |
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.
As a potential contributor to Yoga, these fixes to the build system (and timely ongoing maintenance of the same) would definitely be a priority for me. I tried to get a development environment setup for Yoga recently, and while I was able to get Yoga to build using cmake, I am currently unable to run the Yoga test suite. The documented way of doing so is to use Buck. But Buck doesn't run on Apple Silicon machines (or certainly not easily - getting it to compile would at least involve applying patches before compiling from source and my initial attempts to do this failed). So I would suggest either an (additional - I assume Meta will want to continue using Buck internally?) alternative build system is supported/documented, or that Buck given a similar "revive level of maintenance" as is proposed for Yoga here.
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 100% agree with this, and I've kind of made it worse by the number of times I've told folks I would fixup a CMake and GTest setup before immediately getting sidetracked by something else 🙂. This is a good reminder on that.
|
||
### Taffy | ||
|
||
[Taffy](https://github.com/DioxusLabs/taffy) is a Flexbox layout engine written in Rust, authored by Emil Sjölander, a main contributor of Yoga. It is seeing active development under new hands, and has some capabilities that Yoga doesn’t (e.g. it recently added `align-items: 'space-evenly'`). It does not have bug compatibility with the existing RN ecosystem, its rollout/production usage is unknown, and being owned outside of Meta limits our ability to direct the engine in ways which are targeted towards RN’s use-cases. |
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.
As a maintainer of Taffy and a user of react-native, I can say that I would absolutely be willing to add legacy bug compatibility to Taffy (behind a build-time flag) if that meant adoption by RN. There are couple of other bits that would be missing though: like RTL support and I suspect some things around text/columns.
However, I can't help with it being owned outside of Meta. Although I would comment that moving more of RN outside of Meta ownership (and into meta co-ownership with the community) would probably help with another stated RN project goal "More engagement on PRs". Am I right in thinking it is currently only possible for Meta employees to be maintainers of Yoga (merge PRs, publish releases, etc)? This seems to me to be a root cause of Yoga's current state of unmaintenance, and a good thing to try to change if such a change is possible.
FWIW, most of our production usage comes from it's usage in the UI for the Bevy game engine which works out at around ~30k download/months
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 hope that we can collaborate even if Taffy isn't actually adopted by RN (which is an entirely reasonable decision). The code bases are still quite similar in many places (I was able to port Yoga's implementation of flex-gap to Taffy relatively simply). And in particular the test suites still work in the same way (and the majority of the tests are identical in content) and would be an obvious thing to synchronise across projects.
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.
Wow this is super helpful to know. Taffy targeting RN quirks (and feedback for hitting RN quirks) seems like a viable way to reuse a lot of the great work you all have done, and to share the work being done by folks contributing to RN.
Beyond specific project ownership, a benefit of RN having primary ownership of a layout engine is that we can make choices in accordance to the priorities of RN. But that might be indexing too much into the specific case where RN was less aligned to the goals of Yoga 2/FlexLayout. I would definitely be interested in learning more about how RN’s usage aligns with Taffy’s goals.
Apart from the ideological goal, there’s a practical question of “how do we build enough confidence the behavior is close enough to swap out a layout engine?”. And I don’t think we have a good answer for that yet. We can try to do our best to find the set of non-conformant Yoga behavior to replicate, and then emulate that. But there’s such a wide variety of RN usage which could lose consistency in “quiet” ways that I would expect a long tail with a lot of breakage even if we were methodical. Runtime output diffing (throw if layouts disagrees) is one solution we can use during tests of real internal apps, but it’s too expensive to run in production applications. I also have some dream where we can take many real-world snapshots of nodes in an app (e.g. YGPrint output) so that we can catch changes to output and performance of some more real world cases in greater isolation.
RN using Yoga also helps us to justify work to Yoga that affects users beyond RN. I don’t know what the long-term of that would look like if RN used an engine outside of Yoga.
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.
On ideology / project goals:
I'd say Taffy's goal is somewhat similar to Yoga's in that it intends to be a general-purpose layout library which is easily embeddable. Making this available to the Rust ecosystem is definitely a priority for us, but binding to other languages are definitely on our radar, and something we'd like to add. Where there is perhaps some divergence is that Taffy is keen on becoming layout-algorithm agnostic by defining a trait/interface that allows anybody to plug in their own layout algorithm (i.e. measure funcs on steroids). This is driven in large part by the wider Rust GUI community, not all of whom are keen on web-style layout. Whether this ends up being in conflict in with being fully web-compatible remains to be seen. But trying to allow interoperation with arbitrary layout modes while keeping the shared communication protocol keen certainly makes things more complicated.
On a personal note: my goal is really to enable a truly cross platform GUI solution (across all 5 major platforms: ios/android/windows/mac/linux). I feel like React Native is currently one of the best options for that (although it's state on windows and especially mac is frustrating). And Rust GUIs like Druid/Xilem, Iced, and Dioxus also look promising in this space.
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.
RN using Yoga also helps us to justify work to Yoga that affects users beyond RN. I don’t know what the long-term of that would look like if RN used an engine outside of Yoga.
Presumably the idea would be for that work to go into that other engine instead (which would also be open source and available for others to use). Although I suspect there are a significant number of people/projects already using Yoga who would like to maintain it anyway either because they don't want to switch APIs, or because they prefer a library written in C++. Is Meta unwilling to use and/or commit resources to projects that it does not solely maintain? If is it willing to do this, then I feel like the best thing for Yoga (regardless of whether RN continues to use it) would be to open up the maintenance to the wider community. There are clearly a lot of people willing to contribute (lot's of seemingly reasonable quality PRs), and it seems like the bottleneck is maintainer time. Are there any precedents for a more shared ownership model like this? If Meta isn't willing to go for a shared model, then I guess it would be best to make a clear decision: either commit to maintaining the library or release it to the community to maintain themselves.
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.
Apart from the ideological goal, there’s a practical question of “how do we build enough confidence the behavior is close enough to swap out a layout engine?”.
Hmm... yes, I can definitely see that validating that a new implementation is truly compatible is a much harder task than simply adding a compatibility mode. I have to admit I am somewhat bemused by the focus on backwards compatibility. RN makes all sorts of breaking changes pretty much every release, although I suppose layout probably affects more code than most parts of RN (and also has the unfortunate property that it would likely silently render something different rather than failing).
I wonder if a transition to a new layout engine could be handled similar to the transition to "new architecture", with an opt-in flag at the application level. Library authors could then test and update their code to be compatible, and application developers the same. I guess the issue in this case is how much code would need to be updated at once? It could also be possible to ship both layout engines for a transition period, although I can definitely see that at that point it might make more sense just to stick to the original engine.
I would also bring up the point that if the goal is to be web compatible, then there will probably need to be breaking changes on an ongoing basis (albeit probably smaller than the initial diff), because web browsers themselves make breaking changes to align themselves with the spec, and any RN implementation will almost certainly contain spec-incompatible bugs. It's a fundamentally different approach to making the implementation definitive and keeping things 100% backwards compatible.
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.
Re compatibility, a big difference with other breaking changes is in signal and actionability, like you mention. Most APIs we change can be detected statically via type checking or the compiler, or scream loudly at runtime. So these are detectable. The nature of layout behavior changes is like you say also tricky because it scales to every view of every app, instead of more confined scenarios.
For companies with large existing codebases using RN, I don’t think we could tell folks “Try turning on this flag, and you might see UI changes, but we don’t know where”. Folks just wouldn’t switch, even if we could demonstrate compelling improvements. We would then need to support both layout engines in perpetuity. Though part of the “traceable styles” goal is that we can start shifting product code in the ecosystem still being developed which is reliant on broken behaviors.
Fabric rollout has not yet been a success story in OSS. There are a lot of folks working now to instead start bridging the APIs, because users with large codebases have been reluctant to migrate. But there is a lot of effort to make that path easier. It reinforces that adding a flag to switch isn’t enough, and even after migrating a giant codebase internally, we are still finding bugs.
Re browser breaking changes, it’s something I think we might be able to follow. There are some correctness changes like UseLegacyStretchBehavior that we know broke too many apps to enable for everyone, but Chrome changing behavior for the entire internet means some level of confidence they are not creating breaks everywhere. Really we need a way to know “how breaking” the change is before making it. One of the more powerful ways we have to do this is rolling the change out to a portion of Meta app users and monitoring if behavior changes. I.e. given a large enough set of code, does something break enough to shift user behavior. In those cases we can likely go ahead and make the break, though that measure is still lossy.
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.
Re ownership model, while Meta is still relying on Yoga as critical infrastructure, I think there will be a desire to retain ownership of it. React Native is definitely the main user of Yoga inside of Meta. But really getting off of Yoga, both for the thousands of surfaces inside Meta, and more in the community we can’t reach into and fix, ends up being very expensive, and prone to risk in fragmenting the paths we need to maintain without being able to remove the old path. These sort of large migrations really end up having an opportunity cost on what we could instead provide on the existing infrastructure, along with a cost to our users, where we’re really trying to focus now on making our upgrade story less painful.
In a world where React Native wasn’t using Yoga, I don’t think it’s likely we would have someone to step in to maintain it. If we really didn’t have anyone inside of Meta using it or able to maintain it, I do like the idea of transferring ownership somewhere more shared, to allow the community to take a more active role in maintaining it. But I do genuinely believe RN using Yoga, and really wanting to invest in it, has the potential to help a project that is still being used throughout the industry.
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 still have worried a bit we could veer into fragmentation with StrictLayout, even if we put shiny new bits like grid behind it. It lets us make those breaking changes more confidently, and lets users fix code on a more granular level. E.g. even for a flex basis caching bug we wanted to fix, it was too expensive to do it app-wide in a relatively large app. But, my hope is that starting with a single layout engine which can alter its conformance level puts us in a position to sort of start priming the ecosystem for different behavior without yet taking the path of supporting multiple engines.
|
||
## Example capabilities | ||
|
||
The following is a non-exhaustive list of capabilities where Yoga lags browsers. We should consult specs used by yoga (CSS Box Alignment, CSS Box Sizing, CSS Flexbox, etc) for a more exhaustive list. |
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.
Consider adding contain
and in particular contain: layout
to this list (https://developer.mozilla.org/en-US/docs/Web/CSS/contain). I believe this could lead to significant perf improvements for expensive layouts where it is relevant (although this would of course need to be measured). It effectively allows sub-trees to be computed independently of their context.
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.
Isn't this only relevant if you support float? What stops us doing independent subtree layout already?
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.
It's relevant for content sizing (e.g. auto
flex-basis). If a node has contain: layout
on it then the layout algorithm can assume that the content size of that node is zero. I believe this effectively turns multi-pass layout into single-pass layout for the affected node.
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 could be wrong - but my understanding is contain: layout
requires you to explicitly set width
and height
. But if you've done that, and as long as you don't invalidate those values with stuff like {min,max}{Width,Height}
, and your flex{,Shrink,Grow}
values are all zero, I think you can apply the same optimisations as if contain: layout
was set: you should be able to compute the whole subtree independently to the parent (i.e. another thread).
Because we're a subset of CSS and don't support float
, I don't think we actually get any more guarantees with contain: layout
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.
Ok, so it turns out that contain: layout
does not have the effect I was expecting, but contain: size
does.
Here's an example where contain: size
differs from width: 0px
for CSS grid:
<div style="display: grid; grid-template-columns: auto;grid-template-rows: 40px 40px">
<div style="contain: size">
<div style="width: 200px"></div>
</div>
<div style="width: 100px"></div>
</div>
With contain: size
you get a width of 100px
for first child. With width: 0px
you get a width of 0px
(and with neither you get a width of 200px
). Conceptually this represents the notion "I should be sized by the container, but my content size should not be taken into account when doing this sizing"
And here's an example with Flexbox:
<div style="display: flex; flex-direction: column; align-items: stretch; height: 100px">
<div style="contain: size;flex: 1">
<div style="width: 200px"></div>
</div>
<div style="width: 100px; flex: 1"></div>
</div>
(here it affect cross-axis size)
This is perhaps a bit more niche than I thought (some if not cases can indeed be achieved by application of already existing styles), but I think it is worth considering.
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.
@nicoburns Am I missing from the examples? https://jsfiddle.net/0qbs8pku/2/ - if I take out contain: size
, nothing changes
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.
@jacobp100 Ah, try adding position: absolute
to the root node of each example (or some other attribute which prevents the root node from stretch-fitting into it's parent. Apologies: my test env had a stylesheet that was adding this for me.
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.
Ah yeah that works - can definitely see the difference now. Thanks for the example!
Will need to do some more reading on how this improves performance. I'm not really seeing how it buys us anything over what I said above. But there must be a reason I'm missing - considering every browser did this
|
||
## Mitigating bloat to heap usage | ||
|
||
Yoga internally represents a stylesheet as a packed structure of all possible properties. This structure is already 192 bytes, scaling with each additional Yoga Node. As we add more styles, this could expand. Doubling the number of styles on a tree of 20,000 nodes would increase memory consumption by almost 4MB, a huge amount for some of the devices React Native targets. We should measure, and potentially switch YGStyle to a data structure which is pay-for-play in number of properties. This shift would be performance sensitive, but theoretically quick data structures exist (e.g. a bitfield header with a small-vector for larger values). |
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.
If the cascade was completely handled on the RN side, so we could also just use direct access on the properties without having these helpers to compute margins etc
Taffy is moving in this direction. We now have a trait (interface) which abstracts over node/style storage, and the layout algorithms depend only on that trait and not any specific storage implementation.
We should measure, and potentially switch YGStyle to a data structure which is pay-for-play in number of properties.
We're also looking at doing this. Bevy-ui (which uses Taffy for layout) has a PR implementing this for their style layer even though they currently have to reconstitute an entire Style
struct when passing styles to Taffy. The size problem also gets quite a bit worse with CSS Grid support, and we're hoping to support arbitrary pluggable layout modes which would of course make a single Style
struct unworkable.
3. **`display: contents`:** Useful for composing boxes which should not alter child layout (e.g. embedding event handlers)`.` | ||
4. **`position: fixed/sticky`:** React native does not support `position: fixed` or `position: sticky`. React Native’s closest API is `stickyHeaderIndices`, which allows top-level ScrollView items to be sticky. | ||
5. **`box-sizing`:** React Native does not support specifying box-sizing, and anecdotally is closest to but does not fully conform to `border-box`. implementing box-sizing allows new modes which may be conformant. | ||
6. **`align-content: space-evenly`:** Yoga does not supported `space-evenly` as a value for flexbox alignment. |
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 trivial to support. The change to support this in Taffy was a single line (+tests +adding the variant to the style enum). (See: https://github.com/DioxusLabs/taffy/pull/218/files#diff-04a9f0a39fa5186275beb88d6b9c3a4ce1dff58c01c68e5743af6975c4e7e88c)
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 take a crack at this once the dust has settled for gap
9. **Viewport based units `(vmin, vh, vw, etc)`:** React Native + Yoga does not support units derived from viewport/root node size. | ||
10. **Font based units `(em, rem, etc)`:** React Native + Yoga does not support units derived from the size of the current font. Configurable `fontSize` in React Native is currently confined to text components, and will not cascade. Its pixel value would then be scaled by `fontSize` if set on a Text component or by system default font if unset on Text, or on a View. | ||
11. **Percentages in more places:** Some existing functionality like gap is implemented with support for pixels, but are missing support for percentage. | ||
12. **`min-content`:** As part of not supporting content-based minimum sizing, the `min-content` keyword is not supported. |
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.
It would probably make sense to add support for max-content
and fit-content
if this is being added.
The following is a non-exhaustive list of capabilities where Yoga lags browsers. We should consult specs used by yoga (CSS Box Alignment, CSS Box Sizing, CSS Flexbox, etc) for a more exhaustive list. | ||
|
||
1. **`display: grid`:** CSS Grid is highly used in responsive designs on the modern web, allowing some layouts which are hard or not possible to express with Flexbox. It is one of the most requested additions, but has one of the highest implementation cost of the listed examples. | ||
2. **`display: inline/block/table/ruby`:** Yoga treats text as inline, but other nodes act as `display: flex`. The listed display modes could help compatibility, but Flexbox has superseded much of their usage. |
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'd suggest that inline
and ruby
are different to block
and table
here:
inline
andruby
require deep integration with text layout which IMO means they might be better off being implemented in a separate library that handles the full complexity of rich text.block
andtable
are much more similar to flexbox and grid in that they only deal with boxes.
|
||
Where new capabilities are additive, changing default behavior, or existing layout algorithms would affect the way existing components renders. Even if the new behavior is correct, we must be conscious to preserve compatibility with the vast ecosystem of existing React Native components. | ||
|
||
## Strict layout |
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.
@NickGerleman
I would like to propose an alternative to this. This proposal is inspired by React's handling of the deprecated lifecycle methods like componentWillReceiveProps
by renaming the method to UNSAFE_componentWillReceiveProps
and providing a codemod that automatically applied the migration.
Instead of a StrictLayout
component that affects the entire tree below it, I would like to propose that the default behaviour of View
is changed to the new web compatible behaviour, and that a new LegacyView
component (or an alternatively a useLegacyLayout
prop on the View
component) is introduced which opts back into the legacy layout algorithm for that view only. Additionally a codemod would be provided that would automatically convert all View
s to LegacyView
.
If you wanted to be more sure that people didn't forget to run this codemod, then the View
component could also be renamed to some other name (making not running the codemod a build error). Perhaps in order to compensate for View having a less convenient name, new Row
, Column
and Grid
components could be introduced which function like View
but with the layout algorithm and orientation already set.
|
||
We know about some subset of conformance issues, but there are likely to be more, and as we add new capabilities, we must be systematic in ensuring a correct implementation. Browsermakers share a set of tests for validating conformance called [WPT (Web Platform Tests)](https://github.com/web-platform-tests/wpt/tree/master/css). These have already been leveraged by React Native for pointer events, by porting the HTML to React Native. An alternative to porting the code may be to rely on Yoga’s existing capabilities of generating unit test derived from how Chrome lays out HTML fixtures. | ||
|
||
## Specific W3C conformance issues |
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.
You can add aspect-ratio
to the list here. Yoga's implementation of aspect-ratio
predates the property being added to CSS (by around 3 years), and there are a number of comments around the Yoga codebase stating that the aspect-ratio property is not spec compliant.
This adds a document outlining our long-term vision for adding new layout features to React Native, while moving the ecosystem towards web compliant behavior. It is a long-term, relatively ambitious plan, that we intend to make concrete progress on over the next several months. Feedback on what parts folks agree with, want, or don't want, are welcome.