generated at
Freshで静的サイトを作る
はじめに
本来、Freshは主にSSRIsland Architectureなどを活用して動的なサイトやサービスを開発することを想定したフレームワークです。
ただ、少し工夫をすると、静的なWebページを作れるのではないかと思い、試してみました。
最終的に作ったサイトについてはこちらになります。(awesome-fresh)

前提
このページを書いている時点でのFresh/Denoは以下のバージョンの想定です。
Fresh v1.1.5
Deno v1.3.3
各種ソースコードはこちらにあります。
また、説明のためにリモートモジュールのURLを直接記述していますが、Freshにおいてはimport_map.jsonで依存関係を管理することが推奨されます。

やりたいこと
README.mdの内容をHTMLとしてレンダリングしたい
ひとまずGitHub Pagesへデプロイしたい

Markdownのレンダリング
README.mdからHTMLへの変換は、Deno公式で開発されているdeno-gfmを使用しています。(lint.deno.landなどでも使われており、信頼性が高そうなため)
Markdownのレンダリングは routes/index.tsx でカスタムハンドラを用意して行っています。
routes/index.tsx
import { Head } from "$fresh/runtime.ts"; import type { Handlers, PageProps } from "$fresh/server.ts"; import { CSS, render as renderGFM } from "https://deno.land/x/gfm@0.2.3/mod.ts"; const gfmStyle = ` .markdown-body ul { list-style: disc } .markdown-body a { color: teal } ${CSS}`; export const handler: Handlers = { async GET(req, ctx) { // README.mdをHTMLへ変換します。 const url = new URL("../README.md", import.meta.url); const markdown = await Deno.readTextFile(url); const content = renderGFM(markdown, {}); // 下記のIndexコンポーネントをレンダリングします。 return ctx.render(content); }, }; export default function Index(props: PageProps<string>) { return ( <> <Head> <title>Awesome Fresh</title> <style id="gfm">{gfmStyle}</style> </Head> <main data-color-mode="auto" data-dark-theme="dark" class="p-4 mx-auto max-w-screen-md markdown-body" dangerouslySetInnerHTML={{ __html: props.data }} /> </> ); }

Freshアプリの静的ページ化
ビルド用のスクリプトを用意しています。
build.ts
// Fresh v1.1.5時点では、`DENO_DEPLOYMENT_ID`の有無によって本番or開発環境の判断が行われます。 // ここでは、本番環境と同様の方法でレンダリングを行ってほしいため、手動で`DENO_DEPLOYMENT_ID`を設定しています。 const { stdout: gitOutput } = await new Deno.Command("git", { args: ["rev-parse", "HEAD"], }).output(); const revision = new TextDecoder().decode(gitOutput).trim(); Deno.env.set("DENO_DEPLOYMENT_ID", revision); // Freshのサーバを起動します。 import("./main.ts"); await new Promise((ok) => { setTimeout(ok, 100); }); // HTMLを取得するために、FreshのサーバへHTTPリクエストを送信します。 const res = await fetch("http://localhost:8000"); if (!res.ok) { console.error("Failed to fetch `/`"); Deno.exit(1); } // 取得したHTMLを`build`ディレクトリへ出力します。 const html = await res.text(); const buildDir = new URL("./build", import.meta.url).pathname; await Deno.mkdir(buildDir, { recursive: true }); await Deno.writeTextFile(`${buildDir}/index.html`, html); Deno.exit(0);
このスクリプトの実行後、GitHub Actions build ディレクトリをGitHub Pagesへデプロイします。
yaml
# 省略... - name: Deploy uses: peaceiris/actions-gh-pages@v3 if: ${{ github.event_name == 'push' }} with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build

その他、工夫した点
awesome-lintDenoで動きそうだったため、CIで実行しています
lint.js
import { default as awesomeLint } from "npm:awesome-lint@0.18.2"; await awesomeLint.report({ filename: "README.md" });
Twind v1を試してみたかったため、 twindv1 プラグインを使っています (FreshにTwind v1サポートが入りました)