CSS-Tricksの記事「Soon We Can Finally Banish JavaScript to the ShadowRealm」は、タイトルからしてかなり遊び心があります。
でも中身はちゃんと真面目で、JavaScriptの実行環境を理解するうえでかなり面白い話です。
結論からいうと、この記事が紹介している ShadowRealm は、JavaScriptを「別世界に追放する」ための仕組みではなく、他のコードに干渉されたくないコードを、きれいに隔離して動かすための新しい仕組みです。
JavaScriptはよく「single-threaded」と説明されます。
これはざっくり言えば、1つの実行の流れを順番に処理する言語だという意味です。
ただ、ここでややこしいのは、JavaScriptアプリ全体を見ると、実は複数のthreadを使えることです。
その代表例が Web Worker です。これは、JavaScriptを別のthreadで動かせる仕組みです。
なので、記事では「JavaScriptはsingle-threaded」と言うより、JavaScript realm が single-threaded だと考えるほうが正確だと説明しています。
この言い方、かなりしっくりきます。
“言語そのもの”と“動かす場所”を分けて考えると、だいぶ理解しやすくなるからです。
realmとは、JavaScriptコードが実行される環境のことです。
たとえばブラウザのタブは1つのrealmですし、Web Workerも別のrealmです。
さらに、別オリジンのiframeも、それぞれ別のrealmになります。
ここで重要なのは、realmごとに global object が別だという点です。
ブラウザなら、たいてい Window がそれにあたります。
つまり、同じページの中にiframeがあっても、外側のページと内側のiframeは別の世界です。
見た目は同じブラウザの中にあるのに、JavaScriptから見ると「住んでいる部屋が違う」という感じですね。
記事では、次のような違いを示しています。
window.globalThis と iframe.contentWindow.globalThis は、どちらも Window {} に見えるwindow.Array と iframe.contentWindow.Array も別物このへん、JavaScriptの世界は本当に「見た目が同じでも別人」みたいなことが多く、初学者にはなかなかしんどいです。
でも逆に言えば、この“別世界感”こそが隔離の基本なんですよね。
JavaScriptは長年、global scopeの汚染に悩まされてきました。
global scope とは、どこからでも見える“共有スペース”みたいなものです。
ここにいろんなコードが勝手に値や関数を置くと、次のような問題が起きます。
特に現代のWeb開発は、
フレームワーク、ライブラリ、polyfill、解析ツール、広告など、自分で完全にコントロールできないコードを大量に読み込むことがあります。
そうなると、「このコードだけきれいな部屋で動かしたい」という需要が出てくるのは当然です。
これはかなり現実的で、実務的な話だと思います。
そこで登場するのが ShadowRealm です。
名前がもう強い。正直、かなりかっこいいです。
ShadowRealm は、隔離専用の新しい realm です。
ポイントは、自分専用の global object と built-in objects を持つけれど、別threadは持たないこと。
つまり、ShadowRealm の中で動かすコードは:
という性質を持ちます。
これは Web Worker とはかなり違います。
Web Worker は並列処理向きですが、ShadowRealm は並列化のためではなく、隔離のためにある、というのが面白いところです。
個人的には、ここがこの提案の一番おいしい部分だと思います。
Web Worker は「重い処理を別スレッドに逃がす」道具としては便利ですが、単に安全に隔離したいだけなのに、スレッド間通信の手間まで背負うのは大げさなんですよね。
ShadowRealm はその不満にかなり素直に応えています。
記事では、ShadowRealm のAPIはかなりシンプルだと説明されています。
const shadow = new ShadowRealm();
これで ShadowRealm を作ります。
たとえば外側の realm にこんな関数があったとしても、
function globalFunction() {}
ShadowRealm の中からは見えません。
shadow.evaluate('globalThis.globalFunction'); // undefined
逆に、ShadowRealm の中で関数を定義しても、外側には漏れません。
shadow.evaluate('function globalFunction() {};');
globalThis.globalFunction; // undefined
要するに、外と中はかなりきれいに分離されるわけです。
この“きれいさ”は、地味だけどかなり大事です。
現場で本当に欲しいのは、壮大な魔法よりも「余計なものが入ってこない、予測しやすい環境」だったりしますから。
ここは誤解しやすい点ですが、ShadowRealm は真のセキュリティ境界ではないと記事は明言しています。
つまり、「これに入れたら絶対安全」という意味ではありません。
その代わり、integrity boundary だと考えるのがよい、という説明です。
これはざっくり言うと、**外から直接いじられにくい“整合性の境界”**みたいなものです。
中のコードは外のコードに直接干渉しにくい。
でも、完全に別世界として孤立していて、何も影響し合わないわけではない。
このあたりは、過信しないのが大事だと思います。
記事では、ShadowRealm の用途として次のようなものが挙げられています。
これ、かなり現場目線で便利そうです。
特にテストは相性がよさそうです。
「前のテストが残した何か」が次のテストに影響する、あの地味にイライラする問題を減らせるなら、かなり嬉しい。
重要なのは、この記事時点では ShadowRealm はまだ提案段階だということです。
ES-262 standardにも、ブラウザにも、まだ実装されていないとのことです。
つまり、今すぐ使える実用機能というより、JavaScriptの将来像のひとつとして見るのが正しいです。
とはいえ、こういう提案が出てくること自体が面白いです。
JavaScriptは長く「雑に広がってしまった世界」を抱えてきた言語なので、今になって“隔離”をちゃんと考えるのは、むしろ自然な進化だと思います。
ShadowRealm は、JavaScriptに「もうひとつの部屋」を用意する提案です。
ただしその部屋は、ただの別室ではなく、外の散らかりに影響されず、外にも余計なものをばらまかないための部屋です。
Web Worker ほど大げさではなく、iframe ほど重くもない。
それでいて global scope の汚染を避けやすい。
このバランス感覚はかなり魅力的だと思います。
もし将来これが標準化されれば、JavaScriptの「ごちゃごちゃしやすさ」に対する、かなり実用的な解決策になるのではないでしょうか。
参考: Soon We Can Finally Banish JavaScript to the ShadowRealm | CSS-Tricks