人気のパッケージ
仕様への準拠だけでは、実際のライブラリが動くとは限りません。test262の99 %に合格するJSエンジンでも、ライブラリが「仕様の細部」に依存しているため、本番コードで頻繁に問題が起きます。たとえば、特定の順序で発生するProxyトラップ、Function.prototype.toStringの形式、複雑な入力に対する正規表現の動作、Object.keysの順序と計算プロパティ名との相互作用などです。
Phasisは、広く使われている204種類のライブラリをNode.js(V8)とバイト単位で照合します。リポジトリ内のtests/Popular/に置かれており、エンジンのソースまたはテストスイートが変更されるたびにCIで実行されます。
カテゴリー別の対応範囲
| カテゴリー | ライブラリ |
|---|---|
| パーサー / AST | acorn 8.16.0, chevrotain 11.0.3, parse5 7.2.1, parsimmon 1.18.1, jsep 1.4.0, postcss-value-parser 4.2.0, acorn-walk 8.3.4, estraverse 5.3.0, escodegen 2.1.0, css-tree 3.0.4, sax 1.4.1, nearley 2.20.1 |
| テンプレート / Markdown / HTML | mustache 4.2.0, handlebars 4.7.9, marked 18.0.3, markdown-it 14.1.0 (+ markdown-it-emoji), markdown-table 3.0.4, showdown 2.1.0, cheerio 1.1.2, htmlparser2 11.0.0 |
| ユーティリティ / 関数型 | lodash 4.18.1, ramda 0.31.3, remeda 2.17.4, es-toolkit 1.27.0, immutable 5.1.3, immer 10.1.1, redux 5.0.1, deepmerge 4.3.1, dot-object 2.1.5, lodash.merge 4.6.2, lodash.clonedeep 4.5.0, extend 3.0.2, extend-shallow 3.0.2, mixin-deep 2.0.1, merge-options 3.0.4 |
| オブジェクトアクセス | dequal 2.0.3, clsx 2.1.1, classnames 2.5.1, get-value 4.0.1, set-value 4.1.0, object.omit 3.0.0, object.pick 1.3.0, is-plain-obj 4.1.0, kind-of 6.0.3 |
| 検証 / スキーマ | zod 4.4.3, yup 1.4.0, valibot 1.1.0, superstruct 2.0.2, validator 13.12.0, email-validator 2.0.4, ajv 8.17.1, @sinclair/typebox 0.34.42, runtypes 6.7.0, typanion 3.14.0, ow 2.0.0 |
| 日付 / 時刻 | dayjs 1.11.20, date-fns 4.1.0, timeago.js 4.0.2, luxon 3.7.3, moment 2.30.2 |
| 数学 / 数値 | bignumber.js 11.1.1, decimal.js 10.6.0, numeral 2.0.6, jsbi 4.3.0, big.js 6.2.2, fraction.js 4.3.7, complex.js 2.4.2, simple-statistics 7.8.8 |
| ハッシュ / 暗号 | blueimp-md5 2.19.0, crypto-js 4.2.0, fnv-plus 1.3.1, murmurhash3js 3.0.1, hash-sum 2.0.0, jwt-decode 4.0.0, @noble/hashes 2.2.0, tweetnacl 1.0.3, aes-js 3.1.2, bcryptjs 3.0.2, xxhashjs 0.2.2, object-hash 3.0.0 |
| 検索 / 差分 | fuse.js 7.3.0, minisearch 7.1.1, diff 8.0.2, diff-match-patch 1.0.5, fast-deep-equal 3.1.3, fast-equals 5.2.2, fastest-levenshtein 1.0.16, lunr 2.3.9 |
| JSON / データ形式 | json5 2.2.3, js-yaml 4.1.0, yaml 2.6.1, toml 3.0.0, smol-toml 1.3.1, fast-json-patch 3.1.1, jmespath 0.16.0, jsonata 2.0.6, jsonpath-plus 10.3.0, mingo 6.7.0, sift 17.1.3, fast-json-stable-stringify 2.1.0, safe-stable-stringify 2.5.0, json-stringify-safe 5.0.1, canonicalize 2.1.0, papaparse 5.5.4 |
| 文字列 / 言語処理 | he 1.2.0, slugify 1.6.9, camelcase 8.0.0, entities 6.0.1, pluralize 8.0.0, inflection 3.0.2, wcwidth 1.0.1, emoji-regex 10.4.0, unraw 3.0.0, jsesc 3.1.0, lower-case 2.0.2, upper-case 2.0.2, dot-case 3.0.4, pascal-case 3.1.2, snake-case 3.0.4, title-case 3.0.3, xregexp 5.1.1, escape-string-regexp 5.0.0, ansi-regex 6.1.0, strip-ansi 7.1.2, string-width 8.1.0 |
| URL / Web | url-parse 1.5.10, qs 6.13.1, query-string 9.1.1, tldts 6.1.69, punycode 2.3.1, linkifyjs 4.3.1, ipaddr.js 2.2.0, uri-js 4.4.1, url-template 3.1.1, bowser 2.11.0 |
| 圧縮 / エンコード / アーカイブ | pako 2.1.0, lz-string 1.5.0, base64-js 1.5.1, JSZip 3.10.1 |
| 色 | color-string 2.1.1, d3-color 3.1.0, chroma-js 3.1.2, color-name 2.0.0, tinycolor2 1.6.0, color 5.0.3 |
| クローン / 等価性 | rfdc 1.4.1, klona 2.0.6, fast-copy 3.0.2 |
| 書式 / 表示 | ms 2.1.3, bytes 3.1.2 |
| ID / UUID | uuid 10.0.0, hexoid 2.0.0, ulid 3.0.1, cuid2 2.2.2, short-unique-id 5.4.3 |
| 型判定 / glob | type-detect 4.1.0, picomatch 4.0.2, minimatch 10.1.1, wildcard-match 5.1.4, glob-to-regexp 0.4.1, to-regex 4.0.4, array-uniq 3.0.0, array-unique 0.3.2 |
| Semver / 設定 | semver 7.6.3, compare-versions 6.1.1, semver-compare 1.0.0 |
| キャッシュ / 性能 | lru-cache 11.0.2, marky 1.3.0, array-flatten 3.0.0, arr-diff 4.0.0 |
| 非同期 / イベント | eventemitter3 5.0.1, eventemitter2 6.4.10, mitt 3.0.1, p-limit 7.1.1, p-queue 9.0.2, p-defer 5.0.0, p-retry 7.1.0, throttle-debounce 5.0.2, async 3.2.6 |
| リアクティブ状態 | mobx 6.13.7, xstate 5.23.0, valtio 2.1.5, nanostores 1.0.1, jotai 2.16.1, redux-saga 1.3.0, redux 5.0.1 |
| CSS / スタイリング | postcss 8.5.7, stylis 4.3.6, css-tree 3.0.4 |
| ビルド / AST / コンパイラー | magic-string 0.30.20, escodegen 2.1.0, acorn-walk 8.3.4, estraverse 5.3.0, @babel/parser 7.28.5, terser 5.46.0, prettier 3.3.3, svelte 5.55.7(コンパイラー), typescript 6.0.3 |
| MQTT / プロトコル | mqtt-packet 9.0.4 |
| GraphQL | graphql-js 16.11.0 |
| 構文ハイライト | highlight.js 11.11.1 |
| テスト / アサーション | chai 6.2.1 |
合計:204種類のライブラリすべてが、Nodeと完全に同じ出力を再現します。すべて、最後に更新した時点での上流の最新リリースに固定されています。
仕組み
各シナリオはtests/Popular/<lib>/ディレクトリで、3つのファイルが含まれます。
tests/Popular/lodash/
├── lodash.js ← vendored upstream library (untouched, UMD bundle)
├── runner.js ← Phasis-side script that exercises the library
└── oracle.txt ← Node.js output of running runner.js (committed)
tests/Popular/PopularPackagesTest.phpは、次の処理を行うPHPUnitのデータプロバイダーテストです。
lodash.jsとrunner.jsを新しいnew Phasis\Engine()に読み込みます。console.logの出力をバッファーに取り込みます。- 取り込んだバッファーが
oracle.txtとバイト単位で一致することを検証します。
オラクルの再生成方法:
node tests/Popular/gen-oracle.js tests/Popular/<lib>
(ベンダーライブラリを更新した際に使用します。実際のNode上でランナーを再実行し、oracle.txtを上書きします。)
CIのゲート
ワークフロー:.github/workflows/popular.yml。
mainへのプッシュとプルリクエストのうち、src/、tests/Popular/、phpunit.xml.dist、Composerファイル、またはワークフロー自体を変更する場合に実行されます。手動実行にも対応しています。処理は2段階です。
- Phasis側 — PHPUnitの
popularテストスイートを実行します。各ライブラリをPhasis経由で実行し、コミット済みのオラクルとの差分を確認します。1バイトでも異なれば失敗します。 - Nodeで再検証 — 各ライブラリに対して
node tests/Popular/gen-oracle.js …を実行し、新たに生成したオラクルがリポジトリ内のものと一致することを確認します。「ベンダーライブラリを更新したのにオラクルを更新していない」ことによるずれを検出します。
PRをマージするには、両方の成功が必要です。test262に合格してlodashが壊れるのも、lodashが動いてtest262が壊れるのも、許容できません。
これらのライブラリを選んだ理由
- acorn — 解析はあらゆるJSエンジンで最も壊れやすい部分です。現代のJSが追加するあらゆる構文上の境界ケースを検証します。Phasisがacornで任意の現代コードを解析できれば、ランタイムは健全です。
- mustache.js — テンプレート処理はPhasisが明確に対象としている埋め込み用途の1つです(READMEの最初の具体例は「テンプレートエンジン」です)。Mustacheは、実際のテンプレートと同じ順序で反復、スコープ解決、文字列変換を検証します。
- lodash — 世界で最も広く使われているJSユーティリティライブラリです。300以上の関数が標準ライブラリのあらゆるメソッドに間接的に触れます。lodashが動けば、汎用的なデータ変換JSも動きます。
- marked — Markdownのレンダリングです。実用的で、正規表現(大量に使用)と、行をまたぐ状態を伴う文字列操作を検証します。PCRE2(Phasisのフォールバック)とV8のIrregexp間の正規表現エンジンの差異を明らかにします。
- dayjs — 日時の演算は、test262のどのテストケースでも見つからないことがある、典型的なオフバイワンバグの原因です。dayjsは、コンストラクターの各種形式、DSTで同等となる月境界をまたぐ演算、フォーマットトークンの展開を、ほぼすべてのJS Dateライブラリと同じコードパスで検証します。
- bignumber.js — TypedArraysを使わない任意精度演算により、文字列ベースの数値解析、大きな整数の乗除算ループ、丸めモードの振り分けをすべて純粋なJSで検証します。そのため、エンジンが処理全体のコストを負います。NumberとString間の変換にある微妙な問題を明らかにします。
- json5 — 緩やかなJSON解析では、文字クラスの正規表現と状態を持つトークン化を多用します。正規表現の正確性と文字列反復の境界ケースを検証する優れたカナリアです。
- fuse.js — BabelでトランスパイルされたUMDで、
_classCallCheck(ES6以前のトランスパイル済みクラスがすべて使うthis instanceof Ctorパターン)に加え、完全なbitap曖昧一致アルゴリズムを検証します。Babelでトランスパイルされたライブラリに共通する構造なので、Fuseを壊すものはnpmエコシステムの半分を壊します。 - handlebars — コンパイルしてからレンダリングする、多機能なテンプレートエンジンです。
evalなしのランタイム経路を1つの巨大なcompile()エントリー経由で検証し、ASTの走査、動的ルックアップテーブルを使ったヘルパーの呼び出し、複数のrealmにまたがるinstanceofを使ったSafeStringのブランドチェックも行います。mustache.jsのよりシンプルなテンプレート走査と対比できます。 - zod — ランタイムのスキーマ検証です。メソッドチェーンが絞り込まれたサブクラスを返す、複雑なクラス階層(
ZodString、ZodObject、ZodArray、ZodUnion、…)、JS側の.issues配列の形状をNodeとバイト単位で一致させる構造化エラー形式、識別子やリテラルタグのパターンを検証します。現代のTypeScriptアプリの半分を支えるような、オブジェクト指向の重い検証コードをテストします。 - typescript — 組み込みエンジンで実際のTypeScriptコンパイラーを実行します。9 MBの圧縮済みコンパイラーが任意のTSを再解析してASTを走査し、JSを出力します。想像できる限り最も重い純粋なJSのペイロードです。Phasisがジェネリクス、async/await、enum、クラス階層を
tscとバイト単位で一致する形でトランスパイルできれば、言語の意味論は健全です。 - prettier — 独自の方針を持つJSフォーマッターです。JSでJSをフォーマットするため、エンジンのあらゆる仕様上の境界が、コードを整形する同じ制御フローによって検証されます。ほかに類を見ないほど負荷の高いワークロードです。
- svelte/compiler — Svelteのスタンドアロンコンパイラーです。
.svelteソースを解析してASTを走査し、クライアントとSSRの両方のターゲット向けにJSを出力します。 - terser / @babel/parser / postcss — JSミニファイアー、JSパーサー、CSSポストプロセッサーです。何万行もの圧縮コードが任意の入力を再解析するため、まれなAST形状が現れる部分を検証できます。
- ajv — JSONスキーマバリデーターです。
new Function(...)を介して各スキーマをJITコンパイルし、独自のバリデーター関数を生成します。多くのスキーマ/テンプレートライブラリが頼るFunctionコンストラクターの経路を検証します。 - mobx — Proxyベースのリアクティブな状態管理です。監視対象への読み取りはすべてProxyの
getトラップを通って依存関係を登録し、書き込みのたびにリアクションが実行されます。Proxy基盤、observableなMap/配列/入れ子オブジェクト、自動実行スケジューラーのストレステストになります。 - cheerio / htmlparser2 — 高速なHTMLパーサーを基盤とするサーバーサイドjQueryです。セレクターエンジンによる解析、DOM形式の走査、属性操作、HTMLのシリアライズを1つのパッケージで行います。
- graphql-js / jsonata — GraphQLの参照実装とJSONクエリ言語です。どちらもスキーマ、検証、再帰評価器を多用しており、「ブラウザー内のデータレイヤー」に典型的なコードです。
- JSZip — 非同期ZIPアーカイブビルダーです。処理を
setImmediateベースのスケジューリングに渡して実際のバイナリー出力を生成し、タイマー機能とUint8Arrayを多用する経路の両方を検証します。 - moment / luxon — 日時の演算を扱います。momentは変更可能で細かく調整されたUMD、luxonは現代的な不変データへの書き換えです。合わせて、フォーマット、解析、タイムゾーンが等価な月/年の計算を幅広くカバーします。
- mqtt-packet — 純粋なJSによるMQTT 3.1.1/5.0コーデックです。バイト単位のパッキングとデコードに加え、Annex-B文法の境界ケースを検証する、base64のアルファベット外文字を扱う正規表現の文字クラスを備えています。
- xstate / valtio / nanostores / jotai / redux-saga — 現代的な状態管理の各種ライブラリです。状態機械、Proxy状態、atomストア、派生atom、ジェネレーターを使うエフェクトランナーなど、それぞれ異なるリアクティブ処理の形を検証します。
- escodegen / acorn-walk / estraverse / magic-string — ASTツールです。コード生成、走査、ソースの非破壊編集を備え、JSを変換するツール(ビルドツール、codemod、リンター)の基盤となります。
- papaparse / lunr / yaml / sax / css-tree — 純粋なパーサーとトークナイザーです。CSV、全文検索インデックス、YAML 1.2、SAX形式のストリーミングXML、CSS ASTを扱います。文法はそれぞれ異なりますが、いずれも文字単位の走査と正規表現の挙動を検証する優れたカナリアです。
追加するには
tests/Popular/<name>/の下に、3ファイル構成の新しいディレクトリを追加してから、次を実行します。
node tests/Popular/gen-oracle.js tests/Popular/<name> # one-time, generates oracle.txt
git add tests/Popular/<name>
次回の実行時にPHPUnitスイートが自動検出し、変更がtests/Popular/に触れるため、CIでも実行されます。
追加候補として有力なのは、chevrotain(パーサーコンビネーターで、クロージャーと正規表現を検証)、nanoid(暗号と乱数の境界)、date-fns(dayjsより広いタイムゾーン対応)です。