古いブラウザのための旅:トランスパイルと Babel

古いブラウザのための旅:トランスパイルと Babel

この記事は他の言語でも読めます。

最近は、古いブラウザを使っているユーザーはごくわずかです。主要ブラウザの最適化も進み、ほとんどが自動アップデートに対応しているので、わざわざ古いバージョンにこだわる理由が少ないからです。

しかし韓国の多くの企業や官公庁では、いまだに古いブラウザである Internet Explorer(以下 IE)を使っているところが見られます。関連する業務を担当するフロントエンド開発者にとっては、本当に骨の折れることです。

2024 年になっても、IE をサポートするために JavaScript を書くのは簡単ではありません。新しい構文なら 1 行で書けるコードも、古い構文では何十行も書かなければならないことがあるからです。

でも幸い、世界中の開発者は過去にすでに同じ問題に悩み、解決策も作ってくれています。この記事では、古いブラウザをサポートするための方法であるポリフィル(Polyfill)とトランスパイル(Transpile)について説明し、それを自動化するために生まれたツール Babel について見ていきます。


最新のコードを古いブラウザで実行したら?

まず、正しく書かれた最新構文の JavaScript コードを IE で実行すると、どんな問題が起きるのかを見てみましょう。サンプルコードと実行結果は次のとおりです。

例 1

alert('Hello, World!');
Object.assign({});

例 2

alert('Hello, World!');
async function a() {}

例 1 と例 2 を実行すると、どちらもエラーになりました。しかし、2 つのエラーには大きな違いがあります。

まず例 1 では、コードを実行すると Alert が表示されたあと、コンソールにその関数が存在しないというエラーが出ました。これはコード全体の解釈には成功したものの、実行中にその関数が見つからずに起きたエラーです。こうしたエラーは ランタイムエラー(Runtime Error) と呼ばれます。

一方で例 2 では、コードを実行しても Alert すら表示されず、コンソールにはわかりにくいエラーが出ました。これはコード全体の解釈に失敗し、コードの実行を完全に止めてしまったケースです。こうしたエラーは 構文エラー(Syntax Error) と呼ばれます。

例 1 の解決

では、それぞれのエラーを解決する方法を見ていきましょう。まず例 1 は簡単に解決できます。次のように Object.assign 関数を前もって自分で定義すればよいのです。

if (!Object.prototype.entries) {
  Object.prototype.assign = function () {
    // 実装は省略
  };
}

alert('Hello, World!');
Object.assign({});

ここで重要なのは、元のコードは変更せず、前に何かを埋めておいただけ だということです。こうした方法は、「埋める」という意味を込めて ポリフィル(Polyfill) と呼ばれます。

例 2 の解決

一方で例 2 は、コードを実行する前の構文解析の段階からエラーになるので、ポリフィル だけでは解決できません。この場合は、次のようにコードそのものを、そのブラウザが解釈できる構文に書き換える必要があります。

alert('Hello, World!');
function a() {
  return new Promise(function(resolve) {
    resolve();
  })
}

ここで重要なのは、元のコードを古いブラウザが解釈できる構文に書き換えた ことです。こうした方法は、「変換」という意味を込めて トランスパイル(Transpile) と呼ばれます。

まとめると、最新の JavaScript コードで古いブラウザをサポートするには、まず適切なポリフィルを埋め込み、ポリフィルで解決できない場合はトランスパイルも必要だということがわかります。

しかし、これを開発者がひとつひとつ処理するのはとても面倒だと想像できます。フロントエンドはその性質上、たくさんのライブラリを一緒に使うので、それらすべてのライブラリにポリフィルを書いてトランスパイルするのは不可能に近いからです。ここからは、この問題を解決するために生まれた Babel について見ていきましょう。


私たちを救う Babel

Babel は、先ほど説明した ポリフィル(Polyfill) と トランスパイル(Transpile) を自動で処理するために作られたツールです。開発者が最新の構文でコードを書くだけで、Babel は古いブラウザでもきちんと動く古い形式のコードを出力してくれます。

ここからは Babel の ドキュメント で案内されている使い方のガイドに沿って、Babel がどのような過程でコードを変換するのかを見ていきます。

古いバージョンの Babel の使い方を扱っています。使い方よりも、仕組みだけを参考にすることをおすすめします。

Babel のポリフィル(Polyfill)

ドキュメントでは、IE の JavaScript エンジンが解釈できるように ES2015+ の仕様のコードを変換するために、まず次のライブラリをコードの先頭に追加するよう案内しています。

import "core-js/stable";
import "regenerator-runtime/runtime";

先ほど、元のコードの前に何かを追加することをポリフィル(Polyfill)と説明しました。core-js は、さまざまなポリフィルの集まりです。古いブラウザの JavaScript エンジンに組み込まれていない、さまざまな最新の ECMAScript 標準関数をあらかじめ定義してくれます。

Babel のトランスパイル(Transpile)

ポリフィルのために、すでに core-js を追加しました。しかし Babel は、さらに regenerator-runtime ライブラリもプロジェクトの先頭に追加するよう案内しています。これが何のためのものなのかを見ていきましょう。

次の例は、ポリフィルでは解決できないため、元のコードの構文を書き換える(トランスパイルする)必要があるコードです。このコードを Babel で変換してみます。

例

async function a() {}

例のコードの変換結果

"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

function test() {
  return _test.apply(this, arguments);
}

function _test() {
  // ------------------------------------ ここに注目してください。 ---------------------------------------
  _test = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
          case "end":
            return _context.stop();
        }
      }
    }, _callee);
  }));
  return _test.apply(this, arguments);
}

結果は少し複雑に見えます。しかし注目すべきなのは、async のコードが regeneratorRuntime を参照する構文に変換されたことです。

このように Babel は、ポリフィルでは解決できない非同期のコードを、regenerator-runtime ライブラリを使う構文にトランスパイルします。だから、コードの先頭に regenerator-runtime ライブラリも一緒に追加する必要があるのです。

まとめると、Babel は core-js でポリフィルを適用し、regenerator-runtime を使ってコードをトランスパイルします。これによって、最新の構文のコードを、古いブラウザでも正しく動くコードに変換します。


Babel の限界

Babel を一度設定しておけば、あとは手を入れなくても古いブラウザで正しく動くと思うかもしれません。しかし実際はそうではありません。ユーザーのコードによっては、Babel の core-js と regenerator-runtime だけでは解決できない状況を見ていきましょう。

core-js の限界

まず、core-js は ECMAScript の標準関数だけで構成されています。ECMAScript は、JavaScript という言語として必要な機能だけを定義しています。つまり、ブラウザで必要な機能は含まれていないということです。

ブラウザでだけ必要な機能の例が ResizeObserver です。この API は、ブラウザの DOM 要素のサイズが変わるたびにイベントを発生させる機能を提供します。しかし、これは ECMAScript の標準ではないので core-js では提供されません。

そのため、こうした API を使う必要がある場合は、サードパーティのポリフィルライブラリを含めるか、開発者が自分で書く必要があります。また開発者は、自分が使う API が ECMAScript の標準なのか、ブラウザだけがサポートする API なのかを慎重に確認して使わなければなりません。ポリフィルを追加し忘れると、古いブラウザでアプリが突然動かなくなる可能性があるからです。

regenerator-runtime の限界

Babel は最新の JavaScript 構文のほとんどをよくサポートしていますが、まだ ECMAScript の標準になっていない Stage 3 以下の構文は公式にはサポートしていません。

その代表的な例が Metadata と Decorator です。どちらも標準にはなっていませんが、TypeScript の普及によって広く使われている構文です。Decorator のコード例は次のとおりです。

@annotation
class MyClass {
  constructor() {
    this.name = name;
  }
}

これを Babel で使うには、別途サードパーティのプラグインをインストールする必要があります。JavaScript の標準は進化し続けているので、core-js と同じように、開発者は自分の使う構文が Babel で処理できるかを確認して使う必要があります。

html と css は?

最後に、Babel は JavaScript のためのツールです。しかしウェブサービスは JavaScript だけでできているわけではありません。JavaScript の問題を解決したら、次は html と css の問題に向き合う番です。こちらにも Babel のようなツールはあるのでしょうか?

残念ながら、html と css には Babel と同じレベルのツールが不足しています。css には lightningcss や postcss といったツールがありますが、Babel ほどの手軽さはありません。html にはまだ方法がないので、開発の段階から古いブラウザを考慮してコードを書く必要があります。


Internet Explorer はいつ消えるのか

ここまで、古いブラウザをサポートするための方法とその発展、そして限界について見てきました。最後に、Internet Explorer がいつ頃消えるのかについて、個人的な考えをお伝えします。

私が 2018 年に陸軍を除隊したとき、軍で標準として使われていたブラウザは IE8 でした。今どきどうしてあんなブラウザを、と思うかもしれませんが、軍の業務システムはすべて IE8 の ActiveX 技術をベースに開発されていたので、一気に移行するのも簡単ではなさそうでした。

時が経ち、Microsoft は Windows 11 から IE を削除する方針を打ち出しました。その後、多くのメディアがついに IE が消えると歓迎しました。しかし実は、この話は Windows 10 のリリースのときにも出ていました。

かつて Windows 10 がリリースされたとき、Microsoft は IE を補助的なプログラムに格下げし、新しい Edge ブラウザをリリースしました。誰もが IE が歴史の中に消えると喜びましたが、それでも IE は使われ続けました。Windows 11 でも Edge ブラウザの中で IE 互換モードを提供する予定なので、IE はこれからも使われ続けるだろうと予想しています。

私は、IE が企業や官公庁からまで完全に姿を消すのは、早くても 2040 年頃だと見ています。これすら確信が持てないのは、組織の業務システムというものは決して簡単には変わらないからです。Microsoft もそれを認識していて、IE 互換モードを少なくとも 2029 年までサポートすると約束しています。

開発者にとって下位互換の作業はいつも苦痛です。なので私も、Internet Explorer が一日も早く退場してくれることを願っています。ただ、その日が本当に来るのかはわかりません。

author profile
winetree94

ウェブフロントエンドエンジニア兼、自室の DevOps エンジニアです。
コツコツ書くブロガーになりたいのですが、そうするには怠けすぎています。