Node.js の Webフレームワークと言えば Express ですが、それより18倍速い、という Bun の Web フレームワーク Elysia(エリシア) を見ていこうかなと思う今日この頃です。
さて今回は、Elysia の Quick Start 周辺を、少し脱線しつつうんちくも差し込みながら眺めようと思います。もし説明が回りくどいと思われる方は、先に本家の Quick Start ( https://elysiajs.com/quick-start.html ) へどうぞ^_^;
それにしても、Express の18倍とは凄いですね。まぁ、ベンチは実際とは違うものですけど、このチャートを見ると、上位の3つが圧倒的です。そしてどれも Bun なのですよね。感慨深いです。
関連サイト
では、まずとりあえず、主な関連サイトを並べておきますね。ElysiaJS 本家と Github、そして Bunのドキュメントです。
今回の環境
クラウド: Azure VM (これはオンプレでも何でも良い)
OS: Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-1050-azure x86_64)
Bun: v1.0.14 ( https://bun.sh/blog )
Node.js: v20.9.0
もちろん、ようは JavaScript や TypeScriptですから、これでなければいけないという事はありません。
Quick Start
何はともあれクイックスタートです。ほんとに動くのか?というところから始めてみます。
本家の Quick Start ( https://elysiajs.com/quick-start.html ) にあるコードは下記コードのタイトルに「◎」をつけておきます。ついていないのはこの記事の補足になります。
$ mkdir elysia
$ cd elysia
まずは elysia (好きな名前で良いです) というディレクトリを用意してそこへ入ります。
次は、本家の Quick Start にある最初の呪文です。Bunのインストールですね。
Bunのインストール
$ curl https://bun.sh/install | bash
これで Bun が入るのですが Bun のサイトでよくみかける "curl -fsSL https://bun.sh/install | bash" これと少し違いますね。まぁ、ほぼ余談ですがオプションパラメータが違うだけです。
寄り道して説明しておきます。
curl: HTTPリクエストを行うためのコマンドラインツール。
-fsSL: curlと共に使用されるオプションの組み合わせです。
詳細
-fまたは--fail:HTTPエラーを失敗として扱います。つまり、HTMLエラーページを表示しません。
-sまたは--silent:サイレントモード。進行状況やエラーメッセージを表示しません。
-Sまたは--show-error:エラーメッセージを表示します。これは、-sと組み合わせる場合に必要です。
-Lまたは--location:リダイレクトに従います。
したがって、-fsSLは、サイレントなHTTPリクエストを行い、リダイレクトに従い、エラーを失敗として扱うことを意味します。
https://bun.sh/installは、BunをダウンロードするためのURL。
|:パイプ。左側のコマンド(この場合はcurl)の出力を
右側のコマンド(この場合はbash)の入力として使用します。
bash:Bashシェル。URLから受け取ったスクリプトやコンテンツを解釈して実行するために使用されます。
要するに、このコマンドは指定されたURL(https://bun.sh/install)から
Bun をダウンロードし、それをBashシェル(bash)で実行するものです。
上記オプション無しで実行するとこんなアニメが表示され Bun が ダウンロード/ 実行 されます。
新しい elysia プロジェクトを作る
さて、次は、bun create を使用して新しいプロジェクトを立ち上げます。この「 bun create elysia hi-elysia」のhi-elysiaの部分はプロジェクト名で自由に命名します。
bun criate (https://bun.sh/docs/cli/bun-create) は、npm パッケージ、GitHub リポジトリ、またはローカル テンプレートを使用して新しいプロジェクトを作成しますが、詳しくは bun criate のリンク先をご覧ください。
$ bun create elysia hi-elysia
ディレクトリツリーはこう生成されました
この結果ディレクトリはこうなります。
./hi-elysia/
│ ├─README.md
│ ├─bun.lockb
│ └─node_modules
│ ├─@sinclair
│ ├─bun-types
│ ├─cookie
│ ├─elysia
│ ├─eventemitter3
│ ├─fast-decode-uri-component
│ ├─fast-querystring
│ ├─memoirist
│ └─openapi-types
├─package.json
├─src
│ └─index.ts
└─tsconfig.json
同時に Bun のモジュールキャッシュもできていた
因みにこの時、Bunは、下記のようにキャッシュディレクトリ「~/.bun/install/cache」配下へ次のようなキャッシュをダウンロードして、ディスク全体でパッケージモジュールを一意に管理します。
この場合例えば、 openapi-types@12.1.3 は通常の node_modules に入ってるのと同様のモジュールファイルです。
openapi-types は openapi-types@12.1.3 へのハードリンク。hogehoge.npmはその設定データになります。
ハードリンクやソフトリンクは、元のファイルと同じデータを共有するため、実データの複製は行われません。
そのため、通常のファイルと同じデータを持つハードリンクやソフトリンクを大量に作成しても、データ量は元のファイルとほぼ同じで、更に重複しない一意のキャッシュなのでディスクを節約します。
$ ls -l ~/.bun/install/cache
total 420
-rw-rw-r-- 1 tato tato 8488 Nov 27 21:59 05ed5331e4a4d393.npm
-rw-rw-r-- 1 tato tato 7336 Nov 27 21:59 13963cdf6b29d8e5.npm
-rw-rw-r-- 1 tato tato 1136 Nov 27 21:59 14a2c3ccac884310.npm
-rw-rw-r-- 1 tato tato 112144 Nov 27 21:59 22eb1288cd67ec7b.npm
-rw-rw-r-- 1 tato tato 2048 Nov 27 21:59 396e992ab5031124.npm
-rw-rw-r-- 1 tato tato 12232 Nov 27 21:59 4728612665ed89a7.npm
-rw-rw-r-- 1 tato tato 12576 Nov 27 21:59 77608e31e39bdf0f.npm
-rw-rw-r-- 1 tato tato 98576 Nov 27 21:59 85d93ddef8e3a043.npm
drwxr-xr-x 4 tato tato 4096 Nov 27 21:59 @sinclair
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 bun-types
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 bun-types@1.0.14
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 cookie
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 cookie@0.6.0
-rw-rw-r-- 1 tato tato 84048 Nov 27 21:59 e7b41234e0029d1f.npm
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 elysia
drwxr-xr-x 3 tato tato 4096 Nov 27 21:59 elysia@0.7.29
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 eventemitter3
drwxr-xr-x 3 tato tato 4096 Nov 27 21:59 eventemitter3@5.0.1
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 fast-decode-uri-component
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 fast-decode-uri-component@1.0.1
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 fast-querystring
drwxr-xr-x 3 tato tato 4096 Nov 27 21:59 fast-querystring@1.1.2
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 memoirist
drwxr-xr-x 4 tato tato 4096 Nov 27 21:59 memoirist@0.1.4
drwxr-xr-x 2 tato tato 4096 Nov 27 21:59 openapi-types
drwxr-xr-x 3 tato tato 4096 Nov 27 21:59 openapi-types@12.1.3
さて、これは何を意味するでしょう?Bun は Node.js/npm と互換性があるので、node_modules がディレクトリ内や上位ディレクトリにあれば、それを参照しますが、もしなければ、この ~/.bun/install/cache のキャッシュを参照します。
つまり、下記の作業などで node_modules を削除することで Bun スタイルの仕組みに切り替えて、Bun の恩恵を受けることができます。
この辺、くわしくは先日ふれた Bun のオンザフライインストールとキャッシュの記事あたりでもご覧ください。
node_modules がそのディレクトリに無くても動作するのです。
$ rm -rf node_modules
では、生成された hi-elysia ディレクトリに入ってみましょう。
$ cd hi-elysia
package.json はこう生成されてる
まず、package.json がどうなってるのか見ておきます。
$ cat package.json
{
"name": "hi-elysia",
"version": "1.0.50",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "bun run --watch src/index.ts"
},
"dependencies": {
"elysia": "latest"
},
"devDependencies": {
"bun-types": "latest"
},
"module": "src/index.js"
}t
"scripts" に "dev": "bun run --watch src/index.ts" とあるので、普通の package.json 同様に「bun dev」でその bun run...を開発用として実行します。
--watch は監視モードで bun を実行するためのオプションで、ファイルの変更が検出されるたびに、実行中の Bun プロセスが再起動されるので、コード修正の度に立ち上げなおす手間を省けます。
スクリプト本体をみてみる
では、src/index.ts を開いてみましょう。
package.json の「bun dev」で実行されるファイルですね。
$ cat src/index.ts
import { Elysia } from "elysia";
const app = new Elysia().get("/", () => "Hello Elysia").listen(9000);
console.log(
`? Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
src/index.ts の中をみればわかる通り、これは 2行 で書いた簡単なHTTPサーバーですね。
import { Elysia } from "elysia" で Elysia を読み込んで、
new Elysia() で作成したインスタンス app に HTTPメソッド get を呼び出して "/" パスにアクセスしたら "Hello Elysia" を返す処理を port 9000 に立てています。
それでは実行してみましょう。
実行する
$ bun dev
あるいは、下記などで、
$ bun src/index.ts
このサーバーを起動してから、 http://localhost:9000 や http://アドレス:9000 へブラウザなどでアクセスするとこんなレスポンスが返ります。
※もちろん、この時、ネットワーク設定やファイアウォールなどで port 9000 が開いてることは必要です。例えば、Azure や AWSなどでは、ネットワーク設定のインバウンド(受信用)ポートで 9000 を開けておきます。
上記のコードは例えばこう書いても同じです
import { Elysia } from "elysia";
new Elysia()
.get("/", () => {
return "Hello Elysia"
})
.listen(9000);
そして、jQuery などのようにメソッドチェーンをつないでいくこともできます。
new Elysia()
.get('/hi', () => 'Hi')
.post('/hi', () => 'From Post')
.put('/hi', () => 'From Put')
.route('M-SEARCH', () => 'Custom Method')
.listen(8080)
Elysiaの簡単なサンプル群
ElysiaJS>Cheat Sheet(チートシート)にシンプルなサンプル群が並んでいますので眺めてみることをお勧めします。
tsconfig.json
上記作業で生成された TypeScript プロジェクトの設定ファイル tsconfig.json の有効なプロパティは以下の通りでした。
下記の「cat tsconfig.json | grep -v "//"」は、 tsconfig.json ファイルから "//" でコメントアウトされていない行、つまり有効な設定を出力してくれます。
$ cat tsconfig.json | grep -v "//"
{
"compilerOptions": {
/* Projects */
/* Language and Environment */
"target": "ES2021",
/* Set the JavaScript language version
for emitted JavaScript and include compatible library declarations. */
/* Modules */
"module": "ES2022",
/* Specify what module code is generated. */
"moduleResolution": "node",
/* Specify how TypeScript looks up a file from a given module specifier. */
"types": ["bun-types"],
/* Specify type package names to be included
without being referenced in a source file. */
/* JavaScript Support */
/* Emit */
/* Interop Constraints */
"esModuleInterop": true,
/* Emit additional JavaScript to ease support
for importing CommonJS modules.
This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true,
/* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true,
/* Enable all strict type-checking options. */
/* Completeness */
"skipLibCheck": true
/* Skip type checking all .d.ts files. */
}
}
今日は、ElysiaJS の HTTPサーバーを眺めてみましたが、Elysiaには 高速なuWS (uWebSockets( https://github.com/uNetworking/uWebSockets ) )を利用した WebSocket サーバーも簡単に作れますし、ファイルアップロードなどなど様々な機能もあります。
順次、試していきたいと思っています。
最近 Qiita に書いた Bun 関連の記事