Fresh 用につくったミドルウェアを Fresh 1.3.0 のプラグイン形式に対応させる

Fresh 1.3.0 でプラグインでミドルウェアを適用できるようになった。

これまでも Fresh 用のミドルウェアは作っていたが、これを機にプラグイン対応させたいので確認がてらメモ。

参考

確認

「参考」に記載しているプルリクを見ると、プラグインは次のような形式で記述する必要がありそう。

1
2
3
4
5
6
7
8
9
10
11
{
name: "sameRoutePlugin",
middlewares: [{
middleware: { handler: sameHandler },
path: "/",
}],
routes: [{
path: "/_app",
component: sameComponent
}],
}

これは、fresh の提供する インタフェース Plugin に拠るもの。

Plugin 自体は、次のような定義になっている。

1
2
3
4
5
6
7
8
export interface Plugin {
name: string;
entrypoints?: Record<string, string>;
render?(ctx: PluginRenderContext): PluginRenderResult;
renderAsync?(ctx: PluginAsyncRenderContext): Promise<PluginRenderResult>;
routes?: PluginRoute[];
middlewares?: PluginMiddleware[];
}

これらのうち、routes と middlewares が今回のターゲット。

また、 PluginRoute と PluginMiddleware は次のような定義。

1
2
3
4
5
6
7
8
9
10
export interface PluginMiddleware {
path: string;
middleware: Middleware;
}

export interface PluginRoute {
path: string;
component?: ComponentType<PageProps> | ComponentType<AppProps>;
handler?: Handler<any, any> | Handlers<any, any>;
}

実装

既存では、次のように使うモジュールだった。

_middleware.ts
1
2
3
4
5
// _middleware.ts

import { loggerHandler } from "https://deno.land/x/fresh_logger@0.0.1/mod.ts";

export const handler = [loggerHandler];

これを以下のようにプラグインにした。

src/loggerPlugin.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/loggerPlugin.ts

import {
type Plugin,
type MiddlewareHandlerContext,
type MiddlewareHandler,
} from "../deps.ts";
import {
LogModule,
type Logger,
type RemoteLogSenderFunction,
} from "./loggerModule.ts";

export function getLoggerHandler(logSender?: RemoteLogSenderFunction): Plugin {
const handler: MiddlewareHandler<Logger> = async function (
_req: Request,
ctx: MiddlewareHandlerContext<Logger>
) {
ctx.state.logger = new LogModule(logSender);
return await ctx.next();
};

return {
name: "loggerPlugin",
middlewares: [
{
middleware: {
handler: handler as MiddlewareHandler<Record<"logger", unknown>>,
},
path: "/",
},
],
};
}

middleware の中の handler の型が、MiddlewareHandler<State = Record<string, unknown>> である。
ことため、元よりあるstateの型を設定できなかったので、しかたなく as を使っている。

きれいな書き方があれば直したい。

プラグイン使うFresh側は、次のよう記述する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />

import "$std/dotenv/load.ts";

import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";

import twindPlugin from "$fresh/plugins/twind.ts";
import twindConfig from "./twind.config.ts";

import { getLoggerHandler } from "https://deno.land/x/fresh-logger/mod.ts";

await start(manifest, {
plugins: [
getLoggerHandler(), // <= プラグインを追加
twindPlugin(twindConfig),
],
});

導入はこれだけ、説明のためにソースコードでは https://deno.land/x/~~ を書いているが、import-map に書くのが推奨になるはず。
Fresh のデフォルトのページでログを仕込むときは、次のように記述する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Head } from "$fresh/runtime.ts";
import { useSignal } from "@preact/signals";
import Counter from "../islands/Counter.tsx";
import {type Logger} from "https://deno.land/x/fresh-logger/mod.ts";

export default function Home({state}: {state: Logger} ) {
const count = useSignal(3);
state.logger.log("Home"); // <= 導入したロガー

return (
<>
<Head>
<title>test419-fresh-plugin</title>
</Head>
<div class="px-4 py-8 mx-auto bg-[#86efac]">
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
<img
class="my-6"
src="/logo.svg"
width="128"
height="128"
alt="the fresh logo: a sliced lemon dripping with juice"
/>
<h1 class="text-4xl font-bold">Welcome to fresh</h1>
<p class="my-4">
Try updating this message in the
<code class="mx-2">./routes/index.tsx</code> file, and refresh.
</p>
<Counter count={count} />
</div>
</div>
</>
);
}

実行時のログは、リクエスト単位でid を割り当てて、表示されるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
$ deno task start
Task start deno run -A --watch=static/,routes/ dev.ts
Watcher Process started.
The manifest has been generated for 5 routes and 1 islands.

🍋 Fresh ready
Local: http://localhost:8000/

{ log_id: "5a76vAfNDkRkCU_IulOhk", level: "log", body: "App" }
{ log_id: "5a76vAfNDkRkCU_IulOhk", level: "log", body: "Home" }
{ log_id: "h2RlWaAsTkAIT2s7iBowj", level: "log", body: "App" }
{ log_id: "h2RlWaAsTkAIT2s7iBowj", level: "log", body: "Home" }

Fresh 1.3.0 に対応して、ミドルウェアのプラグイン化を行った。
まだ、いくつか管理しているミドルウェアがあるので、これらも対応してゆきたい。

では。