Azure Web AppsでHonoを動かすメモ

Azure FunctionsでHonoを動かすテンプレは公式に書いてますがWeb Appsだと無いみたいでした。

https://hono.dev/docs/getting-started/azure-functions

あとWeb AppsじゃなくてStatic Web Appsにデプロイするネタはたまに見かけますが、PaaSサービスのWeb Appsにデプロイしたいなと思った次第です。

## Web Appsはコードデプロイで立てておく

コンテナ起動かコードデプロイかのどちらかがありますが、コードデプロイでNode.js 20LTSを選択します。

v22などは現状選択不可でした。

- リージョン: West US
- プラン: B1

## 修正箇所コピペ用

まずはNode.jsのテンプレートでアプリ作成

https://hono.dev/docs/getting-started/nodejs

```bash
$ npm create hono@latest my-app
```

Which template do you want to use?で `nodejs` を選択します。

> ![スクリーンショット 2024-11-04 20.43.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/ed39afa8-e6c5-ba31-6af5-1dc9497907a9.png)

ファイルたちが生成されます。

> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/2def96c8-8db5-4e9b-288a-2933426acec7.png" width="400px" />

`packege.json`と`src/index.ts`を以下のように書き換えます。

```src/index.ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {

  return c.text('Hello Hono!')
})

const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;

console.log(`Server is running on http://localhost:${port}`)

serve({
  fetch: app.fetch,
  port
})

```


```package.json
{
  "name": "hono-node",
  "type": "module",
  "scripts": {
    "start": "node --experimental-specifier-resolution=node --import tsx src/index.ts",
    "dev": "tsx watch src/index.ts"
  },
  "dependencies": {
    "@hono/node-server": "^1.13.4",
    "hono": "^4.6.8"
  },
  "devDependencies": {
    "@types/node": "^20.11.17",
    "tsx": "^4.7.1"
  }
}
```

あとはデプロイすればOKです。

GitHub経由でのデプロイがオススメですがデフォルトで作成されるActionsファイルが古いので差し替えると良いです。

https://qiita.com/n0bisuke/items/41131f40ed3225f28df6

## ポイント解説など

解説というかハマったところです。

### ポート

Azure Web Appsだと`process.env.PORT`にサーバーの起動ポートが入っていく形になるので

Honoデフォルトのテンプレで作成されるコードにある3000番ポート指定から変えてあげます。

```src/index.ts
省略

const port = 3000

省略
```

↓変更後

```src/index.ts
省略

const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;

省略
```

### 起動方法

Azure Web Appsだと起動時に`npm run start` （`npm start`）をするっぽいのでscriptsにstartの記述が欲しいです。

また、 `"start": "tsx src/index.ts"`で試したところ、Azure Web Appsで何億回もみる`:( Application Error`となりました。

ログストリームでログを見たところ、

> ![スクリーンショット 2024-11-04 20.54.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/4f080248-fb8e-d7b1-ec38-75dceee363e4.png)

```
2024-11-04T11:12:23.786301046Z > start
2024-11-04T11:12:23.786307346Z > tsx src/index.ts
2024-11-04T11:12:23.786311447Z
2024-11-04T11:12:24.729976104Z (node:100) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
2024-11-04T11:12:24.730062905Z (Use `node --trace-warnings ...` to show where the warning was created)
2024-11-04T11:12:24.815200758Z /home/site/wwwroot/node_modules/.bin/tsx:2
.
.
.
.
e}a(De,"g");var xu=a(t=>Math.max(...t.split(`
2024-11-04T11:12:24.816810474Z                                                                                  ^^^^^^
2024-11-04T11:12:24.816814874Z
2024-11-04T11:12:24.816818274Z SyntaxError: Cannot use import statement outside a module
2024-11-04T11:12:24.816821674Z     at wrapSafe (node:internal/modules/cjs/loader:1281:20)
2024-11-04T11:12:24.816825074Z     at Module._compile (node:internal/modules/cjs/loader:1321:27)
2024-11-04T11:12:24.816828574Z     at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
2024-11-04T11:12:24.816831974Z     at Module.load (node:internal/modules/cjs/loader:1208:32)
2024-11-04T11:12:24.816835275Z     at Module._load (node:internal/modules/cjs/loader:1024:12)
2024-11-04T11:12:24.816838775Z     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
2024-11-04T11:12:24.816842275Z     at node:internal/main/run_main_module:28:49

```

といったエラーになっていました。

GPT-4oに聞いたところ

> 原因: tsx コマンドを使用していますが、Node.js環境がモジュール形式を適切に解釈していない可能性があります。
	•	解決策:
	•	tsx は通常ESモジュールと互換性があるはずですが、Azureの環境で何らかの理由で問題が発生しているようです。
	•	"type": "module" が設定されている package.json を確認してありますが、問題が解決しない場合は以下を試してください:
	1.	Node.js のオプションを明示的に指定:
	•	スクリプト内でtsxを呼び出す際に、nodeに対してESモジュールを強制的に有効にするオプションを追加します。
`"start": "node --experimental-specifier-resolution=node --loader tsx src/index.ts",`

とのことです。

ただ--loaderオプションが非推奨になっていてエラーが出たため以下の記述に落ち着きました。

`"start": "node --experimental-specifier-resolution=node --import tsx src/index.ts"`

```packege.json
{
  "name": "my-app",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/index.ts"
  },
  "dependencies": {
    "@hono/node-server": "^1.13.5",
    "hono": "^4.6.9"
  },
  "devDependencies": {
    "@types/node": "^20.11.17",
    "tsx": "^4.7.1"
  }
}
```

↓変更後

```packege.json
{
  "name": "hono-node",
  "type": "module",
  "scripts": {
    "start": "node --experimental-specifier-resolution=node --import tsx src/index.ts",
    "dev": "tsx watch src/index.ts"
  },
  "dependencies": {
    "@hono/node-server": "^1.13.4",
    "hono": "^4.6.8"
  },
  "devDependencies": {
    "@types/node": "^20.11.17",
    "tsx": "^4.7.1"
  }
}
```

ローカルで動くけどAzure Web Appsでtsxがうまく動かない問題はあるみたいですね。

Web Apps上のnodeコマンドはES ModulesやTypeScriptに対して特別な処理をしない限り、tsxを正しく解釈できないぽいです。（たぶん）

## まとめ

この設定でローカルでもWeb Apps上でもHonoが動いてくれました。

> ![スクリーンショット 2024-11-04 21.18.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/42b37b54-7030-ab1c-53c8-c7e4f2a09841.png)

ここから複雑にしていったときにまた別の問題あるかもですが一旦は動いてくれてます。