PIB - 20200918: Babel で IE11 対応

状況

Arrow Function 等、モダンな ECMAScript の機能を使いたかったので、in-browser Babel transformer を用いて IE11 対応しようと思った。

そこでまず、以下のようなコードを書いた。
<script>
document.addEventListener("DOMContentLoaded", ()=>{
  let div = document.createElement("div");
  div.textConten t= "hello, world";
  document.body.appendChild(div);
});
</script>
これは Firefox では問題なく動いたが、IE11 は Arrow Function に対応してないので、1行目で構文エラーとなる。

次に、in-browser で動かすために babel-standalone と babel-polyfill を追加して以下のようにした。
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js" integrity="sha512-kp7YHLxuJDJcOzStgd6vtpxr4ZU9kjn77e6dBsivSz+pUuAuMlE2UTdKB7jjsWT84qbS8kdCWHPETnP/ctrFsA==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.11.5/polyfill.min.js" integrity="sha512-uzOpZ74myvXTYZ+mXUsPhDF+/iL/n32GDxdryI2SJronkEyKC8FBFRLiBQ7l7U/PTYebDbgTtbqTa6/vGtU23A==" crossorigin="anonymous"></script>
<script type="text/babel">
document.addEventListener("DOMContentLoaded", ()=>{
  let div = document.createElement("div");
  div.textContent = "hello, world";
  document.body.appendChild(div);
});
</script>
これは Firefox, IE11 共に "hello, world" が表示されない。
コンソールには
You are using the in-browser Babel transformer. Be sure to precompile your scripts for production - https://babeljs.io/docs/setup/
のような警告が表示されてはいるが、エラーなしで動作している模様。

addEventListener の対象を document の代わりに window にして、イベントを DOMContentLoaded の代わりに load に変更してみたところ、これは一応期待に沿った動作をするのだ。
しかし、document の場合 DOMContentLoaded, load イベントの双方で "hello, world" が表示されない。
window の場合でも DOMContentLoaded イベントでは "hello, world" が表示されない。

つまり、in-browser で babel を使うと、なぜか DOMContentLoaded イベントが拾えないという状況。

原因

しばらく頭を抱えたのだが、
babel DOMContentLoaded」でググってみたところ、以下のページを見つけた。
曰く、「既に DOMContentLoaded が発火済みなんじゃね?」と。
document.readyuState を確認してみるといいらしい。

確認してみるたところ、素の JavaScript が "loading" であるのに対して、Balel を通した場合 "interactive" になっていた。

つまり、ブラウザ上で処理する手前、DOMContentLoaded で、<script type="text/babel"> 拾ってトランスパイルするしかないってことかな?
改めて考えてみると、そりゃそうだって感じ。

in-browser 版 babel の警告でも precompile 推奨っぽい事言われてるけど、インラインで書けないのはちょっと面倒な場面有りそうなのと、
node.js 使うの面倒なのと、
npm が ./node_modules/ 以下にモジュール大量にばら撒いたりとか、ソースとビルド結果とでディレクトリ分けしてたりとか、
いろいろ面倒そう。

DOMContentLoaded の拾い方

とりあえず、in-browser の babel で DOMContentLoaded 引っ掛けるにはどうするのが推奨なのだろう?
こうかな?
<script type="text/babel">
(proc => window.readyState !== 'loading' ? proc() : window.addEventListener("DOMContentLoaded", proc))(()=>{
  let div = document.createElement("div");
  div.textContent = "hello, world";
  document.body.appendChild(div);
});
</script>
動くけど、折角の Arrow Function でコンパクトに書いたのが台無し感高い。

他にも DOMContentLoaded 引っ掛けてるやつがいると、二重に実行されそうでよろしくないのだが、
readyState を見て、イベントを発行し直すコードを末尾に入れるという手は使えそう。
<script type="text/babel">
document.addEventListener("DOMContentLoaded", ()=>{
  let div = document.createElement("div");
  div.textContent = "hello, world";
  document.body.appendChild(div);
});
document.readyState !== 'loading' && document.dispatchEvent(new Event("DOMContentLoaded"));
</script>
一応 Firefox では動くんだけど、
IE11 は new Event() が出来ないらしく
SCRIPT445: このオブジェクトではサポートされていない操作です。
と言われてエラーになってしまう。

IE11 new Event()」でググってみると以下のページを見つけた。
new Event() 失敗した時用に fallback 用意しとけばどうにかなりそう?

一応
function newEvent(event) {
  var e;
  try {
    e = new Event(event);
  } catch(ex) {
    e = document.createEvent("Event");
    e.initEvent(event, false, true);
  }
  return e;
}
document.readyState !== 'loading' && document.dispatchEvent(newEvent("DOMContentLoaded"));
とすれば、IE11 でも動いたが、これを追加するのはやっぱり台無し感が高い。

いっそ、
<script type="text/babel">
function onload(){
  let div = document.createElement("div");
  div.textContent = "hello, world";
  document.body.appendChild(div);
}
</script>
<script>
document.addEventListener("DOMContentLoaded", function(){
  onload ? onload() : setTimeout(arguments.callee, 10);
})
</script>
のように、babel 側処理終わるまで、素の JavaScript の方で待たせるのも手かな?と思ったが、Arrow Function 消えてしまってるので、これはこれで本末転倒感が高いし、setTimeout のループで関数定義待ってるのもなんかダサいな。

補足

因みに、公式の例では CDN として unpkg.com が例示されてて、以下のようになっている。
<div id="output"></div>
<!-- Load Babel -->
<!-- v6 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Your custom script here -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
しかし、IE11 ではこのままだと babel.min.js を読み込む箇所で以下のように言われてエラーとなり in-browser で動いてくれない。
SCRIPT5009: 'Symbol' は定義されていません。

コメントアウトしてある v6 の方をアンコメントして、バージョン指定のなしで最新版読んでる方をコメントアウトしてやると動く。

babel IE11 Symbol undefined in-browser」でググると以下のページを見つけた。 なんか、pollyfill 入れとけば動くようなことを言われているようだ。
試しに
<script src="https://unpkg.com/@babel/polyfill/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
の順で読み込んでやると動いた(逆順だとやはり Symbol が未定義と言われて動かない)。

以上、IE11 対応、地味に嫌らしいなと。

参考

関連