【ビルド不要】HTML単体でES6+をトランスパイルしIE11へ対応


(2020/07/03 ... 記事を全面リニューアルしました)

令和の時代、わが国の企業は大部分が未だIEの呪縛に囚われている・・・

MicrosoftがいくらIEを非推奨としてEdge(Chromium)を推奨しても、御社の情シスはそれを許しません。

そんな闘う社内エンジニアの方々に朗報です。

HTMLサンプル

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
        <meta http-equiv="x-ua-compatible" content="ie=edge">

        <script src="https://cdn.jsdelivr.net/npm/@babel/polyfill@latest/dist/polyfill.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/@babel/standalone@latest/babel.min.js"></script>

        <title>Babel</title>
    </head>

    <body>
        Babel Test
    </body>

    <style>
        html{
            color: #d8d8d8;
            background-color: #242424;
        }
    </style>

    <script type="text/babel" data-presets="env,stage-3">
        class FileReaderEx extends FileReader{
            constructor(){
                super();
            }

            #readAs(blob, ctx){
                return new Promise((res, rej)=>{
                    super.addEventListener("load", ({target}) => res(target.result));
                    super.addEventListener("error", ({target}) => rej(target.error));
                    super[ctx](blob);
                });
            }

            readAsArrayBuffer(blob){
                return this.#readAs(blob, "readAsArrayBuffer");
            }

            readAsDataURL(blob){
                return this.#readAs(blob, "readAsDataURL");
            }

            readAsText(blob){
                return this.#readAs(blob, "readAsText");
            }
        }

        (async()=>{
            const blob = new Blob([new Uint8Array(1024)]);
            const buffer = await new FileReaderEx().readAsArrayBuffer(blob);
            console.log(buffer);
        })();
    </script>
</html>

バリバリなES6+ですが、このHTMLはそっくりそのままIE11で動きます。

解説

@babel/standaloneというBabelのスタンドアロン版を使用します。
通常版はWebpackやParcelといったビルドツール経由で受動的に使用されることを前提としているのに対し、スタンドアロン版はそれ単体で能動的に動作します。

@babel/polyfillはBabelがトランスパイル時に使用する機能も含まれているので@babel/standaloneより先にロードする必要があります。

具体的な動作としては、HTML内の<script type="text/babel">タグを検出し、その中のソースコードをトランスパイルした後にHTML内へ挿入します。

通常であればbabelrcに記載するコンフィグはdata-xxx属性へ記載します。

babelrc
{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-stage-3"
    ]
}
<script type="text/babel" data-presets="env,stage-3">

また、Babelはwindow.Babelとしてグローバルスコープへ展開されているので、ソースコードを任意のタイミングでトランスパイルすることも可能です。

const src = "(async() => await new Promise(res => res(1)))();";

const transpiled = Babel.transform(src, {
    presets: ["env", "stage-3"]
}).code;

なお、通常版は7.4.0から@babel/polyfillの使用が非推奨となり、代わりにポリフィル内部で使用されていたcore-jsregeneratorを直接ロードする方法が推奨となりました。
しかし、この2つは現時点ではバンドル済のコードが存在しないため、スタンドアロン版ではバンドル済の@babel/polyfillを使用する必要があります。

おわりに

御社が1日も早くIEの呪縛から解放されますように...

SpecialThanks