元々 Node.js v18.18.2 と jsdom 22.1.0、global-jsdom 9.1.0 の組み合わせで動いていたテストがあり、Node.js v20 が LTS になったこともあり移行をした際に、落ちるテストが出てきたのでそれの対策を紹介します。
最初に結論
2点解決手段があって、
- テストランナーに vitest を使っているのであれば、
// @vitest-environment
を使って、global-jsdom を使わないようにする - jsdom が提供する API を使いそうであれば、明示的に
window.
をつける
今回の取り組みは
GitHub - yamachu/jsdom-with-window-prefix
このリポジトリにまとまっています
何が起こったか
React で ResizeObserver を使用する Hooks を実装した時に、jsdom を使用してテストを実施していました。 Node.js v18 では動いていたのですが、Node.js v20 で実行してみたところ、テストが実行できなくなっていました。
やっていたこととしては、HTMLEelemt に対して CustomEvent
を dispatchEvent で送出するコードを実行しています。
実行結果するとこのようなエラーが出力されました。
TypeError: Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.
なぜ起こってしまったか
前提として、Node.js v18 では明示的なフラグ無しでは CustomEvent
を使用することが出来ません。
Global objects | Node.js v18.18.2 Documentation
Stability: 1 - Experimental. Enable this API with the --experimental-global-customevent CLI flag.
その状態でも使えていたのは、jsdom (global-jsdom) が提供していた CustomEvent を使用できていたためです。
では、Node.js v20 ではどうでしょうか。
Global objects | Node.js v20.9.0 Documentation
Node.js v18 ではフラグを使用することで提供されていた CustomEvent が、v20 ではフラグ無しで使用できるようになりました。
$ node Welcome to Node.js v20.8.1. Type ".help" for more information. > CustomEvent [class CustomEvent extends Event] > (To exit, press Ctrl+C again or Ctrl+D or type .exit) > $ nodenv global 18.18.2 $ node Welcome to Node.js v18.18.2. Type ".help" for more information. > CustomEvent Uncaught ReferenceError: CustomEvent is not defined > (To exit, press Ctrl+C again or Ctrl+D or type .exit) > $ node --experimental-global-customevent Welcome to Node.js v18.18.2. Type ".help" for more information. > CustomEvent [class CustomEvent extends Event] >
その変更により、jsdom から提供されていた CustomEvent ではなく、globalThis
に存在する Node.js ランタイムから提供される CustomEvent を dispatchEvent で使用するようになってしまったのです。
ランタイムの提供する CustomEvent だとダメなの?って感じですが、その辺りは jsdom/lib/jsdom/living/generated/CustomEvent.js
辺りのコードを見ていただければ…と。
Node.js v20 でも動くようにするには
対策1 は再現のコードを書いているうちに見つけた workaround なので詳細を追っていないので省略で…
対策2 の様に、ブラウザ上で動かすことを前提としたようなコード(window.
でアクセスする)に書き換えるのがまず間違いがないです。
スコープの解決の順序の問題で、globalThis
を見てしまい、そこにランタイムが提供する Object や Constructor を優先してしまうからです。
限られた環境ではありますが、Node.js v20 への移行への助けになれば幸いです。