@ahmedbanihani/mesk
v0.1.0
Published
Mesk — virtualization for React — innerHTML + portals + RAF scroll + event delegation. Standalone engine, zero dependencies.
Downloads
48
Maintainers
Readme
mesk
Monaco-grade innerHTML virtualization + selective React portals for heavy React lists
Zero re-renders during scroll. The only virtualization library where React Scan shows nothing.
npm install meskWhy mesk?
React virtual lists (virtua, react-window, @tanstack/virtual) all render items as React components. With heavy rows (shadcn menus, Radix dropdowns, Lucide icons), this means hundreds of component mount/unmount cycles per scroll frame.
mesk uses the same technique VS Code's Monaco editor uses: innerHTML for structure, React portals for interactivity. Zero React components in the scroll path.
Quick Start
import { MeskList } from "mesk";
const items = Array.from({ length: 100_000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
}));
function App() {
return (
<div style={{ height: "100vh" }}>
<MeskList
data={items}
renderHTML={(index, item) => `
<div style="padding: 8px 16px; border-bottom: 1px solid #27272a">
<span style="color: #60a5fa">#${item.id}</span>
<span style="margin-left: 8px">${item.name}</span>
</div>
`}
onRowClick={(index) => console.log("Clicked", index)}
/>
</div>
);
}Interactive Content with Portals
Need a button in a row? Use renderPortal:
import { createPortal } from "react-dom";
<MeskList
data={items}
renderHTML={(index, item) => `
<div style="display: flex; padding: 8px">
<span>${item.name}</span>
<div data-portal-slot style="margin-left: auto"></div>
</div>
`}
renderPortal={(index, element, item) => {
const slot = element.querySelector("[data-portal-slot]");
if (!slot) return null;
return createPortal(
<button onClick={() => handleDelete(item.id)}>Delete</button>,
slot
);
}}
/>Key Features
| Feature | How it works |
|---------|-------------|
| innerHTML Fast-Path | Rows created via innerHTML — one native DOM parse instead of hundreds of React.createElement calls |
| React Portals | Mount React components only where needed — interactive buttons, dropdowns, forms |
| Event Delegation | One click listener on the container instead of N per row. Resolves via DOM traversal |
| RAF Scroll Coalescing | Multiple scroll events per frame → single DOM update. No redundant reconciliation |
| DOM Recycling | Kept nodes untouched, only edges add/remove. Full rebuild only when overlap drops below 50% |
| Zero Re-renders | React Scan shows nothing during scroll — items are HTML strings, not React components |
vs Other Libraries
| Library | Approach | Re-renders on scroll? | |---------|----------|----------------------| | virtua | React components per row | Yes — mount/unmount on range change | | react-window | React components per row | Yes — same | | @tanstack/virtual | React components per row | Yes — same | | mesk | innerHTML + portals | No |
API Overview
MeskList Props
| Prop | Type | Description |
|------|------|-------------|
| data | ArrayLike<T> | Data items array |
| renderHTML | (index, data) => string | Returns HTML for each item (required) |
| renderPortal | (index, element, data) => Portal \| null | Mounts React into DOM slots (optional) |
| bufferSize | number | Extra pixels before/after viewport (default: 200) |
| itemSize | number | Hint for unmeasured items (auto-estimated if omitted) |
| horizontal | boolean | Horizontal scroll mode |
| gpuAccelerate | boolean | Use transform instead of top |
| onRowClick | (index, event) => void | Delegated click handler |
| onRowContextMenu | (index, event) => void | Delegated right-click handler |
| onScroll | (offset) => void | Scroll callback |
| onScrollEnd | () => void | Scroll stop callback |
Imperative Handle
const ref = useRef<MeskListHandle>(null);
ref.current.scrollToIndex(500);
ref.current.scrollToIndex(500, { smooth: true, align: "center" });
ref.current.scrollTo(1000);
ref.current.scrollBy(100);
ref.current.forceRender();
// Read-only:
ref.current.cache; // CacheSnapshot for serialization
ref.current.scrollOffset; // current scrollTop
ref.current.scrollSize; // current scrollHeight
ref.current.viewportSize; // current offsetHeightArchitecture
The core engine (VirtualStore, ScrollObserver, SizeObserver) is framework-agnostic — no React dependencies in src/engine/. The React layer (MeskList, MeskVirtualizer, MeskWindowVirtualizer) is a thin adapter on top.
Standalone: ~7.2 kB gzipped. Zero dependencies beyond React.
Examples
See examples/ for:
- Simple text list
- List with context menus via portals
- Log viewer with action buttons
Security (XSS Protection)
NOTE: mesk uses DOMPurify to automatically sanitize all HTML content passed to renderHTML, item icons, and custom renderer functions. This protection is built-in and active in all components:
FastCommand,FastDropdownMenu,FastCombobox– icon HTML is automatically sanitizedMeskList,MeskVirtualizer,MeskWindowVirtualizer–renderHTMLoutput is automatically sanitizedrenderPortal– React portals have React's built-in DOM escaping
What DOMPurify blocks
<script>tags and inline scripts- Event handlers (
onclick,onerror, etc.) javascript:anddata:URLs- Unknown HTML tags/attributes
What remains safe
- Safe SVG icons (basic paths, circles, rects, etc.)
classandstyleattributes for styling- Standard HTML elements (div, span, svg, etc.)
Recommended practices
- Only pass trusted HTML for icons and custom
renderHTMLfunctions - If displaying untrusted user input, use the library's automatic sanitization (no additional code needed)
- For additional protection, set a strict Content-Security-Policy:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';">
License
MIT
Made with ❤️ by the mesk team
