本文は、フロントエンドフレームワークにおけるランタイムとコンパイル時の違いについて説明しています。まず、この記事は書籍 《Vue.js の設計と実装》 を参考にしており、それに自分の理解を加えて作成しました。もし間違いがあれば指摘してください。
ランタイム#
ランタイムとは、コードが実際に実行される段階のことを指します。フロントエンドのコードはブラウザで実行されるため、フレームワークのコードがブラウザで直接実行できる場合、それは純粋なランタイムフレームワークです。
例を挙げると、私たちがフレームワークを設計したとしましょう。そのフレームワークは Render 関数を提供し、ユーザーは DOM ツリーの構造を記述するパラメータオブジェクトをその関数に提供します。そして、Render 関数はそのオブジェクトに基づいてデータを再帰的に DOM 要素にレンダリングします。以下のようなオブジェクト構造を渡すことが規定されているとします:
const obj = {
tag: "div",
children: [
{
tag: "span",
children: "hello world!",
},
],
};
オブジェクトには 2 つのプロパティがあります:tag はタグ名を表し、children は配列(子ノードを表す)またはテキスト(テキスト子ノードを表す)のいずれかです。そして、Render 関数を実装します:
function Render(obj, root) {
const el = document.createElement(obj.tag);
if (typeof obj.children === "string") {
const text = document.createTextNode(obj.children);
el.appendChild(text);
} else if (obj.children) {
obj.children.forEach((child) => Render(child, el));
}
root.appendChild(el);
}
これでブラウザ環境で Render 関数を呼び出すことができます:
// bodyにレンダリングする
Render(obj, document.body);
これにより、期待どおりの結果が表示されます。これは純粋なランタイムのフレームワークの例です。ブラウザは JavaScript ファイルを直接実行できるため、変換操作は必要ありません。
コンパイル時#
ランタイムを理解したら、コンパイル時も理解しやすいです。コンパイル時は、ソースコードを実行可能なコードに変換する段階を指します。フロントエンドでは、最も単純な例は高度な ES6 構文をブラウザが理解して実行できる ES5 構文に変換することです。このプロセスもコンパイルと呼ばれます。
先ほどのコード例を再度使用して、それをコンパイル時のフレームワークに変更する方法を説明します。ただし、コンパイル時を追加するためにコンパイル時を追加するのではなく、問題を解決するためにコンパイル時を追加する必要があることを知っておく必要があります。
先ほどのランタイムのコードにはどのような問題があるでしょうか?明らかに DOM ツリーのオブジェクトを定義するのは少し手間がかかります。DOM の構造が複雑な場合、定義する必要があるオブジェクトのネストが深くなり、抽象的ではありません。そのため、直接 HTML のように UI を宣言できるようにすることはできないかと考えました。そうです、vue のテンプレート(template)のように書くことができるかと思います。まず、vue ファイルのテンプレート部分は html ではないことに注意してください。見た目は似ていますが、ブラウザはそれらを直接認識することはできませんので、テンプレートはコンパイルする必要があります。では、何にコンパイルするのでしょうか?
コンパイルの結果は、ブラウザで直接実行できるようにするためです。次のようにテンプレートを記述するとします:
<div>
<span>hello world!</span>
</div>
これは html ファイルではないことを覚えておいてください。したがって、これを命令形のコードにコンパイルする必要があります:
const div = document.createElement("div");
const span = document.createElement("span");
span.innerText = "hello world!";
div.appendChild(span);
document.body.appendChild(div);
これでブラウザはコードを直接実行し、期待どおりの結果が得られます。このプロセスをコンパイラでラップし、ユーザーのすべてのコードはコンパイラを介してコンパイルされる必要があります。これが純粋なコンパイル時のフレームワークであり、Svelte
のようなフレームワークを代表します。
ランタイム + コンパイル時#
先ほど、vue のテンプレートはブラウザで実行するためにコンパイルする必要があると述べましたが、vue はコンパイル時のフレームワークですか?答えは違います。
vue はランタイム + コンパイル時のフレームワークです。公式ドキュメントでもランタイムとコンパイル時の反応性に触れています。ドキュメントには次のように書かれています:
Vue の反応性システムは基本的にランタイムベースです。トラッキングとトリガリングはブラウザでランタイム中に行われます。
ここでは vue の実装の詳細には触れません。先ほどの例を再度使用して、ランタイム + コンパイル時のフレームワークをどのように実現するかを説明します。
実際には、コンパイラを追加するだけですが、命令形の作成コードに直接コンパイルするのではなく、テンプレートを DOM の記述オブジェクトにコンパイルし、そのオブジェクトをレンダラに渡すだけです。以下のようになります:
<div>
<span>hello world!</span>
</div>
をコンパイルすると ⬇️
const obj = {
tag: "div",
children: [
{
tag: "span",
children: "hello world!",
},
],
};
そしてレンダリングします:
Render(obj, document.body);
これがランタイム + コンパイル時のプロセスであり、正確にはランタイム中にコンパイルされます。実際のフレームワークの使用では、ビルドプロセス中にコンパイルを実行し、実行時にはコンパイルする必要がないため、パフォーマンスが向上します。
ここまでのコードを書いてみると、ランタイム中にコンパイルすることは本当に必要なのかと思うかもしれません。完全にコンパイルする方が簡単ではないですか?理論的には、純粋なコンパイル時のパフォーマンスが確かに優れています。ランタイムが関与しないため、コードは直接実行可能な JavaScript コードにコンパイルされます。ただし、これにより柔軟性の一部が失われ、ユーザーのコンテンツはコンパイル後にのみ使用できるようになります。一方、純粋なランタイムでは、コンパイルプロセスがないため、ユーザーが提供するコンテンツを分析することができず、対応する最適化を行うことができません。一般的に、コンパイラでは構文解析、最適化、変換などを実装でき、構文エラーチェック、モジュールパッケージング、コードの圧縮などのタスクを実行できます。フレームワークの作者はフレームワークを設計する際に自分自身の選択肢がありますので、どちらが良いかを直接判断することはできません。
フレームワークの使用 or 直接 HTML を書く#
上記の分析を見ると、フレームワークを捨てて直接 HTML を書く方が直感的で簡単ではないかと思うかもしれません。しかし、vue や react などの現代のフレームワークを使用することで、開発効率を大幅に向上させることができることを感じることもできるでしょう。その理由は、これらのフレームワークが作業プロセスを抽象化してくれるためです。従来のネイティブな HTML 開発では、DOM 要素の作成、更新、削除などの作業全体を自分で管理する必要があります。しかし、フレームワークを使用すると、結果の UI を宣言するだけで、実装の詳細について心配する必要はありません。これがフレームワークの利点です。ランタイムとコンパイル時を理解することも、フレームワークの実装原理をより良く理解するためです。
結論#
最後に、ランタイムとコンパイル時についてまとめます。
ランタイム(Runtime)は、コードが実際に実行される段階を指します。フロントエンドフレームワークでは、ランタイムは通常、ブラウザ環境でコンパイルされたコードが読み込まれて実行される段階を指します。この段階では、HTML 構造の解析、DOM ツリーの構築、JavaScript コードの実行、ユーザーの操作の処理などのタスクが含まれます。ランタイムでは、フロントエンドフレームワークはコンパイル時に生成されたコードを使用して、動的にページを更新およびレンダリングし、イベントやデータの変更などを処理します。
コンパイル時(Compile Time)は、開発プロセス中に、開発者が書いたソースコードを実行可能なコードに変換する段階を指します。例えば、ES6 を ES5 に変換する、vue のテンプレートを HTML に変換するなどです。コンパイル時に、フロントエンドフレームワークのコンパイラはコードを構文解析し、最適化し、変換して、ブラウザで実行可能なコードを生成します。コンパイル時のタスクには、構文チェック、モジュールのバンドル、コードの圧縮などが含まれます。