マスタリングDeno Fresh メモ
⚠️これはドキュメントを読んだメモです。
テンション上げていけという気持ちでマスタリングという名をつけています。
Deno Freshとは
速度・信頼性・シンプルを掲げて作られた、Deno実装の次世代Webフレームワーク。ビルドステップがなくエッジレンダリングベース。TypeScriptやフロントエンド開発に必要な基本的な機能に設定を必要としない。アイランドアーキテクチャを採用していて、デフォルトではクライアントはJavaScriptを必要としない。
引用: https://fresh.deno.dev/
Fresh embraces the tried and true design of server side rendering and progressive enhancement on the client side.
よくわかっていない単語集
プロジェクト作成
❯ deno run -A -r https://fresh.deno.dev deno-fresh-project 🍋 Fresh: the next-gen web framework. Let's set up your new Fresh project. Fresh has built in support for styling using Tailwind CSS. Do you want to use this? [y/N] y Do you use VS Code? [y/N] y The manifest has been generated for 3 routes and 1 islands. Project initialized! Enter your project directory using cd deno-fresh-project. Run deno task start to start the project. CTRL-C to stop. Stuck? Join our Discord https://discord.gg/deno Happy hacking! 🦕
サーバー起動
❯ deno task start Task start deno run -A --watch=static/,routes/ dev.ts Watcher Process started. The manifest has been generated for 3 routes and 1 islands. Listening on http://localhost:8000/
Hot Module Reloadに対応していて、ファイルの変更保存を検知してブラウザページがリロードされる。
ディレクトリ構造
❯ tree . . ├── README.md ├── components │ └── Button.tsx ├── deno.json ├── dev.ts ├── fresh.gen.ts ├── import_map.json ├── islands │ └── Counter.tsx ├── main.ts ├── routes │ ├── [name].tsx │ ├── api │ │ └── joke.ts │ └── index.tsx ├── static │ ├── favicon.ico │ └── logo.svg └── twind.config.ts 5 directories, 14 files
ファイル
deno.json
: package.jsonのようなもの
dev.ts
: プロジェクトのエントリーポイント。deno.jsonのタスクで指定される
fresh.gen.ts
: Freshが生成するマニフェストファイル。アイランドアーキテクチャで読み込みするために使われてそう
import_map.json
: Denoでよくライブラリ import/export をするファイル
ディレクトリ
routes
: ファイルシステムベースのルーティング。アイランドアーキテクチャな分そのまま配信しているわけではなさそう
islands
: インタラクティブアイランドを置くディレクトリ
components
: コンポーネントを置く場所ではあるけどislandsとの違いは何だろう?
static
: 静的ファイルを置くディレクトリ
ルート作成
routes
ディレクトリ内に tsx
ファイルを追加するとルーティングが追加される。
ドキュメントの例だと AboutPage
という名前で関数を定義しているが default export で出していればなんでもいい。
引用: https://fresh.deno.dev/docs/getting-started/create-a-route
// routes/about.tsx export default function AboutPage() { return ( <main> <h1>About</h1> <p>This is the about page.</p> </main> ); }
ダイナミックルーティング
[propName].tsx
でファイル名を作成してページコンポーネントの引数でpropNameを受け取ることができる。
引用: https://fresh.deno.dev/docs/getting-started/dynamic-routes
// routes/[name].tsx import { PageProps } from "$fresh/server.ts"; export default function GreetPage(props: PageProps) { const { name } = props.params; return ( <main> <p>Greetings to you, {name}!</p> </main>); }
ファイルシステムルーティングって
仕様もしくはそういうフレームワークあるのかな。
型で保護を受けたいみたいな視点でいうと難しさもあるらしい。
カスタムハンドラー
ルートファイルの中で handler
オブジェクトを名前付きエクスポートして適用する。 handler
オブジェクトはHTTPリクエストメソッド毎に定義ができる。
⚠️ Next.jsと若干違うところ
Next.jsでは getStatisProps
やgetServerSideProps
といった関数を定義することで、リクエストに対してハンドラーのようなものを定義できる。
Next.jsでは getServerSideProps
後にページコンポーネントの処理に移る。対してFreshではハンドラー内で受け取る ctx
を使って、ページコンポーネントのレンダー結果をレスポンスに含める。
引用: https://fresh.deno.dev/docs/getting-started/custom-handlers
// routes/about.tsx import { Handlers } from "$fresh/server.ts"; export const handler: Handlers = { async GET(req, ctx) { const resp = await ctx.render(); resp.headers.set("X-Custom-Header", "Hello"); return resp; }, }; export default function AboutPage() { return ( <main> <h1>About</h1> <p>This is the about page.</p> </main>); }
ページコンポーネントではなく、JSONやファイルなどのデータを返したい場合は routes/api
ディレクトリ以下でハンドラーのみ定義するのがいい。
データフェッチ
ハンドラー内で取得してきて ctx.render
を呼び出すときに渡してやるといい。
フォーム送信
ReactとかVue使ってるとフォームもAPIで送信しがちだからハンドラーの方に GET
or POST
で返すの逆に珍しい。
クライアント側にJavaScript送らなくていいって言っているのこの辺りだよな。
サーバーサイドでレンダリングするとサーバーの負荷が高まる。ブラウザでレンダリングするとクライアントにJavaScriptがたくさん必要になる。サーバーサイドレンダリングからハイドレーションみたいなやり方でも、クライアント側でJavaScriptが多く必要な上に、サーバーとクライアントで同じような処理をするところが無駄無駄。
Freshのこのやり方はエッジサーバーで負荷分散の能力が高まってる前提があって、Denoがやり始めたCDN EdgeのDeno Deployとのシナジーがすごい。
フォーム送信についてブラウザがいいシステムを持ってる話は、ほんとにいいなと思う。扱っているシステムが素で持っている(追加されている)機能が結構強いものあったりする。TypeScriptに対するJavaScript本体とか。
引用: https://fresh.deno.dev/docs/getting-started/form-submissions
// routes/search.tsx import { Handlers, PageProps } from "$fresh/server.ts"; const NAMES = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Frank"]; interface Data { results: string[]; query: string; } export const handler: Handlers<Data> = { GET(req, ctx) { const url = new URL(req.url); const query = url.searchParams.get("q") || ""; const results = NAMES.filter((name) => name.includes(query)); return ctx.render({ results, query }); }, }; export default function Page({ data }: PageProps<Data>) { const { results, query } = data; return ( <div> <form> <input type="text" name="q" value={query} /> <button type="submit">Search</button> </form> <ul> {results.map((name) => <li key={name}>{name}</li>)} </ul> </div>); }
アイランドアーキテクチャの話
Deno Freshを使うと静的なページやユーザーアクションのないページに関してはJavaScriptのバンドルサイズ0が実現できる。けど現実にはそんなページはほとんどなくて、何かの機能を提供するボタンやリッチアニメーションなどの装飾のために少しだけJavaScriptが必要になる。
こういう静的なコンテンツの多いページの中に、ぽつぽつと島のようにJavaScriptが必要な要素があるみたいな形をアイランドアーキテクチャと呼んでるらしい。
Deno Freshでの islands
ディレクトリで定義するコンポーネントはクライアントでレンダリング(というかハイドレーション)される。これパーシャルハイドレーション。
マークアップのみのコンポーネントであれば components
ディレクトリに定義して、状態を持ったりするコンポーネントは islands
ディレクトリに定義することになりそう?
マークアップのみのコンポーネントであっても islands
内のコンポーネントから使われる場合もある。それを考慮するために $fresh/runtime.ts
がIS_BROWSER
っていうフラグを持ってる。
import { JSX } from "preact"; import { IS_BROWSER } from "$fresh/runtime.ts"; export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) { return ( <button {...props} disabled={!IS_BROWSER || props.disabled} class="px-2 py-1 border(gray-100 2) hover:bg-gray-200" /> ); }
他にない感じの処理だ。
デプロイ先
GitHubにプッシュしてDeno Deploy使うといいよって。Cloudflare Workersとかもよく聞く。