@viceser/view
v0.0.1
Published
A high-performance, lightweight virtual DOM library with efficient diffing and patching algorithms. Built for modern web applications with JSX support, server-side rendering, and minimal overhead.
Readme
@viceser/view
A high-performance, lightweight virtual DOM library with efficient diffing and patching algorithms. Built for modern web applications with JSX support, server-side rendering, and minimal overhead.
✨ Features
- 🚀 Ultra-fast DOM patching - Minimal DOM operations through intelligent diffing
- ⚛️ JSX Support - Full JSX/TSX compatibility with custom runtime
- 🔄 Efficient Diffing - Smart tree comparison with cache key optimization
- 🌐 Universal Rendering - Server-side and client-side rendering support
- 🎯 Tiny Bundle Size - Minimal footprint without sacrificing features
- 🔧 TypeScript First - Full type safety and excellent IntelliSense
- 🎪 Fragment Support - Built-in Fragment component for grouping elements
- 🎨 Modern Event Handling - Efficient event delegation and management
📦 Installation
npm install @viceser/view📦 Bundle Options
This library provides multiple build options optimized for different environments and use cases:
Module Formats
Default Import (Environment-aware)
import { h, patchFactory } from '@viceser/view';
// Automatically resolves to browser or server build based on environmentBrowser Bundles (Optimized for Performance)
For optimal browser performance, the library provides pre-bundled versions:
<!-- Development Bundle (24.9kb) - Human readable, includes source maps -->
<script type="module">
import { h, patchFactory } from './node_modules/@viceser/view/dist/client.bundle.js';
</script>
<!-- Production Bundle (10.0kb) - Minified for maximum performance -->
<script type="module">
import { h, patchFactory } from './node_modules/@viceser/view/dist/client.bundle.min.js';
</script>Direct Bundle Import
// Import specific bundle version
import { h, patchFactory } from '@viceser/view/bundle';
// Always resolves to production minified bundle (10.0kb)Server Support (Node.js Compatible)
For server-side rendering and Node.js environments:
// CommonJS (traditional Node.js) - uses bundled version
const { h, htmlFactory, buildRoot } = require('@viceser/view/server');
// ES Modules (modern Node.js) - uses TypeScript-compiled modular version
import { h, htmlFactory, buildRoot } from '@viceser/view/server';Build Commands
To generate the latest bundles, run:
npm run build # Build everything: TypeScript + client bundles + server CommonJS bundle
npm run build:tsc # Build only TypeScript (includes server ESM)
npm run build:bundle # Build only client bundles (dev + prod)
npm run build:server:cjs # Build only server CommonJS bundleBundle Comparison
| Format | Size | Use Case | Source Maps | |--------|------|----------|-------------| | Default Client | 368B | Node.js import, bundler usage | ✅ | | Development Bundle | 24.9kb | Browser development, debugging | ✅ | | Production Bundle | 10.0kb | Browser production, performance | ✅ | | Server ESM (TypeScript) | 310B + deps | Modern Node.js with ES modules | ✅ | | Server CommonJS Bundle | 14.6kb | Traditional Node.js with require() | ✅ |
When to Use Each Bundle
- Default Import: Use with modern bundlers (Vite, Webpack, etc.) for automatic optimization
- Development Bundle: Use for browser development when debugging without a bundler
- Production Bundle: Use for maximum performance in browsers without a bundler (60% smaller)
- Server ESM (TypeScript): Use in modern Node.js environments - modular, efficient, relies on compiled dependencies
- Server CommonJS Bundle: Use in traditional Node.js environments or when needing a single self-contained file
🚀 Quick Start
Basic Usage
Option 1: Dynamic Patching (Simplified API)
import { h, patchFactory } from '@viceser/view';
// Set up patch function with just the target element
const element = document.getElementById('app');
const patch = patchFactory(element);
// Start with some content
patch(
h(
'div',
{ class: 'container' },
h('h1', null, 'Hello World'),
h('p', null, 'This is a paragraph'),
),
);
// Update with new content dynamically
patch(
h(
'div',
{ class: 'container updated' },
h('h1', null, 'Hello Universe'),
h('p', null, 'This is an updated paragraph'),
h('button', { onClick: () => console.log('clicked') }, 'Click me'),
),
);Option 2: Pre-initialized Patching (Traditional API)
import { h, patchFactory } from '@viceser/view';
// Create initial virtual DOM
const initialVNode = h(
'div',
{ class: 'container' },
h('h1', null, 'Hello World'),
h('p', null, 'This is a paragraph'),
);
// Set up patch function with initial state and target element
const element = document.getElementById('app');
const patch = patchFactory(initialVNode, element);
// Render initial content immediately
patch();
// Update with new virtual DOM
const updatedVNode = h(
'div',
{ class: 'container updated' },
h('h1', null, 'Hello Universe'),
h('p', null, 'This is an updated paragraph'),
h('button', { onClick: () => console.log('clicked') }, 'Click me'),
);
// Apply the update
patch(updatedVNode);JSX Setup
Configure your tsconfig.json for JSX:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@viceser/view"
}
}Then use JSX naturally:
import { Fragment } from '@viceser/view/jsx-runtime';
function App({ name }: { name: string }) {
return (
<div className="app">
<h1>Welcome, {name}!</h1>
<Fragment>
<p>This is a fragment</p>
<p>Multiple elements without wrapper</p>
</Fragment>
</div>
);
}🏗️ Core Concepts
Virtual DOM Nodes (Patches)
Virtual DOM nodes are represented as patches with the following structure:
interface Patch {
a?: Action; // Action type (Add, Change, Move, Delete, Swap)
i?: number; // Index position
t?: PatchTag; // Tag name or component
c?: PatchChildren[]; // Children
at?: PatchAttributes; // Attributes
v?: string | null; // Text value
m?: number; // Move target index
ns?: string; // Namespace (for SVG)
k?: string; // Key for reconciliation
ck?: unknown; // Cache key for optimization
}Actions
The library supports five core actions:
A- Add: Create new DOM nodeC- Change: Update existing nodeM- Move: Reposition nodeD- Delete: Remove nodeS- Swap: Exchange positions of two nodes
Patch Factory
The patchFactory creates a patching function that handles diffing and DOM updates automatically:
import { patchFactory } from '@viceser/view';
const patch = patchFactory(targetElement);
// Returns a function that can be called with new virtual DOM to updateWhen to Use Each Signature
Use patchFactory(node) when:
- Building dynamic UIs with changing content
- Working with state management libraries
- Creating reusable components that render different content
- Starting with an empty container
Use patchFactory(initialPatch, node) when:
- You have a predefined initial layout
- Building static sites with occasional updates
- Server-side rendering with client-side hydration
- You want to separate initial render from updates
// Dynamic content example
const dynamicPatch = patchFactory(document.getElementById('dynamic'));
dynamicPatch(userGeneratedContent);
// Pre-initialized example
const staticPatch = patchFactory(<Layout />, document.getElementById('static'));
staticPatch(); // Renders Layout immediately📚 API Reference
Core Functions
h(tag, attributes, ...children)
Creates virtual DOM nodes (equivalent to React.createElement):
// Element nodes
const div = h('div', { id: 'app', className: 'container' }, 'Hello');
// Component nodes
const MyComponent = (props) => h('span', null, props.text);
const comp = h(MyComponent, { text: 'Hello Component' });
// With JSX
const jsx = (
<div id="app" className="container">
Hello
</div>
);patchFactory(node) or patchFactory(initialPatch, node)
Creates a patching function for efficient DOM updates. Supports two signatures for different use cases:
Signature 1: Dynamic Patching (Recommended for most use cases)
import { patchFactory } from '@viceser/view';
// Set up with just the target element
const patch = patchFactory(document.getElementById('app'));
// Apply patches dynamically
patch(<div>First content</div>);
patch(<div>Updated content</div>);
patch(); // Clears content (renders undefined)Signature 2: Pre-initialized Patching (Great when you have initial content)
import { patchFactory } from '@viceser/view';
// Set up with initial virtual DOM and target element
const patch = patchFactory(<div>Initial content</div>, document.getElementById('app'));
// Render initial content
patch();
// Update to new content
patch(<div>Updated content</div>);
// For document-level updates
const docPatch = patchFactory(<html><body>Initial</body></html>, document);
docPatch(); // Renders initial content to document
docPatch(<html><body>Updated</body></html>); // Updates html, head, and body elementshtmlFactory(patch)
Renders virtual DOM to HTML string (server-side):
import { htmlFactory } from '@viceser/view';
const html = htmlFactory(vnode);
// Returns HTML string representationEnvironment-Specific Exports
This library provides different builds optimized for client and server environments:
Client-Side (Browser) Exports
Default import automatically resolves to client build in browser environments:
import {
h, // createElement function
Fragment, // Fragment component
patchFactory, // Creates DOM patch function (client-only)
htmlFactory, // Renders to HTML string
diffFactory, // Creates diff computation function
domPatch, // Direct DOM patching utilities (client-only)
Children, // Type for component children
} from '@viceser/view';
// Client-specific functions for DOM manipulation
const patch = patchFactory(document.getElementById('app'));
patch(<div>Hello Browser!</div>);
// Direct DOM operations
domPatch(element, patches);Server-Side (Node.js) Exports
In Node.js environments, the library automatically provides server-optimized build:
// Automatic server build in Node.js
import {
h, // createElement function
Fragment, // Fragment component
htmlFactory, // Renders to HTML string
buildRoot, // Build root element (server-only)
diffFactory, // Creates diff computation function
Children, // Type for component children
} from '@viceser/view';
function App({ title }) {
return (
<html>
<head>
<title>{title}</title>
</head>
<body>
<div id="app">
<h1>Server Rendered!</h1>
</div>
</body>
</html>
);
}
// Server-specific HTML generation
const html = htmlFactory(<App title="My App" />);
const rootElement = buildRoot(html);Explicit Import Paths
You can also import from specific builds explicitly:
// Force client build
import { patchFactory, domPatch } from '@viceser/view/client';
// Force server build
import { buildRoot } from '@viceser/view/server';Shared API
These functions are available in both environments:
h- createElement functionFragment- Fragment componenthtmlFactory- Renders to HTML stringdiffFactory- Creates diff computation functionChildren- Type for component children
JSX Runtime
The JSX runtime is automatically imported when using JSX:
// These are available from @viceser/view/jsx-runtime
import { jsx, jsxs, Fragment } from '@viceser/view/jsx-runtime';
// But you typically don't import these directly - they're used automatically📚 Advanced Features
Fragment Component
Use fragments to group elements without wrapper:
import { Fragment } from '@viceser/view/jsx-runtime';
function List() {
return (
<Fragment>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</Fragment>
);
}
// Or with JSX shorthand
function List() {
return (
<>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</>
);
}Cache Keys for Optimization
Use cache keys to skip expensive computations:
function ExpensiveComponent({ data }) {
return <div cache:key={data.id}>{/* Expensive rendering logic */}</div>;
}Namespaces for SVG
Proper SVG support with namespaces:
function Icon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
);
}Event Handling
Efficient event handling with automatic delegation:
function Button({ onClick, children }) {
return (
<button
onClick={onClick}
onMouseEnter={(e) => console.log('hover')}
onCapture:Focus={(e) => console.log('focus captured')}
>
{children}
</button>
);
}Refs
Access DOM elements directly:
function Input() {
const handleRef = (element) => {
if (element) {
element.focus();
}
};
return <input ref={handleRef} type="text" />;
}Style Objects
Dynamic styling with object notation:
function StyledDiv({ color, size }) {
return (
<div
style={{
backgroundColor: color,
fontSize: `${size}px`,
padding: '10px',
borderRadius: '4px',
}}
>
Styled content
</div>
);
}Diff Factory for Optimization
Use diffFactory to create efficient state-aware diffing:
import { diffFactory } from '@viceser/view';
// Create a diff function for a specific component
function useOptimizedUpdates(initialState) {
const renderState = (state) => (
<div className="counter">
<span>Count: {state.count}</span>
<button disabled={state.loading}>
{state.loading ? 'Loading...' : 'Click me'}
</button>
</div>
);
const diff = diffFactory(renderState(initialState));
return (newState) => {
return diff(renderState(newState)); // Returns minimal patch
};
}
// Usage
const updateCounter = useOptimizedUpdates({ count: 0, loading: false });
const patch = updateCounter({ count: 1, loading: false }); // Only updates count🔧 Integration Examples
With State Management
import { h, patchFactory } from '@viceser/view';
class SimpleStore {
constructor(initialState) {
this.state = initialState;
this.listeners = [];
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.listeners.forEach((listener) => listener(this.state));
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter((l) => l !== listener);
};
}
}
const store = new SimpleStore({ count: 0 });
function Counter({ count }) {
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => store.setState({ count: count + 1 })}>
Increment
</button>
</div>
);
}
// Method 1: Simplified API (Recommended)
const patch = patchFactory(document.getElementById('app'));
// Render initial state
patch(<Counter count={store.state.count} />);
// Subscribe to state changes
store.subscribe((state) => {
patch(<Counter count={state.count} />);
});
// Method 2: Traditional API (Alternative)
const initialVNode = <Counter count={store.state.count} />;
const patchTraditional = patchFactory(
initialVNode,
document.getElementById('app2'),
);
patchTraditional(); // Render initial
store.subscribe((state) => {
patchTraditional(<Counter count={state.count} />);
});Server-Side Rendering
// server.ts
import { htmlFactory } from '@viceser/view';
function App({ title }) {
return (
<html>
<head>
<title>{title}</title>
</head>
<body>
<div id="app">
<h1>Server Rendered!</h1>
</div>
</body>
</html>
);
}
const html = htmlFactory(<App title="My App" />);Diff Factory for State Management
import { diffFactory, h } from '@viceser/view';
// Create a diff factory with initial state
const initialPatch = <div>Loading...</div>;
const createDiff = diffFactory(initialPatch);
// Use it to track state changes
function useStateDiff(initialState) {
const stateDiff = diffFactory(renderState(initialState));
return function updateState(newState) {
const newPatch = renderState(newState);
return stateDiff(newPatch); // Returns only the differences
};
}
// Example usage
function renderState(state) {
return (
<div className="app">
<h1>Count: {state.count}</h1>
<button disabled={state.loading}>
{state.loading ? 'Loading...' : 'Increment'}
</button>
<ul>
{state.items.map((item, i) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
const stateDiff = useStateDiff({ count: 0, loading: false, items: [] });
// Each call returns only the patch for what changed
const patch1 = stateDiff({ count: 1, loading: false, items: [] });
// Only updates count
const patch2 = stateDiff({ count: 1, loading: true, items: [] });
// Only updates button state
const patch3 = stateDiff({
count: 1,
loading: false,
items: [{ id: 1, name: 'New Item' }]
});
// Only updates loading state and list⚡ Performance Tips
1. Choose the Right patchFactory Signature
// ✅ Use simplified API for dynamic content
const patch = patchFactory(element);
patch(dynamicContent); // More flexible, cleaner code
// ✅ Use traditional API when you have predefined initial content
const patch = patchFactory(initialContent, element);
patch(); // Immediate render of initial content2. Use Keys for List Items
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}3. Leverage Cache Keys
function ExpensiveList({ items, filter }) {
const filteredItems = useMemo(
() => items.filter((item) => item.type === filter),
[items, filter],
);
return (
<div cache:key={filter}>
{filteredItems.map((item) => (
<Item key={item.id} {...item} />
))}
</div>
);
}4. Reuse Patch Functions
// Create one patch function per element/container
const patch = patchFactory(element);
// Reuse it for all updates - very efficient!
patch(content1);
patch(content2);
patch(content3);
// Don't create new patch functions unnecessarily
// ❌ Inefficient
content.forEach((item) => {
const itemPatch = patchFactory(containerElement); // Creates new function each time
itemPatch(item);
});
// ✅ Efficient
const patch = patchFactory(containerElement);
content.forEach((item) => {
patch(item); // Reuses same patch function
});🛠️ TypeScript Support
Full TypeScript definitions included:
import type {
Patch,
PatchAttributes,
PatchChildren,
Action,
} from '@viceser/view';
// Custom component with proper typing
interface ButtonProps {
variant?: 'primary' | 'secondary';
onClick?: (event: MouseEvent) => void;
children: PatchChildren;
}
function Button({
variant = 'primary',
onClick,
children,
}: ButtonProps): Patch {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
);
}🤝 Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
📄 License
MIT License - see the LICENSE file for details.
🔗 Links
Built with ❤️ for modern web development
📦 What's Exported
Client-side Build
// Default import in browser environments
import {
h,
Fragment,
patchFactory,
htmlFactory,
diffFactory,
domPatch,
Children,
} from '@viceser/view';
// Or explicit client import
import {
h,
Fragment,
patchFactory,
htmlFactory,
diffFactory,
domPatch,
Children,
} from '@viceser/view/client';Server-side Build
// Default import in Node.js environments
import {
h,
Fragment,
htmlFactory,
buildRoot,
diffFactory,
Children,
} from '@viceser/view';
// Or explicit server import
import {
h,
Fragment,
htmlFactory,
buildRoot,
diffFactory,
Children,
} from '@viceser/view/server';JSX Runtime
// Automatically imported by JSX transform
import { jsx, jsxs, Fragment } from '@viceser/view/jsx-runtime';
// Manual imports for utilities
import {
encodeEntities,
jsxEscape,
jsxAttr,
jsxTemplate,
} from '@viceser/view/jsx-runtime';Key Differences
| Function | Client | Server | Purpose |
| -------------- | ------ | ------ | --------------------------------- |
| h | ✅ | ✅ | createElement function |
| Fragment | ✅ | ✅ | Fragment component |
| htmlFactory | ✅ | ✅ | Renders to HTML string |
| diffFactory | ✅ | ✅ | Creates diff computation function |
| Children | ✅ | ✅ | Type for component children |
| patchFactory | ✅ | ❌ | DOM patching (requires DOM) |
| domPatch | ✅ | ❌ | Direct DOM operations |
| buildRoot | ❌ | ✅ | Build root element |
🔧 JSX Runtime
@viceser/view provides a complete JSX runtime with automatic transforms and manual utilities:
Automatic JSX Transform
Configure your tsconfig.json or build tool:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@viceser/view"
}
}Your JSX is automatically transformed:
// Your JSX
const App = () => (
<div className="app">
<h1>Hello World</h1>
<button onClick={handleClick}>Click me</button>
</div>
);
// Becomes (automatic transform)
import { jsx as _jsx } from '@viceser/view/jsx-runtime';
const App = () =>
_jsx('div', {
className: 'app',
children: [
_jsx('h1', { children: ['Hello World'] }),
_jsx('button', { onClick: handleClick, children: ['Click me'] }),
],
});Manual JSX Runtime
For advanced use cases or server-side rendering:
import {
jsx,
Fragment,
encodeEntities,
jsxAttr,
} from '@viceser/view/jsx-runtime';
// Manual JSX creation
const element = jsx('div', {
className: 'container',
children: [
jsx('h1', { children: ['Safe Content'] }),
jsx('p', { children: [encodeEntities('User & Input <script>')] }),
],
});
// Custom attribute handling
const styleAttr = jsxAttr('style', {
color: 'red',
fontSize: 16,
backgroundColor: 'blue',
});
// Returns: 'style="color:red;font-size:16px;background-color:blue;"'
// HTML entity encoding
const safeText = encodeEntities('<script>alert("xss")</script>');
// Returns: '<script>alert("xss")</script>'Template Literals (Experimental)
For precompiled template optimization:
import { jsxTemplate } from '@viceser/view/jsx-runtime';
const template = jsxTemplate`
<div class="user">
<h2>${'name'}</h2>
<p>Score: ${'score'}</p>
</div>
`;