iframe の中で ES Modules のコードを評価する

by mizchi created at 2020/05/28/01:25

ToC

rollup のような ESM を吐くツールのプレビューツールを作っていると、文字列として組み立てたコードを安全に評価したいことがあります。

方法

  • iframe を createObjectURL
  • dynamic import で data uri として文字列を評価
  • 得られた関数を実行

コード

// これを評価したい
const code = `export default () => { console.log("xxx") }`;

// 追記:2020/06/24 ユニコード文字対応
const encoded = btoa(unescape(encodeURIComponent(code)));

const blob = new Blob(
  [
    `<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
  <script type=module>
    import("data:text/javascript;base64,${encoded}").then(mod => mod.default());
  </script>
  </body>
</html>`,
  ],
  { type: "text/html" }
);

// const iframe = document.querySelector("iframe");
iframe.src = URL.createObjectURL(blob);

解説: data uri の import

ESM のコードを script タグの中で評価しても、実行はできても export されたオブジェクトの参照を手に入れることができません。

なので、 data uri 化されたコードを import(...) を経由することで、export オブジェクトを手に入れています。

import("data:text/javascript;base64,${encoded}").then((mod) => mod.default());

これでモジュールが手に入るので、関数として実行しています。

解説: iframe

UI スレッドで何度もコードを実行すると、window に起きる副作用の影響を受けてしまうので、実行コンテキストを隔離する目的で iframe を使います。

HTML を blob 化して URL.createObjectURL(blob) で, 何にも紐付かない iframe を作ります。

const blob = new Blob([`<!DOCTYPE html><html><body></body></html>`], {
  type: "text/html",
});
iframe.src = URL.createObjectURL(blob);

本当にセキュアにやるなら realm などを使ったほうがいいです。

tc39/proposal-realms: ECMAScript Proposal, specs, and reference implementation for Realms

History
b9b5552 - Update Wed Jun 24 19:23:11 2020 +0900 
169c33d - Add eval-iframe Thu May 28 04:01:05 2020 +0900