Bun & Hono🔥 & Cloudflare Workers🌥️
Bunのインストール
$ brew tap oven-sh/bun # for macOS and Linux
$ brew install bun
.zshrc にpathを通す
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
$ source ~/.zshrc
$ bun --version
1.0.26
path自動で通してくれるらしい。コマンド使えなかったら自分でファイルに追加
Honoでアプリを新規作成
$ bunx create-hono hono_app
create-hono version 0.3.2
✔ Using target directory … hono_app
✔ Which template do you want to use? › cloudflare-workers
cloned honojs/starter#main to /Users/midori/workspace/hono_app
✔ Copied project files
.
├── README.md
├── package.json
├── src
│ └── index.ts
├── tsconfig.json
└── wrangler.toml
node_modulesのインストール
$ bun install
bun install v1.0.26 (c75e768a)
Checked 76 installs across 102 packages (no changes) [4.42s]
bun.lockbが追加される。が、バイナリファイルなので参照できない。
なぜバイナリーなのか?
一言で言えば、パフォーマンスです。Bunのロックファイルは、保存とロードが驚くほど速く、一般的なロックファイルよりも多くのデータを保存する。
Bunのロックファイルを調べるには?
bun install -yを実行すると、より簡単に検査できるYarn互換のyarn.lock(v1)が生成されます。
ってことで
$ bun install -y
bun install v1.0.26 (c75e768a)
Checked 76 installs across 102 packages (no changes) [32.00ms]
yarn.lockが追加された。
├── package.json
├── src
│ └── index.ts
├── tsconfig.json
├── wrangler.toml
└── yarn.lock
2進数、もしくはコンピュータが読み取ることのできるデータ形式のことをバイナリまたはバイナリ形式と呼ぶ。
Bunのロックファイルはなぜ速いのか?
すべてのデータに線形配列を使用している。パッケージは自動インクリメントの整数IDかパッケージ名のハッシュで参照される。8文字以上の文字列は重複排除される。ディスクに保存する前に、パッケージツリーを歩き、依存関係順にパッケージをクローンすることで、ロックファイルはガベージコレクションされ、決定論的になります。
RUN
$ bun run --hot src/index.ts
$ npm run dev
> dev
> wrangler dev src/index.ts
⛅️ wrangler 3.28.1
-------------------
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
╭────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [b] open a browser, [d] open Devtools, [l] turn off local mode, [c] clear console, [x] to exit │
╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
お〜、npmの方が便利は便利。
.tsxを使用している時はコマンドの拡張子も変える
Cloudflare Workersにデプロイ
$ bun run deploy
wrangler deploy --minify src/index.ts
⛅️ wrangler 3.28.1
-------------------
Total Upload: 20.15 KiB / gzip: 7.45 KiB
Uploaded hono_app (0.96 sec)
Published hono_app (3.99 sec)
https://hono_app.mintgreen7programming.workers.dev
Current Deployment ID:
?!?!?!?!?!めちゃ簡単だが....ビビる....
Cloudflare Workersとは
概要
・世界中にデプロイされ、リクエストの送信元に最も近い地域のデータセンターにリクエストが届く。つまり、ユーザーに最も近い場所でサービスを提供するエッジコンピューティングという形態である
→情報の高速化、ネットワーク負荷の軽減、必要なデータのみをクラウドサーバへ送るためセキュリティも強化される
・サーバーレスである
→私たちがサーバを運用する必要がない
・似たサービスにAWS LambdaやGCP Cloud Runがあるが、Cloudflare Workersはコンテナ仮想化ではなくJavaScriptランタイムのV8を実質的な仮想化ソフトウェアとして利用している。
→コンテナの起動を待つ必要がなく、コールドスタートとホットスタートの概念もない。
料金
app.get('/', (c) => {
const posts = getPosts()
return c.html(<Top posts={posts} />)
})
(c)ってなに
GPT
app.get('/'):ルートパス('/')へのHTTP GETリクエストを処理するルートを設定します。
(c) => { ...}:これは、ルートのハンドラとして機能するアロー関数です。(c)はコンテキストまたはリクエスト/レスポンスオブジェクトを表すパラメータです。
const posts = getPosts():getPosts関数を使って投稿を取得しているように見えます。このスニペットには、この関数の詳細が記載されていません。
return c.html(<Top posts={posts} />):この行は、<Top>と呼ばれるコンポーネントまたはテンプレートによって生成されたHTMLコンテンツでクライアントに応答し、そのコンポーネントにpropとして投稿を渡しているようです。
cはコンテキストのc
htmlヘルパー
htmlヘルパーを使うと、JavaScriptのテンプレート・リテラルにhtmlというタグでHTMLを書くことができる。raw()を使用すると、コンテンツはそのままレンダリングされます。これらの文字列は自分でエスケープする必要があります。
import { Hono } from 'hono'
import { html, raw } from 'hono/html'
const app = new Hono()
app.get('/:username', (c) => {
const { username } = c.req.param()
return c.html(
html`<!DOCTYPE html>
<h1>Hello! ${username}!</h1>`
)
})
HonoRequest
HonoRequestは、Requestオブジェクトをラップしたc.reqから取り出せるオブジェクトである。
param()
パスパラメータの値を取得する。
// Captured params
app.get('/entry/:id', (c) => {
const id = c.req.param('id')
...
})
// Get all params at once
app.get('/entry/:id/comment/:commentId', (c) => {
const { id, commentId } = c.req.param()
})
Context
RequestとResponseを処理するために、Contextオブジェクトを使うことができる。
req
reqはHonoRequestのインスタンスである。
app.get('/hello', (c) => {
const userAgent = c.req.header('User-Agent')
...
})
body()
HTTP レスポンスを返します。
c.header()でヘッダを設定し、c.statusでHTTPステータスコードを設定します。
これは、c.text() や c.json() などでも設定できます。
⚠️注意:テキストまたはHTMLを返す場合は、c.text()またはc.html()を使用することを推奨する。
app.get('/welcome', (c) => {
// Set headers
c.header('X-Message', 'Hello!')
c.header('Content-Type', 'text/plain')
// Set HTTP status code
c.status(201)
// Return the response body
return c.body('Thank you for coming')
})
次のようにも書ける
app.get('/welcome', (c) => {
return c.body('Thank you for coming', 201, {
'X-Message': 'Hello!',
'Content-Type': 'text/plain',
})
})
レスポンス結果
new Response('Thank you for coming', {
status: 201,
headers: {
'X-Message': 'Hello',
'Content-Type': 'text/plain',
},
})
text()
Content-Type:text/plainとしてテキストをレンダリングする。
app.get('/say', (c) => {
return c.text('Hello!')
})
json()
JSONをContent-Type:application/jsonとしてレンダリングする。
app.get('/api', (c) => {
return c.json({ message: 'Hello!' })
})
html()
HTMLをContent-Type:text/htmlとしてレンダリングする。
app.get('/', (c) => {
return c.html('<h1>Hello! Hono!</h1>')
})
notFound()
Not Found レスポンスを返す。
app.get('/notfound', (c) => {
return c.notFound()
})
//error
Component' は値を参照していますが、ここでは型として使用されています。'typeof Component' を意図していましたか?
→.tsxに変えるの忘れてた
RUNコマンドも
hono_app % bun run --hot src/index.tsx
に変更