Virtualize CSS highlighting for large files - #385
Open
bartveneman wants to merge 6 commits into
Open
Conversation
✅ Deploy Preview for projectwallace ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
bartveneman
commented
Aug 31, 2026
For CSS files above ~250KB, only register Custom Highlight API ranges for tokens/locations within the visible scroll window (plus overscan), instead of the whole document, so highlight range count and AST-walk cost stay bounded regardless of file size instead of scaling with it. The visible window is tracked by a new standalone `track_viewport_window` Svelte action that measures size via CSS container query units (falling back to the viewport when no ancestor opts in via `container-type`) and scroll position via a generic nearest-scrollable-ancestor walk (falling back to document scroll). It reports changes as a `viewportwindowchange` CustomEvent on the shared `<code>` node, so it composes with the existing `highlight_css` action without either one referencing the other directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT
Per review: fold the intersects() guard into a single in_view() check inside add_range() itself, so call sites (Comment/AtruleName/Property/ Important) go back to their pre-virtualization form instead of each wrapping the call in its own condition. The AT_RULE/STYLE_RULE subtree pruning keeps its own in_view() check since it's a distinct decision (skip descending) from range registration. Also de-duplicate the schedule(cleanup + do_highlight) pattern used by both the viewport-change listener and the action's update() into one rehighlight() function. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT
Per review: raf_id was an unclear abbreviation, renamed to frame_id. Also switched every single-statement if in this file to always use braces with the body on its own line, matching what oxlint's `curly` rule (in its default "all" mode) would enforce. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT
Virtualization already bounds the walk (via SKIP-pruning off-screen subtrees) and range registration (via in_view()), but do_highlight() was still calling parse() over the *entire* document on every scroll-triggered rehighlight, not just on content changes - that's the one cost left that scales with file size regardless of scroll position, and the main remaining source of jank on large files. Cache the parse result (and the line-offset table used to compute char_range) keyed by (css, node_type), invalidated only when either actually changes. Comment ranges move from being added during parsing to a plain array collected once and re-filtered against the current window on every call, same as the other token types, so they still correctly track scroll after the walk-only refresh. Verified in a browser against a ~2.6MB/30k-rule stylesheet: parse() now runs exactly once regardless of how many times the visible window changes, and highlighted comment/property/selector ranges still track the scroll position correctly (offsets move from the top of the file, to ~50%, to the end, matching each scroll step). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT
Rebased on main, which now enforces oxlint's curly rule repo-wide (#386). Fixes the handful of one-line if-statements in this branch's own new code that the rule now flags. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT
bartveneman
force-pushed
the
claude/css-highlight-virtualization-r301dq
branch
from
August 31, 2026 08:54
36f3399 to
a657a2f
Compare
The .body scroll container opted into smooth scrolling whenever the user hadn't set prefers-reduced-motion, which made jumping to a selected location or the next/previous uncovered coverage block animate instead of snap. Drop it so scrollTo() always jumps instantly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implement viewport-based virtualization for CSS syntax highlighting to improve performance when highlighting large files. Only visible lines are highlighted, reducing DOM operations and memory usage.
Key Changes
New module
highlight-viewport.ts: Provides viewport tracking and geometry utilitiestrack_viewport_window: Svelte action that monitors scroll position and visible line range, dispatchingviewportwindowchangeeventscompute_visible_line_range: Calculates which lines are visible based on scroll offset and line heightline_range_to_char_range: Converts line ranges to character offsets for efficient range filteringUpdated
use-css-highlight.ts: Integrates viewport tracking into highlighting logicviewportwindowchangeevents to update visible rangeintersects()helper@mediablocks) for efficiencyUpdated
HighlightCssCode.svelte: Applies virtualization to componenttrack_viewport_windowaction to code elementImplementation Details
CustomEventrather than shared state, enabling independent composition ofuse:actionscqb) for viewport measurement, allowing future scoping without code changeshttps://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT