CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-snabbdom

A virtual DOM library with focus on simplicity, modularity, powerful features and performance.

Pending
Overview
Eval results
Files

advanced.mddocs/

Advanced Features

Advanced Snabbdom features including performance optimization through thunks, comprehensive lifecycle hooks, and helper utilities for complex use cases.

Capabilities

Thunks

Thunks provide performance optimization by deferring virtual node creation until necessary, ideal for expensive computations with immutable data.

/**
 * Create a thunk for performance optimization
 * @param sel - Element selector
 * @param fn - Function that returns a virtual node
 * @param args - Arguments passed to the function
 * @returns Thunked virtual node
 */
function thunk(
  sel: string,
  fn: (...args: any[]) => any,
  args: any[]
): VNode;

/**
 * Create a thunk with a key for proper identification
 * @param sel - Element selector
 * @param key - Unique key for the thunk
 * @param fn - Function that returns a virtual node
 * @param args - Arguments passed to the function
 * @returns Thunked virtual node
 */
function thunk(
  sel: string,
  key: any,
  fn: (...args: any[]) => any,
  args: any[]
): VNode;

Usage Examples:

import { thunk, h, init } from "snabbdom";

// Expensive computation function
function createComplexView(data: any[]): VNode {
  // This function is only called when data changes
  return h("div.complex", data.map((item, i) => 
    h("div.item", { key: i }, `Item: ${item.name}`)
  ));
}

// Using thunk to optimize
function render(state: { items: any[] }): VNode {
  return h("div.app", [
    h("h1", "My App"),
    // Only re-renders when state.items changes
    thunk("div.items", createComplexView, [state.items])
  ]);
}

// With key for sibling thunks
function renderWithKey(state: { users: any[], posts: any[] }): VNode {
  return h("div.app", [
    thunk("div.users", "users", createUsersList, [state.users]),
    thunk("div.posts", "posts", createPostsList, [state.posts])
  ]);
}

Lifecycle Hooks

Comprehensive hook system providing fine-grained control over the virtual DOM lifecycle.

interface Hooks {
  pre?: PreHook;
  init?: InitHook;
  create?: CreateHook;
  insert?: InsertHook;
  prepatch?: PrePatchHook;
  update?: UpdateHook;
  postpatch?: PostPatchHook;
  destroy?: DestroyHook;
  remove?: RemoveHook;
  post?: PostHook;
}

type PreHook = () => any;
type InitHook = (vNode: VNode) => any;
type CreateHook = (emptyVNode: VNode, vNode: VNode) => any;
type InsertHook = (vNode: VNode) => any;
type PrePatchHook = (oldVNode: VNode, vNode: VNode) => any;
type UpdateHook = (oldVNode: VNode, vNode: VNode) => any;
type PostPatchHook = (oldVNode: VNode, vNode: VNode) => any;
type DestroyHook = (vNode: VNode) => any;
type RemoveHook = (vNode: VNode, removeCallback: () => void) => any;
type PostHook = () => any;

Hook Usage Examples:

import { h, init, VNode } from "snabbdom";

// Element-level hooks
const elementWithHooks = h("div.animated", {
  hook: {
    init: (vnode: VNode) => {
      console.log("Element initialized");
    },
    create: (emptyVnode: VNode, vnode: VNode) => {
      console.log("DOM element created");
    },
    insert: (vnode: VNode) => {
      // Safe to measure DOM here
      const rect = (vnode.elm as Element).getBoundingClientRect();
      console.log("Element inserted, size:", rect);
    },
    update: (oldVnode: VNode, vnode: VNode) => {
      console.log("Element updated");
    },
    remove: (vnode: VNode, removeCallback: () => void) => {
      // Animate out before removing
      const elm = vnode.elm as HTMLElement;
      elm.style.opacity = "0";
      setTimeout(removeCallback, 300);
    },
    destroy: (vnode: VNode) => {
      console.log("Element destroyed");
    }
  }
});

// Module-level hooks example
const animationModule = {
  create: (emptyVnode: VNode, vnode: VNode) => {
    const elm = vnode.elm as HTMLElement;
    if (elm && vnode.data?.animate) {
      elm.style.opacity = "0";
    }
  },
  insert: (vnode: VNode) => {
    const elm = vnode.elm as HTMLElement;
    if (elm && vnode.data?.animate) {
      setTimeout(() => {
        elm.style.transition = "opacity 0.3s";
        elm.style.opacity = "1";
      }, 10);
    }
  }
};

const patch = init([animationModule]);

AttachTo Helper

Utility for attaching virtual nodes to different DOM locations than their logical parent.

/**
 * Attach a virtual node to a different DOM location
 * @param target - DOM element to attach to
 * @param vnode - Virtual node to attach
 * @returns Modified virtual node with attachment behavior
 */
function attachTo(target: Element, vnode: VNode): VNode;

interface AttachData {
  [key: string]: any;
  [i: number]: any;
  placeholder?: any;
  real?: Node;
}

Usage Example:

import { attachTo, h, init } from "snabbdom";

const patch = init([]);

// Modal that renders in document.body but is managed in component tree
function Modal(props: { isOpen: boolean }): VNode {
  if (!props.isOpen) {
    return h("div"); // Empty placeholder
  }
  
  const modalContent = h("div.modal", [
    h("div.modal-backdrop"),
    h("div.modal-content", [
      h("h2", "Modal Title"),
      h("p", "Modal content goes here"),
      h("button", "Close")
    ])
  ]);
  
  // Attach modal to document.body instead of normal DOM position
  return attachTo(document.body, modalContent);
}

// Usage in app
const app = h("div.app", [
  h("h1", "Main App"),
  h("button", "Open Modal"),
  Modal({ isOpen: true }) // Renders in document.body
]);

Utility Functions

Helper functions for type checking and manipulation.

/**
 * Check if value is an array (alias for Array.isArray)
 */
const array: (arg: any) => arg is any[];

/**
 * Check if value is a primitive type (string or number)
 */
function primitive(s: any): s is string | number;

/**
 * Default HTML DOM API implementation
 */
const htmlDomApi: DOMAPI;

Usage Example:

import { array, primitive, htmlDomApi } from "snabbdom";

// Type checking utilities
const data = [1, 2, 3];
if (array(data)) {
  console.log("Is array:", data.length);
}

const value = "hello";
if (primitive(value)) {
  console.log("Is primitive:", value.toString());
}

// Custom DOM API (for testing or server environments)
const customDomApi = {
  ...htmlDomApi,
  createElement: (tagName: string) => {
    console.log("Creating element:", tagName);
    return htmlDomApi.createElement(tagName);
  }
};

const patch = init([], customDomApi);

Advanced Patterns

Component State Management

Using hooks to manage component-level state and effects.

import { h, VNode, init } from "snabbdom";

interface ComponentState {
  count: number;
  mounted: boolean;
}

function Counter(initialCount: number = 0): VNode {
  let state: ComponentState = { count: initialCount, mounted: false };
  
  const increment = () => {
    state.count++;
    // Trigger re-render (you'd implement this in your app)
    // render();
  };
  
  return h("div.counter", {
    hook: {
      insert: (vnode: VNode) => {
        state.mounted = true;
        console.log("Counter mounted with count:", state.count);
      },
      destroy: (vnode: VNode) => {
        state.mounted = false;
        console.log("Counter unmounted");
      }
    }
  }, [
    h("p", `Count: ${state.count}`),
    h("button", { 
      on: { click: increment } 
    }, "Increment")
  ]);
}

Performance Monitoring

Using hooks to monitor rendering performance.

const performanceModule = {
  pre: () => {
    console.time("patch");
  },
  post: () => {
    console.timeEnd("patch");
  }
};

const patch = init([performanceModule]);

Types

interface ThunkData extends VNodeData {
  fn: () => VNode;
  args: any[];
}

interface Thunk extends VNode {
  data: ThunkData;
}

interface ThunkFn {
  (sel: string, fn: (...args: any[]) => any, args: any[]): Thunk;
  (sel: string, key: any, fn: (...args: any[]) => any, args: any[]): Thunk;
}

Install with Tessl CLI

npx tessl i tessl/npm-snabbdom

docs

advanced.md

core.md

index.md

jsx.md

modules.md

tile.json