⚡
「happy-dom」vs「jsdom」 機能比較とパフォーマンス測定
はじめに
現在、React Router を使ってウェブアプリを作っているのですが、テストライブラリには happy-dom を使っています。
当初は jsdom を使っていたのですが、画面遷移時のテストで上手くいかない問題に遭遇しました。
具体的には、リダイレクト先の URL をテストで確認したかったのですが、jsdom で実現するには結構難航しそうでした。
happy-dom に切り替えることで無事解消されましたが、そもそも happy-dom と jsdom にはどんな違いがあるのか気になり、今回調べてみました。
また、以下の記事で「happy-dom の方がパフォーマンスが良い」という話を知り、本当に優れているのか実際にベンチマークを取って比較してみました。
TL;DR(結論)
- happy-dom: 速度重視(jsdom の 2〜5 倍高速)、基本的なテストに最適
- jsdom: 完全性重視、複雑なブラウザ機能のテストに最適
- React Router テスト: happy-dom の方がナビゲーション実装が充実しており、テストしやすい
基本情報の比較
項目 | happy-dom | jsdom |
---|---|---|
週間ダウンロード数 | 2,420,127 | 32,906,228 |
GitHub Stars | 4,023 | 21,260 |
ライセンス | MIT | MIT |
Node.js 要件 | - | v20+ |
主な特徴 | 高速、軽量 | 完全性、標準準拠 |
jsdom の方が圧倒的に普及していますが、happy-dom も急速に成長しています。
機能比較
共通でサポートされている機能
両方とも以下の基本機能はサポートしています:
- ✅ DOM 操作(createElement, appendChild, querySelector 等)
- ✅ イベントシステム
- ✅ Mutation Observer
- ✅ Tree Walker
- ✅ Fetch API
- ✅ Custom Elements(Web Components)
- ✅ Shadow DOM
- ✅ WHATWG DOM/HTML 標準
happy-dom 特有の機能
- ✅ Declarative Shadow DOM(サーバーサイドレンダリング対応)
- ✅ 超高速 DOM 操作
- ✅ より軽量なメモリフットプリント
- ✅ Bun、Vitest、Jest との統合
jsdom 特有の機能
- ✅ より完全なブラウザ API 実装
- ✅ スクリプト実行制御(dangerously、outside-only モード)
- ✅ Cookie Jar 管理
- ✅ カスタムリソースローディング
- ✅ Virtual Console 機能
- ✅ フレーム、iframe、スタイルシート、画像の読み込み
制限事項
両方に共通する制限
- ❌ 視覚的なレンダリングができない
- 実際に画面に描画することはできません
-
element.offsetWidth
などが正確な値を返しません
- ❌ レイアウト計算なし
- ❌ CSS の完全なサポートなし
happy-dom の制限
- ⚠️ 一部のブラウザ API が未実装
-
HTMLOptionElement
がグローバルスコープに存在しない
-
- ⚠️ イベントの発火順序が正しくない場合がある
- ⚠️
IntersectionObserver
、ResizeObserver
未対応
jsdom の制限
- ⚠️ パフォーマンスが happy-dom より遅い
- ⚠️ メモリ使用量が多い
- ⚠️ Declarative Shadow DOM 未対応
ブラウザ API 対応状況
主要 API 対応表
API | happy-dom | jsdom |
---|---|---|
querySelector/querySelectorAll | ✅ | ✅ |
createElement/appendChild | ✅ | ✅ |
addEventListener | ✅ | ✅ |
innerHTML/outerHTML | ✅ | ✅ |
MutationObserver | ✅ | ✅ |
IntersectionObserver | ❌ | ⚠️ 限定的 |
ResizeObserver | ❌ | ❌ |
Custom Elements | ✅ | ✅ |
Declarative Shadow DOM | ✅ | ❌ |
Canvas API | ❌ | ⚠️ 要パッケージ |
フォーム関連
API | happy-dom | jsdom |
---|---|---|
HTMLFormElement | ✅ | ✅ |
HTMLInputElement | ✅ | ✅ |
HTMLSelectElement | ✅ | ✅ |
HTMLOptionElement | ❌ | ✅ |
FormData | ✅ | ✅ |
React Router テストの違い
なぜ happy-dom の方がテストしやすいのか?
React Router のテストでhappy-dom だと動くのに、jsdom だとエラーが出るという経験はありませんか?
jsdom の問題
jsdom では以下のようなエラーが発生します:
Error: Not implemented: navigation (except hash changes)
原因:
- jsdom は
window.location.assign()
やwindow.location.replace()
などのナビゲーションメソッドを実装していない - React Router がページ遷移時にこれらを使用するとエラーになる
- History API の実装も不完全
happy-dom の利点
- ナビゲーション機能がより多く実装されている
- React Router v6 の History API 依存にも対応
- 追加のモックなしでテストが動作
jsdom での回避策
どうしても jsdom を使いたい場合は、以下のようにモックする必要があります:
// テストセットアップファイル(setupTests.js等)
delete window.location;
window.location = {
assign: jest.fn(),
replace: jest.fn(),
href: "",
};
// または jest-location-mock パッケージを使用
import "jest-location-mock";
React Router テストの推奨環境
{
"devDependencies": {
"happy-dom": "^19.0.0",
"vitest": "^1.0.0",
"@testing-library/react": "^14.0.0"
}
}
// vitest.config.js
export default {
test: {
environment: "happy-dom",
},
};
パフォーマンス比較
実際にベンチマークを取ってみました。
ベンチマーク環境
// 測定コード例
function measure(name, fn) {
const start = process.hrtime.bigint();
fn();
const end = process.hrtime.bigint();
return Number(end - start) / 1_000_000; // ms
}
結果
テスト項目 | happy-dom | jsdom | 速度差 |
---|---|---|---|
DOM 初期化 | 5.67ms | 29.98ms | 5.29 倍高速 ⚡ |
要素の作成と追加(1000 個) | 7.98ms | 16.66ms | 2.09 倍高速 |
querySelector(10000 回) | 114.38ms | 117.56ms | 1.03 倍高速 |
属性の読み書き(10000 回) | 18.27ms | 25.37ms | 1.39 倍高速 |
innerHTML(1000 回) | 18.94ms | 67.40ms | 3.56 倍高速 |
使い分けガイド
happy-dom を選ぶべき場合
- ✅ テストの実行速度を最優先したい
- ✅ CI パイプラインで大量のテストを高速に実行したい
- ✅ 基本的な DOM 操作のテストのみで十分
- ✅ メモリ使用量を抑えたい
- ✅ Vitest、Bun などの最新ツールと統合したい
- ✅ React Router のテストを簡単にしたい 🎯
- ✅ サーバーサイドレンダリング(SSR)で Web Components を使用
jsdom を選ぶべき場合
- ✅ ブラウザ環境の完全な再現が必要
- ✅ より正確な Web 標準への準拠が必要
- ✅ 複雑な DOM 操作や高度なブラウザ API を使用
- ✅ Cookie やカスタムリソースローディングが必要
- ✅ より成熟したライブラリが必要(広く使われている)
- ✅
HTMLOptionElement
など、happy-dom で未サポートの API が必要
実際の移行例
Jest からの移行(happy-dom 使用)
// package.json
{
"scripts": {
- "test": "jest"
+ "test": "vitest"
},
"devDependencies": {
- "jest": "^29.0.0",
- "jsdom": "^22.0.0",
+ "vitest": "^1.0.0",
+ "happy-dom": "^19.0.0"
}
}
// vitest.config.js
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "happy-dom",
globals: true,
},
});
パフォーマンス改善の実例
以下は実際のプロジェクトでの移行結果です:
jsdom使用時:
✓ 500 tests passed (2m 15s)
happy-dom使用時:
✓ 500 tests passed (45s)
→ 約3倍の高速化 🚀
ベンチマークコード(再現用)
この記事のベンチマーク結果を再現したい方は、以下のリポジトリをご利用ください。
クローン後、以下のコマンドで実行できます:
npm install
node benchmark.js
まとめ
- パフォーマンス: happy-dom が圧勝(2〜5 倍高速)
- 完全性: jsdom の方が標準準拠度が高い
- React Router テスト: happy-dom の方が簡単(ナビゲーション実装が充実)
- メモリ効率: happy-dom の方が軽量
- 成熟度: jsdom の方が広く使われている
個人的な推奨
新規プロジェクトや、特に理由がなければhappy-domを推奨します。理由は:
- 圧倒的な速度(テストが快適)
- React Router などモダンライブラリとの相性が良い
- Vitest 等の新しいツールとの統合がスムーズ
- 基本的なテストには十分な機能
ただし、以下の場合はjsdomを選択してください:
- 既存の Jest プロジェクトで移行コストが高い
- happy-dom で未実装の API が必要
- より厳密なブラウザ互換性が必要
Discussion