Freshで静的サイトを作る
#Fresh #Deno
はじめに
本来、Freshは主にSSRやIsland 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でカスタムハンドラを用意して行っています。
code: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アプリの静的ページ化
ビルド用のスクリプトを用意しています。
code: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へデプロイします。
code:yaml
# 省略...
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.event_name == 'push' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
その他、工夫した点
awesome-lintがDenoで動きそうだったため、CIで実行しています
code: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サポートが入りました)