JavaScriptランタイム
動作にあたり動作環境(ランタイム)を必要とするプログラミング言語には、たいてい「標準」といえるランタイムが存在する。
ex)
- Python: CPython
- Java: Oracle JDK
ところがJavaScriptのランタイムは複数あり、「標準」がない。
ブラウザ
元々JavaScriptはブラウザで実行するものであり、各ブラウザが独自のJavaScriptエンジン(ランタイムのコア)を実装している。
-
Chrome(Chromium): V8
- Edge、OperaなどはカスタムChromiumなのでここ
- Firefox: SpiderMonkey
- Safari: JavaScriptCore
これらはそれぞれ挙動が異なり、対応するJavaScriptの文法も違う。さらにブラウザバージョンによっても違う。
ローカル・サーバ向けランタイム
JavaScriptをサーバでも使いたいということで、ブラウザ外で動作するJavaScriptランタイムも作られるようになった。
後にフロントエンド開発全般において使用されるようになる。
Node.js
ローカル・サーバ用として初めて実装されたランタイムであり、デファクトスタンダード。ChromiumのJavaScriptエンジンであるV8を内部で使用している。
開発用途においては、特別な理由や思想がない場合はこれを使えばよいはず。
Deno
Node.jsの作者(ライアン・ダール)が新しく作成したランタイム。同様にV8を使用している。
Node.jsに対する作者の反省を元に作られており、Node.jsとは異なるパッケージシステムなどを採用する。
bun
高速性を売りにしたランタイム。
Node.js/Denoとは異なり、Safariで使われるJavaScriptCoreをJavaScriptエンジンとして採用している。
エッジランタイム
最近はCDNに設置されたエッジで動作するランタイムも出現している。
HTTPリクエストごとに起動するサーバレスであることがほとんどで、実行時間やメモリ量、アプリケーションサイズ、使用できる文法などに大きな制約がかかる。
近年は制約がゆるくなってきたこともあり、エッジランタイムでNext.jsなどのフレームワークを動かしてしまう例も増えてきている。
Cloudflare Workers
実行時間 | ~30秒 |
---|---|
メモリ | ~128MB |
バンドルサイズ |
~10MB
(1MBまでを推奨) |
Cloudflare社が提供するCDN上で動作するエッジランタイム。
Node.jsと同じくV8をベースにしているため、比較的Node.jsとの互換性が高い。
Lambda@Edge
実行時間 | ~5秒 |
---|---|
メモリ | ~128MB |
バンドルサイズ | ~50MB |
AWS Lambdaのエッジ版。CloudFront上で動作する。
基本的にはLambda同様にNode.jsを利用できるが、環境変数が使えないなどの制限がかかる。実行時間の制限なども通常のLambdaより厳しい。
CloudFront Functions
実行時間 | 1ミリ秒未満 |
---|---|
メモリ | ~2MB |
バンドルサイズ | ~10KB |
同じくCloudFront上で動くエッジランタイム。
動作条件が非常に厳しく、またNode.jsではなく独自ランタイムになっている。JavaScript文法も非常に限られたものしか使えない。
その代わりLambda@Edgeより高速・大量・安価にアクセスをさばける。
言語仕様
JavaScriptは各ランタイムが独自に実装しているため、その動作はバラバラである。
一応、 文法は ECMAScript という形で規格化されている。
2015年以降は毎年仕様が更新され、ECMAScript2024のような形で呼ばれる。
ただし各ランタイムによって対応状況はまちまちで、個別の文法レベルで対応状況が異なる。
文法ごとの対応状況はCanIUseなどのサイトで確認することになる…
モジュールシステム
他言語でいう
import
など、他のソースファイルやライブラリを読み込む機能をモジュールシステムと呼ぶ。
JavaScriptにはもともとモジュールシステムは存在しなかった。元はHTMLからしか呼び出せなかったので、
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
/* ここに自前のJavaScriptソース */
</script>
</head>
のようにすればライブラリは読み込めたからである。
Node.jsなどの出現により、HTMLを経由することなくJavaScriptから別のJavaScriptファイルを読み込む必要が生じた。ここで歴史的経緯により複数のモジュールシステムが存在してしまっている。
CommonJS (CJS)
// ライブラリ側
const add = (a: number, b: number): number => {
return a + b;
};
module.exports = {
add
};
// 呼び出す側
const { add } = require('./lib');
add(1, 2);
Node.jsが最初に採用したモジュールシステム。
require()
を用いて別ファイルを読み込む。
それまでのJavaScript文法を壊さないように導入されたので、
require()
はただの
関数
である。JavaScriptの文法ではない。
ES Modules (ESM)
// ライブラリ側
const add = (a: number, b: number): number => {
return a + b;
};
export { add };
// 呼び出す側
import { add } from './lib.js';
add(1, 2);
CommonJSなどの出現を受け、JavaScript(ECMAScript)の 文法 として整備されたモジュールシステム。ECMAScript 2015より使用できる。
公式な文法なのでブラウザでも使える。Node.js 12以上でもサポートするようになった。
Node.js界隈でもこちらへの移行が進んでおり、いずれは統一されるものと思われる。
既に書いてしまったコードはなかなか移行が難しく、各社苦労している。