Skip to content

ui(coordinateTrainer): Snabbdom JSX syntax refactor [experiment] - #21690

Draft
Simek wants to merge 10 commits into
lichess-org:masterfrom
Simek:ui-coordinate-trainer-jsx-experiment
Draft

Simek wants to merge 10 commits into
lichess-org:masterfrom
Simek:ui-coordinate-trainer-jsx-experiment

Conversation

@Simek

@Simek Simek commented Sep 15, 2026

Copy link
Copy Markdown
Member

Why

With custom snabbdom elements having small issues and continuous growing imports count (one fucntion per element used) I have decided to give the Snabbdom JSX syntax one more try, on very small and well encapsulated module which is coordinate trainer.

How

Enable JSX notation in coordinateTrainer, adjust Oxlint rule (we cannot avoid importing jsx since Snabbdom does not support jsxImportSource TS config), test out which exact syntax is required, make sure that module works just like before the refactor. All files including JSX has to be renamed to *.tsx to make sure TS interprets them correctly, and editors provide better autocompletion suggestions.

I have also extended the default Snabbdom JSX to flatten the tags structure and allow avoiding "attrs" prop, in familiar fashion it was done for custom elements, also aligning source code more to the actual DOM output.

Using JSX syntax also:

  • give use null/undefined/false children support for free, in cases when previously hl extend was used to solve that,
  • allow avoid arguments overwrite issue with custom tags with text content being a valid CSS selectors
  • is fully supported by IDEs/editors LSP, an makes the view code easier to read with multiple element nesting and makes it distinguishable from logic code in large files
  • slightly lowers the entry for new contributors, since most web devs are more familiar with XML like syntax for DOM

LoC

  • view.ts: 131 → 134 (+3)
  • side.ts: 283 → 267 (-16)

Bundle size

CoordinateTrainer bundle without JSX:

  • Raw: 30,434 bytes (~29.7 KiB)
  • Gzip: 8,269 bytes (~8.1 KiB)

CoordinateTrainer bundle with JSX:

  • Raw: 30,578 bytes (~29.9 KiB)
  • Gzip: 8,326 bytes (~8.1 KiB)

@jonbgamble

Copy link
Copy Markdown
Contributor

I am jon and I support this endeavor.

@Simek
Simek force-pushed the ui-coordinate-trainer-jsx-experiment branch from 8d453b4 to 607953c Compare September 16, 2026 09:37
@Simek
Simek marked this pull request as ready for review September 16, 2026 10:00
@Simek

Simek commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

PR should be ready for review, happy to answer any questions, and reason more why I think it's a move in good direction. 🙂

@Simek
Simek requested a review from ornicar September 16, 2026 10:45
@jonbgamble

Copy link
Copy Markdown
Contributor

I am actively using these commits already.

I wanted to see how it compared against a domDialog that had grown a bit unwieldy. Spoiler alert, I like the vdom/jsx better than the older domDialog version.

There is a profound structural reason the jsx & language server combo is a time saver compared to old snab. Balancing begin/end tags with helpful IDE feedback beats trying to figure out whether a closing square bracket, closing parentheses, or closing curly brace is needed at a particular point after pasting some code. Those problems are much reduced.

Snabbdom's event listener module pairs well with this due to the jsx constructor hoisting the on attr (so code stays pretty).

@Simek, can haz "bracketSameLine": true in .oxfmtrc.json?

@Simek

Simek commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

@Simek, can haz "bracketSameLine": true in .oxfmtrc.json?

We can try that in a separate, follow-up PR and see how it looks/feels in the whole codebase, since it's a setting mostly for XML-like languages it should not have a big impact overall. But at the end it's not my call to accept it.

Ref: https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html#bracketsameline

@Simek
Simek marked this pull request as draft September 18, 2026 10:26
@jonbgamble

Copy link
Copy Markdown
Contributor

Why draft?

@Simek

Simek commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

I need to resolve the request to inject non-dynamic classes and IDs to selector and combine them with tag, for performance reasons, ref:

Might also try constructing key dynamically for JSX nodes, it should result in the same optimization, but both approaches need some testing:

@jonbgamble

jonbgamble commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

I need to resolve the request to inject non-dynamic classes and IDs to selector and combine them with tag, for performance reason

I'd be interested in performance metrics illustrating a slowdown due to reduced static selector identity enforcement. The analysis movelist tree view is the most intense workout for snab patching by far. I don't think there's a single selector used there. It's all plain tags.

Being forced to manage keys is something I currently like about this PR. It comes in a distant second to end tag balancing in my editor but it's still a plus.

https://discord.com/channels/280713822073913354/352976626558042122

I worry that secret dynamic key construction behind the scenes could just introduce ugly edge cases. I like the simplicity of this PR.

@ornicar

ornicar commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

I think there's going to be intense and hard to detect breakage if we don't have snabbdom sel. It's currently used everywhere our elements have classes. When JSX lands it will change all the sels, removing classes and IDs. Dog knows what will break.

@jonbgamble

Copy link
Copy Markdown
Contributor

Are you considering this merge with the expectation of porting existing code? This codebase is not small enough where the cost of global refactors can be justified by increased developer ergonomics (which is a fancy synonym for "cosmetics").

Asking devs what they prefer is useful info, and their willingness to understand and handle both approaches is also key. LLMs do not care and most engineers who do their own coding can choose for themselves.

It makes sense for Simek (currently the only one besides me on team jsx) to migrate a module only in the course of adding functional behaviors, or maybe if he's bored, but only with full knowledge of node identity pitfalls and proper testing. The closing tags cheat code for easier editing is not worth the hurt of porting all legacy constructors for the sake of uniformity.

For new work, there is no need to build out a sel compatibility because that mechanism already exists as key. I suppose I'll wait to see what Simek comes up with, but if it involves heuristics like "such and such an attribute/class and only when there's lifecycle hooks gets magically hoisted to sel"... that would probably end up a footgun.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants