fresh v1.3がリリースされました。
この記事では主な変更点などについて解説します。
非同期Routeコンポーネント
非同期Routeコンポーネントがサポートされました。
例えばデータベースや外部のAPIなどから非同期に取得したデータをRouteコンポーネントに渡すためには、今までは以下のようにhandler
を定義する必要がありました。
// routes/users/[id].tsx
import type { Handlers, PageProps } from "$fresh/server.ts";
export const handler: Handlers<Data> = {
async GET(req, ctx) {
const user = await findUserByID(ctx.params.id);
if (user == null) {
return ctx.renderNotFound();
}
const resp = await ctx.render(user);
return resp;
},
};
export default async function User(props: PageProps<User>) {
return <UserDetail user={props.data} />;
}
非同期Routeコンポーネントを利用することで、handler
を記述せずに非同期でのデータの取得とコンポーネントのレンダリングが行えるようになります。
// routes/users/[id].tsx
import type { RouteContext } from "$fresh/server.ts";
export default async function User(
req: Request,
ctx: RouteContext,
) {
const user = await findUserByID(ctx.params.id);
if (user == null) {
return ctx.renderNotFound();
}
return <UserDetail user={user} />;
}
上記のようにasync関数がdefault export
されている場合、その関数は非同期Routeコンポーネントとみなされます。
非同期RouteコンポーネントはRequest
とRouteContextを引数として受け取り、vnodeまたはResponse
のいずれかを返すことができます。
https://github.com/denoland/fresh/pull/1388
プラグインからのRouteとMiddlewareの注入がサポート
Pluginでroutes
とmiddlewares
プロパティがサポートされました。
これらを指定することで、プラグインによってRouteやMiddlewareを注入できます。
import type { Plugin } from "$fresh/server.ts";
interface Options {
path: string;
}
export default function samplePlugin(options: Options): Plugin {
return {
name: "samplePlugin",
// 全てのリクエスト(`path: "/"`)に対して実行されるMiddlewareを登録します。
middlewares: [{
middleware: { handler: loggingMiddleware },
path: "/",
}],
// `options.path`にアクセスされた際に、`component`がレンダリングされます。
routes: [{
path: options.path,
component: () => <div>hello</div>,
}],
};
}
https://github.com/denoland/fresh/pull/1455
Error Boundaryがサポート
PreactのError Boundaryがサポートされました。
// components/ErrorBoundary.tsx
import { Component } from "preact";
import ErrorView from "./ErrorView.tsx";
export default class ErrorBoundary extends Component {
state = { error: null } as { error: Error | null };
static getDerivedStateFromError(error: Error) {
return { error };
}
componentDidCatch(error) {
console.error(error.message);
}
render() {
if (this.state.error) {
return <ErrorView error={this.state.error} />;
}
return <>{this.props.children}</>;
}
}
Error Boundaryを利用することで、コンポーネントのレンダリング時に発生したエラーを補足できます。
// routes/index.tsx
import Main from "../components/Main.tsx";
import ErrorBoundary from "../components/ErrorBoundary.tsx";
export default function Index() {
return (
<ErrorBoundary>
<Main />
</ErrorBoundary>
);
}
- https://github.com/denoland/fresh/pull/1442
- https://github.com/preactjs/preact-render-to-string/releases/tag/v6.2.0
Islandコンポーネントに関する改善
単一ファイルでの複数コンポーネントのexport
がサポート
islands/
ディレクトリ内のファイルで複数のコンポーネントをexport
できるようになりました。(元々はdefault export
されたコンポーネントのみが許可されていました。)
// islands/foobar.tsx
export function Foo() {
return <div>foo</div>;
}
export function Bar() {
return <div>bar</div>;
}
https://github.com/denoland/fresh/pull/1397
props
でのbigint
型のサポート
bigint
型の値をIslandコンポーネントのprops
として渡せるようになりました。
// islands/Counter.tsx
import { useState } from "preact/hooks";
interface Props {
count: bigint;
}
export default function Counter(props: Props) {
const [count, setCount] = useState(props.count);
return (
<div class="flex gap-2 w-full">
<p class="flex-grow-1 font-bold text-xl">{count}</p>
<button onClick={() => setCount((count) => count - 1n)}>-1</button>
<button onClick={() => setCount((count) => count + 1n)}>+1</button>
</div>
);
}
<Counter count={123n} />
https://github.com/denoland/fresh/pull/1317
Server
デフォルトでDeno.serve
が使用されるように
Deno v1.35での安定化に合わせて、デフォルトでDeno.serveがFreshの内部で使われるようになりました。
これにより、パフォーマンスの改善などが期待されそうです。
https://github.com/denoland/fresh/pull/1427
HandlerContext.renderNotFound
でパラメータがサポート
HandlerContext.renderNotFound
に引数を渡せるようになりました。
// routes/users/[id].tsx
import type { Handlers, PageProps } from "$fresh/server.ts";
import type { Data } from "../_404.tsx";
export const handler: Handlers = {
async GET(req, ctx) {
const user = await getUser(ctx.params.id);
if (user == null) {
const data: Data = { message: "Not Found" };
return ctx.renderNotFound(data);
}
return ctx.render(user);
},
};
export default function Index(props: PageProps<User>) {
return <UserDetail user={props.data} />;
}
引数に渡した値は、_404.tsx
でprops.dataとして受け取ることができます。
// routes/_404.tsx
import { UnknownPageProps } from "$fresh/server.ts";
export interface Data {
message: string;
}
export default function NotFoundPage(props: UnknownPageProps<Data>) {
return <p>{props.data.message}</p>;
}
https://github.com/denoland/fresh/pull/1310
createHandler
で作ったハンドラの第2引数が省略可能に
createHandlerから返されるハンドラの第2引数の型がServeHandlerInfoという新しい型へ変更されています。
これに合わせて、ハンドラの第2引数を省略できるようになりました。
import manifest from "./fresh.gen.ts";
import { createHandler } from "$fresh/server.ts";
import twindv1 from "$fresh/plugins/twindv1.ts";
import twindConfig from "./twind.config.ts";
const handler = await createHandler(manifest, { plugins: [twindv1(twindConfig)] });
// 第2引数を省略できます。
const res = await handler(new Request("http://localhost:8000/"));
Routes
ミドルウェアでRouteパラメータを受け取れるように
MiddlewareHandlerContextにparams
プロパティが追加されました。
これにより、ミドルウェアでRouteパラメータを受け取れるように改善されています。
// routes /_middleware.ts
import { MiddlewareHandlerContext } from "$fresh/server.ts";
export async function handler(
_req: Request,
ctx: MiddlewareHandlerContext,
) {
doSomethingWithParams(ctx.params);
const resp = await ctx.next();
return resp;
}
https://github.com/denoland/fresh/pull/1314
Routeコンポーネントでstate
がサポート
PageProps及びAppPropsにstate
が追加されています。
これにより、各RouteコンポーネントなどがMiddlewareで設定されたstateを参照できるようになります
// types/route_state.ts
export interface State {
date: Date;
}
例えば、以下のようにMiddlewareでstate
に値を設定したとします。
// routes /_middleware.ts
import { MiddlewareHandlerContext } from "$fresh/server.ts";
import type { State } from "../types/route_state.ts";
export async function handler(
_req: Request,
ctx: MiddlewareHandlerContext<State>,
) {
ctx.state.date = new Date();
const resp = await ctx.next();
return resp;
}
state
に設定された値はRouteコンポーネントから参照できます。
// routes/index.tsx
import type { PageProps } from "$fresh/server.ts";
import type { State } from "../types/route_state.ts";
export default function Index(props: PageProps<unknown, State>) {
return (
<div>{props.state.date.toISOString()}</div>
);
}
https://github.com/denoland/fresh/pull/1264
重複するRouteの検出
重複したRouteを定義しようとした際に、エラーが発生するように挙動が改善されました。
例えば、routes/user.ts
とroutes/user.tsx
が同時に存在する場合はエラーが発生します。
https://github.com/denoland/fresh/pull/1410
Freshの自動アップデート
devサーバの起動時にFreshの最新バージョンがリリースされていないか自動でチェックが行われるようになりました。 (1日に1回)
もしFreshの最新バージョンがリリースされていたら、コンソールにメッセージが表示されます。
この機能を無効化したい場合は、FRESH_NO_UPDATE_CHECK
環境変数にtrue
を設定する必要があります。
バージョンチェック用のキャッシュファイルは、Linuxだと$XDG_CACHE_HOME/latest.json
, Macだと$HOME/Library/Caches/latest.json
に作られるようです。
https://github.com/denoland/fresh/pull/1444
バグ修正
- handlerが同期的に例外を投げた際に、
_500.tsx
が描画されない問題が修正されました - Middlewareがドキュメントに記述されている順序通りに実行されるように修正されました。 (#1090)
- router.trailingSlashオプションが有効化されている状態でハッシュやクエリパラメータが付与されたURLが渡された際に、意図した位置に
/
が付与されない問題が修正されました。