世界最速でHonoをAmplify Hostingで動かしてみた
前置き
筆者は(執筆時)AWSジャパンのソリューションアーキテクトです。本記事は個人的な興味関心による検証記事となります。
普段は常体で書いているこのブログですが、本記事は書きやすさから敬体で書いていきます。
AmplifyにおけるNext.js"以外"のSSRサポート
これまで、Next.jsのSSRのみをサポートしていましたが、Nuxt.jsのSSRサポートと同時に、node.jsで実行できる任意のフレームワークでSSRアプリケーションを実行できるようになりました。
ドキュメントには、Amplify Hosting用のディレクトリ構成が記載されており、ビルド成果物をこのディレクトリ構成に合わせて調整し、 deploy-manifest.json
に設定を記載してやることで任意のエントリポイントからアプリケーションを起動できます。
※2023/11/21時点ではドキュメントは英語版のみ更新されています
Next.js および Nuxt.js 以外は公式アダプタが提供されておらず、この記事を書いている時点では自分でこれらの設定を記述する必要があるので、勉強がてら自分でアダプターを設定して作ってみました。
直近で一番利用頻度が高いフレームワークと言うことで、Astro SSRを試してみたかったところですが、エムスリーさんに先を越されてしまったため、別のフレームワークで試してみることにしました。
そこで、軽量かつ、エッジランタイムで爆速なWebフレームワーク、HonoをAmplify Hostingにデプロイしていきます。
なお、本記事で利用しているコードは以下のリポジトリで公開しています。
Hono Node.js Adapter
Honoといえば、専ら話題になるのはCloudflare Workers, Deno, Bunなどのエッジランタイムでの動作ですが、LambdaやLambda@Edgeをはじめとする一般的なnode環境でも使うことができます。
Amplify HostingのSSRはnode.jsで動いているように読めるので、今回はこのアダプターを使ってHonoプロジェクトを作成していきます。
ドキュメントのQuickStartに従ってプロジェクトを作成します。
npm create hono@latest
>nodejs
SSRであることがわかりやすくなるよう、ヘッダからUser-Agentを表示するロジックを追加して、localhostで動作確認します。
//src/index.ts
import { serve } from "@hono/node-server"
import { Hono } from "hono"
const app = new Hono()
app.get("/", c => {
const userAgent = c.req.raw.headers.get("User-Agent")
return c.text(`Hello Hono!\nUA: ${userAgent}`)
})
serve(app)
うまくいきました。飾り気がないですが動作確認には十分なのでこのまま進めていきます。
Amplify Hosting用の設定ファイルを追加する
ドキュメントにExpressサーバー用のサンプルが記載されているので、これを参考にしながら設定していきます。
Amplify Hostingの設定については、ほぼエムスリーさんのブログの通りなので省略します。
たまたまローカルのnodeバージョンが18になっていたので、私は「ライブパッケージの更新」で Node.js version に18を設定しました。
tsxでentrypointを叩けるのか?
create hono
で作成したサンプルはtsxを使って内部的にesbuildを使ったトランスパイルをしてTypescriptを実行しており、プロジェクトディレクトリに明示的にトランスパイル後のファイルが出てきません。
これは、感覚としてはTypeScriptをそのまま実行できており、開発者体験もよいので、「tsxでの実行をそのままAmplify Hostingに持ち込めたらいいなぁ~~!」くらいの気持ちで、ソースをそのまま.amplify-hosting
ディレクトリにコピーしてentrypoint
にindex.tsを指定してみました。
npm serve
を自動的に認識していい感じに動いてくれればもうけものです。
//deploy-manifest.json
{
"version": 1,
"framework": { "name": "hono", "version": "3.10.2" },
"imageSettings": {
"sizes": [100, 200, 1920],
"domains": [],
"remotePatterns": [],
"formats": [],
"minimumCacheTTL": 60,
"dangerouslyAllowSVG": false
},
"routes": [
{
"path": "/_amplify/image",
"target": {
"kind": "ImageOptimization",
"cacheControl": "public, max-age=3600, immutable"
}
},
{
"path": "/*.*",
"target": {
"kind": "Static",
"cacheControl": "public, max-age=2"
},
"fallback": {
"kind": "Compute",
"src": "default"
}
},
{
"path": "/*",
"target": {
"kind": "Compute",
"src": "default"
}
}
],
"computeResources": [
{
"name": "default",
"runtime": "nodejs18.x",
"entrypoint": "index.ts" ←
}
]
}
ドキュメント通りにpostbuild.sh
も作りますが、ビルドせずにソースをそのままコピーしてみます。
# postbuild.sh
#!/bin/bash
rm -rf ./.amplify-hosting
mkdir -p ./.amplify-hosting/compute
# 改変
cp -r ./src ./.amplify-hosting/compute/default
cp -r ./node_modules ./.amplify-hosting/compute/default/node_modules
cp -r public ./.amplify-hosting/static
cp deploy-manifest.json ./.amplify-hosting/deploy-manifest.json
ビルドは成功したのですが、動きませんでした。そこまで都合よくはいきません…
jsファイルにトランスパイルする処理を追加していきます。
Honoのtsファイルをトランスパイルする
//tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
//package.json
{
"type": "commonjs",
"scripts": {
"start": "tsx watch src/index.ts",
"build": "tsc",
"serve": "tsx watch src/index.ts",
"postbuild": "chmod +x bin/postbuild.sh && ./bin/postbuild.sh"
},
"dependencies": {
"@hono/node-server": "^1.2.3",
"hono": "^3.10.2"
},
"devDependencies": {
"@types/node": "^20.9.3",
"tsx": "^3.12.2",
"typescript": "^5.3.2"
}
}
ビルドコマンドにtscを指定しつつ、ESmoduleとして認識されないように"type":"commonjs"
を追加します。
tsconfigは、ドキュメントのものをそのままコピペして、postbuild.sh
や deploy-manifest.json
もサンプル通りに修正しまして、無事デプロイが通りUser-Agentが表示されたことを確認出来ました。
オリジンの前にAmplifyがCloudfrontを挟んでアクセスさせているため、UAもCloudFrontと表示されています。
Discussion