Skip to content

fix(runtime): support CSS v-bind() on Lynx native#144

Open
KealanAU wants to merge 18 commits into
Huxpro:mainfrom
KealanAU:fix/opacity-investigation
Open

fix(runtime): support CSS v-bind() on Lynx native#144
KealanAU wants to merge 18 commits into
Huxpro:mainfrom
KealanAU:fix/opacity-investigation

Conversation

@KealanAU

@KealanAU KealanAU commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Background-Thread-safe support for v-bind() in Vue <style> blocks on Lynx native.

Compatibility

Verification

  • pnpm test:local in packages/upstream-tests
  • Verified in the Lynx native simulator: background-color, color, font-size, and opacity all update via CSS v-bind().
Before After
CleanShot.2026-04-05.at.15.29.59.mp4
CleanShot.2026-04-05.at.15.07.14.mp4

KealanAU and others added 12 commits April 4, 2026 18:48
- Move use-css-vars.md and LYNX-ISSUES.md from runtime/src/ to
  packages/vue-lynx/docs/ so rslib does not try to bundle them
- Restore root package.json private: true (was accidentally set to false)
…seCssVars

Two critical fixes:

1. Add onBeforeUpdate + onMounted wrapping (matching upstream Vue):
   - patchProp's SET_STYLE overwrites CSS vars on every re-render since
     it doesn't know about them. Without onBeforeUpdate, CSS vars silently
     vanish whenever a style binding changes but the CSS var value doesn't.
   - VNode tree changes (v-if, v-for) also need CSS vars re-applied to
     newly created root elements.

2. Restrict SF_ARRAY_CHILDREN walking to Fragment vnodes only:
   - The standalone `if (shapeFlag & SF_ARRAY_CHILDREN)` also fired for
     element vnodes with children, applying CSS vars to every descendant
     element (O(N) SET_STYLE ops). Root elements are sufficient since
     enableCSSInheritance cascades vars to descendants.
- Move v-bind() from "Upcoming" to "Works (requires config)"
- Add required config snippet (enableCSSInlineVariables + enableCSSInheritance)
- Document the known layout-property limitation
- Remove the workaround snippet (no longer needed)
- Update both EN and ZH versions
Revert element-registry.ts and TransitionGroup.ts type annotation
changes that were rejected in PR Huxpro#122 review. Add changeset for
the v-bind() CSS vars feature.
@vercel

vercel Bot commented Apr 5, 2026

Copy link
Copy Markdown

@KealanAU is attempting to deploy a commit to the huxpro's projects Team on Vercel.

A member of the Team first needs to authorize it.

@KealanAU KealanAU marked this pull request as ready for review April 5, 2026 05:33
@vercel

vercel Bot commented Apr 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vue-lynx Ready Ready Preview, Comment Apr 5, 2026 0:03am

@Huxpro

Huxpro commented Apr 5, 2026

Copy link
Copy Markdown
Owner

@KealanAU Thanks for the thorough investigation and the detailed PR description -- the root cause analysis of the Lynx engine's CSS var inheritance gap is spot on, and the isolation test (CSSInheritanceTest.vue) is a great addition.

I've been digging into this and wanted to share where I've landed.

What we've verified

I traced through the Lynx engine source and confirmed the bug. The root cause is that FiberSetInlineStyles calls UpdateCSSInlineVariables to store --* properties, but never calls RecursivelyMarkChildrenCSSVariableDirty afterward -- so descendants never see the change. The working path (FiberElement::UpdateCSSVariable, used by setProperty) does all three steps: MarkCustomPropertiesDirty, MarkStyleDirty, and RecursivelyMarkChildrenCSSVariableDirty. I've updated the upstream issue with the full analysis and suggested fix: lynx-family/lynx#5889

I also verified on Lynx Explorer 3.8 that same-element CSS vars (set and consumed on the same element via inline :style) work, but parent-to-child inheritance does not. This confirms the workaround is necessary on current engine versions.

Separate fix for entry.ts

The entry.ts change (from encodeData.compilerOptions to encodeData.sourceContent.config) is actually a pre-existing bug from #127 -- the CSS config options were being injected into the wrong section and never reaching the engine. I've split this out into a standalone fix: #145.

Performance concerns with stampElementDescendants

The workaround stamps CSS vars on every element in the component subtree, which means:

  • O(N) SET_STYLE ops per CSS var change, where N = total elements in the subtree (instead of O(1) for root-only stamping)
  • O(N) on every component re-render (not just when CSS vars change), because onBeforeUpdate re-stamps the full subtree to compensate for patchProp overwriting the merged styles
  • Cross-component double-stamping: if a parent and child component both use v-bind() in CSS, the parent's walk stamps into the child's subtree, then the child's own useCssVars stamps them again

For small components this is fine, but for components with 100+ elements or frequent updates, this could become a bottleneck.

Questions

Before merging this, I'd like to understand:

  1. How urgent is this for you? If it's not blocking, I'd prefer to first see how hard the upstream fix is (the suggested change in lynx#5889 is fairly contained -- adding ~5 lines after the ForEachLepusValue loop). With the engine fix, we'd get O(1) root-only stamping with no workaround needed.

  2. If we do merge the workaround, I think we should document the performance characteristics so users know to prefer inline :style for performance-critical reactive values in large components.

Thanks again for all the work here -- the examples, tests, and the debugging that led to finding the entry.ts config bug are all valuable.

@KealanAU

KealanAU commented Apr 5, 2026

Copy link
Copy Markdown
Contributor Author

@Huxpro I totally agree that O(N) is terrible to pay on every change. I'm happy to wait for
the upstream engine fix instead, then update or close this PR. Since you've narrowed it
down to the lines and functions in lynx#5889, the O(1) solution is definitely the long-term
solution — we don't want a short-term fix that ends up sticking around long-term.

I did think it was still an upstream issue that would need to be resolved, and had made the
PR since it would be easy to pull out, but this isn't urgent — just wanting to help get
Vue working well on Lynx and learn.

Thanks for splitting it off into its own PR for the other fix and the deeper dive. I was
going to try to find the function and line in Lynx causing the problem today!

@Huxpro

Huxpro commented Apr 6, 2026

Copy link
Copy Markdown
Owner

@KealanAU I just confirmed that we can make it work by fixing lynx#5889! I'll see what the nearest release I can land it in is. Thanks for the detailed issue analysis and the comprehensive CSS feature tests! I'll get back to you when it's landed.

image

@Huxpro Huxpro self-assigned this Apr 6, 2026
Resolves conflict in entry.ts: both sides had the same fix
(sourceContent.config instead of compilerOptions). Also includes
the stampElementDescendants workaround removal and root-only
stamping tests from the current session.
@KealanAU KealanAU force-pushed the fix/opacity-investigation branch from ebb4ef3 to e80ced0 Compare April 9, 2026 16:38
@Huxpro

Huxpro commented Apr 16, 2026

Copy link
Copy Markdown
Owner

The engine fix is merged lynx-family/lynx#5912 and will be released in 3.8 (May)

@KealanAU

Copy link
Copy Markdown
Contributor Author

@Huxpro , Perfect, I'll remove the work around then and we can merge in May

@KealanAU KealanAU changed the title fix(runtime): work around Lynx CSS var inheritance gap in useCssVars fix(runtime): support CSS v-bind() on Lynx native Jun 25, 2026

@Huxpro Huxpro left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking from my side: this adds user-facing v-bind() support/docs that appear to depend on the newer Lynx engine/toolchain surface, but the compatibility baseline is still ambiguous here.

If this lands before #192, readers can reasonably take the docs as applying to the default current toolchain, while the required support may only exist on the newer release line. I think this either needs to be explicitly stacked after #192 or the docs need to state the minimum required Lynx engine/toolchain version very clearly.

Also, the linked Lynx issue references should be reconciled. I saw both lynx#5912 and lynx#5889 in the surrounding discussion/context, and that kind of mismatch makes the support story harder to trust.

…ersion

Standardize all references on the fix PR (#5912, closing issue #5889) and
document that v-bind() in CSS requires Lynx engine >= 3.8.1.
@KealanAU

KealanAU commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — both addressed.

Issue references: the two numbers are an issue↔fix pair, not a mismatch. lynx-family/lynx#5912 is the fix PR; it closes issue #5889. Every mention in the code comments, the spec, the changeset, and the docs now reads "#5912 (closing #5889)" consistently.

Compatibility baseline: you're right that this depends on the engine fix in #5912, which ships in the 3.8.1 line. I've made the requirement explicit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants