From 8a176bb8f41bd70e2d0039eee941ed0401be446f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 18:44:56 +0000 Subject: [PATCH 1/6] perf: virtualize CSS syntax highlighting for large stylesheets 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 `` node, so it composes with the existing `highlight_css` action without either one referencing the other directly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01E7X88yKZHKpehyuogXpFDT --- src/lib/components/HighlightCssCode.svelte | 43 +++- src/lib/components/highlight-viewport.ts | 230 +++++++++++++++++++++ src/lib/components/use-css-highlight.ts | 60 +++++- 3 files changed, 328 insertions(+), 5 deletions(-) create mode 100644 src/lib/components/highlight-viewport.ts diff --git a/src/lib/components/HighlightCssCode.svelte b/src/lib/components/HighlightCssCode.svelte index 5e0c56e..3d32610 100644 --- a/src/lib/components/HighlightCssCode.svelte +++ b/src/lib/components/HighlightCssCode.svelte @@ -3,6 +3,16 @@ import { browser } from '$app/env' import type { CssLocation } from '#lib/css-location.js' import { highlight_css } from './use-css-highlight' + import { + track_viewport_window, + build_line_offsets, + line_range_to_char_range, + FULL_LINE_RANGE, + VIRTUALIZE_THRESHOLD_CHARS, + type LineRange, + type CharRange, + type ViewportWindowChangeEvent + } from './highlight-viewport' type Props = { css?: string @@ -34,15 +44,36 @@ let own_lines_ranges = new Set() let own_selected_ranges = new Set() + // Reported by the track_viewport_window action below - a separate `use:` directive on the + // same element, decoupled from this component's own highlighting via a DOM CustomEvent + // rather than any direct reference between the two. + let visible_range = $state(FULL_LINE_RANGE) + let virtualize = $derived(css.length > VIRTUALIZE_THRESHOLD_CHARS) + let visible_char_range: CharRange | undefined = $derived.by(() => { + if (!virtualize) return undefined + return line_range_to_char_range( + build_line_offsets(css), + visible_range.start_line, + visible_range.end_line, + css.length + ) + }) + + function on_viewport_window_change(event: Event) { + visible_range = (event as ViewportWindowChangeEvent).detail + } + onMount(function () { supports_highlights = browser && 'highlights' in window.CSS if (supports_highlights) { lines = window.CSS.highlights.get(lines_highlight_name) || new Highlight() selected_line = window.CSS.highlights.get(selected_highlight_name) || new Highlight() } + code_node?.addEventListener('viewportwindowchange', on_viewport_window_change) }) onDestroy(function () { + code_node?.removeEventListener('viewportwindowchange', on_viewport_window_change) if (supports_highlights) { for (const range of own_lines_ranges) { lines?.delete(range) @@ -100,7 +131,11 @@ } } + const window_range = visible_char_range for (const { start, end } of punched) { + if (window_range && (start >= window_range.end || end <= window_range.start)) { + continue + } let range = new StaticRange({ startContainer: node, startOffset: start, @@ -141,7 +176,13 @@ }) -{css} +{css}