A blazingly fast, lightweight virtual DOM implementation powered by Bun. Perfect for testing web applications without the overhead of a real browser.
very-happy-dom is designed to be a faster, leaner alternative to happy-dom and jsdom for testing environments. Built from the ground up with Bun's performance in mind, it provides a comprehensive browser-like environment for your tests.
Built for speed with Bun, very-happy-dom delivers exceptional performance:
| Operation | Time | Details |
|---|---|---|
| Window Creation | 1.74 µs | Create new window instance |
| Element Creation | 1.68 µs | createElement('div') |
| 100 Elements | 5.60 µs | Create 100 elements |
| appendChild | 1.78 µs | Append single child |
| 100 appendChild | 6.70 µs | Append 100 children |
| querySelector | 4.12 µs | Simple tag selector |
| querySelectorAll (100 nodes) | 102.18 µs | Query 100 elements |
| setAttribute | 1.90 µs | Set single attribute |
| addEventListener | 1.95 µs | Add event listener |
| Canvas createElement | 2.57 µs | Create canvas element |
| Canvas getContext('2d') | 2.71 µs | Get 2D context |
| localStorage setItem | 2.16 µs | Store data |
| innerHTML parsing | 10.41 µs | Parse complex HTML |
Microseconds (µs), not milliseconds! Most operations complete in under 5 microseconds.
Benchmarks run on Apple M3 Pro with Bun 1.3.2
- 🚀 Blazingly Fast - Optimized for Bun's runtime
- 🪶 Lightweight - Minimal dependencies, small bundle size
- 🧪 Test-Ready - Drop-in replacement for happy-dom/jsdom
- 🔋 Batteries Included - Comprehensive browser API support
- 🎯 Framework Agnostic - Works with any testing framework
- 🔌 Easy Migration - Compatible with happy-dom's API
bun add -d very-happy-domOr with npm/pnpm:
npm install --save-dev very-happy-dom
pnpm add -D very-happy-domimport { Window } from 'very-happy-dom'
// Create a virtual browser environment
const window = new Window()
const document = window.document
// Use it like a real browser
document.body.innerHTML = '<h1>Hello World</h1>'
const heading = document.querySelector('h1')
console.log(heading?.textContent) // "Hello World"import { describe, expect, test } from 'bun:test'
import { Window } from 'very-happy-dom'
describe('MyComponent', () => {
test('renders correctly', () => {
const window = new Window()
const document = window.document
document.body.innerHTML = '<div class="container">Test</div>'
const element = document.querySelector('.container')
expect(element?.textContent).toBe('Test')
})
})import { describe, expect, test } from 'bun:test'
import { Window } from 'very-happy-dom'
describe('React Component', () => {
test('counter increments', () => {
const window = new Window()
global.window = window as any
global.document = window.document as any
// Your React component test here
const button = document.querySelector('button')
button?.click()
expect(document.querySelector('.count')?.textContent).toBe('1')
})
})import { describe, expect, test } from 'bun:test'
import { Window } from 'very-happy-dom'
describe('Vue Component', () => {
test('updates on data change', () => {
const window = new Window()
global.window = window as any
global.document = window.document as any
// Your Vue component test here
})
})import { Browser } from 'very-happy-dom'
const browser = new Browser()
const context = browser.createContext()
const page = context.newPage()
page.goto('https://example.com')import { Window } from 'very-happy-dom'
const window = new Window()
// Intercept and mock network requests
window.interceptor.addInterceptor({
onRequest: (request) => {
if (request.url.includes('/api/')) {
return new Response(JSON.stringify({ mocked: true }))
}
return request
}
})import { Window } from 'very-happy-dom'
const window = new Window({
url: 'https://example.com',
width: 1920,
height: 1080,
settings: {
navigator: {
userAgent: 'MyCustomUserAgent/1.0'
},
device: {
prefersColorScheme: 'dark'
}
}
})const window = new Window()
const document = window.document
const button = document.createElement('button')
let clicked = false
button.addEventListener('click', () => {
clicked = true
})
button.click()
console.log(clicked) // trueconst window = new Window()
// localStorage
window.localStorage.setItem('key', 'value')
console.log(window.localStorage.getItem('key')) // "value"
// sessionStorage
window.sessionStorage.setItem('session', 'data')const window = new Window()
const document = window.document
// MutationObserver
const observer = new window.MutationObserver((mutations) => {
mutations.forEach((mutation) => {
console.log('DOM changed:', mutation.type)
})
})
observer.observe(document.body, {
childList: true,
attributes: true,
subtree: true
})
// IntersectionObserver
const io = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log('Visibility changed:', entry.isIntersecting)
})
})
// ResizeObserver
const ro = new window.ResizeObserver((entries) => {
entries.forEach((entry) => {
console.log('Size changed:', entry.contentRect)
})
})const window = new Window()
const document = window.document
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 100, 100)
ctx.strokeStyle = 'blue'
ctx.strokeRect(10, 10, 80, 80)
// Export canvas data
const dataUrl = canvas.toDataURL()
const blob = await canvas.toBlob()- Window - Main window/global object with all browser APIs
- Document - DOM document with querySelector, createElement, etc.
- Element - DOM elements with full manipulation API
- Browser - Browser instance for advanced scenarios
- BrowserContext - Isolated browser contexts
- BrowserPage - Individual pages with navigation
- Complete DOM manipulation (createElement, appendChild, removeChild, etc.)
- CSS selectors (querySelector, querySelectorAll)
- Event system (addEventListener, removeEventListener, dispatchEvent)
- Attributes and properties (getAttribute, setAttribute, classList, etc.)
- XPath support (evaluate)
- Fetch API - fetch(), Request, Response, Headers, FormData
- Storage API - localStorage, sessionStorage
- Timer APIs - setTimeout, setInterval, requestAnimationFrame
- Observer APIs - MutationObserver, IntersectionObserver, ResizeObserver
- WebSocket - Full WebSocket implementation
- Canvas API - Basic 2D rendering context
- File API - File, FileReader, FileList
- Clipboard API - Clipboard and Navigator
- Performance API - Performance marks and measures
- Console API - Full console implementation
- Custom Elements (customElements.define)
- Shadow DOM (attachShadow)
very-happy-dom is designed to be API-compatible with happy-dom:
// Before (happy-dom)
import { Window } from 'happy-dom'
// After (very-happy-dom)
import { Window } from 'very-happy-dom'
// The rest of your code stays the same!// Before (jsdom)
import { JSDOM } from 'jsdom'
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>')
const { window } = dom
const { document } = window
// After (very-happy-dom)
import { Window } from 'very-happy-dom'
const window = new Window()
const document = window.document- Faster - Built for Bun's performance
- Lighter - Smaller bundle size, fewer dependencies
- Modern - Uses modern JavaScript features
- Simpler - Cleaner API surface
Click to expand full API list
- ✅ Document
- ✅ Element
- ✅ TextNode
- ✅ CommentNode
- ✅ DocumentFragment
- ✅ Attributes
- ✅ ClassList
- ✅ Style
- ✅ querySelector / querySelectorAll
- ✅ getElementById / getElementsByClassName / getElementsByTagName
- ✅ CSS Selectors (all combinators)
- ✅ XPath
- ✅ addEventListener / removeEventListener
- ✅ Event bubbling and capturing
- ✅ CustomEvent
- ✅ Event.preventDefault / stopPropagation
- ✅ fetch()
- ✅ XMLHttpRequest
- ✅ WebSocket
- ✅ Request / Response / Headers
- ✅ FormData
- ✅ Request Interception
- ✅ localStorage
- ✅ sessionStorage
- ✅ setTimeout / clearTimeout
- ✅ setInterval / clearInterval
- ✅ requestAnimationFrame / cancelAnimationFrame
- ✅ MutationObserver
- ✅ IntersectionObserver
- ✅ ResizeObserver
- ✅ Canvas element
- ✅ 2D rendering context
- ✅ Basic drawing operations
- ✅ toDataURL / toBlob
- ✅ Custom Elements
- ✅ Shadow DOM
- ✅ Performance API
- ✅ Console API
- ✅ Clipboard API
- ✅ Navigator API
- ✅ Geolocation API
- ✅ Notification API
- ✅ History API
- ✅ Location API
- ✅ Cookie API
- ✅ File API
- ✅ FileReader API
bun test # Run all tests
bun test --coverage # Run with coverageRun the full benchmark suite to see performance metrics:
bun run bench # Run performance benchmarksThe benchmarks test various operations including DOM creation, manipulation, querying, events, Canvas API, and storage operations.
We welcome contributions! Please see CONTRIBUTING for details.
Please see our releases page for more information on what has changed recently.
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
Very Happy DOM is free and open-source, but we'd love to receive a postcard from you! Send one to:
Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
We showcase postcards from around the world on our website!
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
The MIT License (MIT). Please see LICENSE for more information.
Made with 💙