窓を作っては壊していた人のブログ

この謎のブログタイトルの由来を知るものはもういないだろう

global-jsdom + Node.js v20の組み合わせでテストが通らなくなってしまったのを直していく

元々 Node.js v18.18.2 と jsdom 22.1.0、global-jsdom 9.1.0 の組み合わせで動いていたテストがあり、Node.js v20 が LTS になったこともあり移行をした際に、落ちるテストが出てきたのでそれの対策を紹介します。

最初に結論

2点解決手段があって、

  1. テストランナーに vitest を使っているのであれば、// @vitest-environment を使って、global-jsdom を使わないようにする
  2. 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 への移行への助けになれば幸いです。