fresh v1.6がリリースされました。

この記事では主な変更点などについて解説します。

Tailwind CSSの公式サポート

fresh公式でTailwind CSSのサポートが導入されました。

Tailwind CSSプラグイン (plugins/tailwind.ts)が追加されています。

// fresh.config.ts
import { defineConfig } from "$fresh/server.ts";

import tailwind from "$fresh/plugins/tailwind.ts";

export default defineConfig({
  plugins: [tailwind()],
});

本番環境での利用について

このTailwind CSSプラグインをDeno Deployで利用する場合は、事前ビルドとの併用が必須になるためご注意ください。

このプラグインを導入した状態でdeno task buildを実行すると、_fresh/staticにCSSファイルが生成されるため、Deno Deployではこちらが利用される想定です。(_fresh/staticについては後述します。また、本番ビルド時に生成されるCSSファイルはcssnanoで最適化されます)

新規プロジェクトへの導入について

freshのinit.tsでプロジェクトを初期化する際に、Tailwind CSSの有効化がサポートされています。

既存プロジェクトへの導入方法

deno.jsonで公式で推奨されるバージョンのTailwind CSSを読み込むよう設定します。 ($fresh/src/dev/imports.ts)

{
  "imports": {
    // ... 省略 ...
    "tailwindcss": "npm:tailwindcss@3.3.5",
    "tailwindcss/": "npm:/tailwindcss@3.3.5/",
    "tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js"
  },
  // ... 省略 ...
}

次にTailwind CSSの設定ファイルをルートディレクトリに配置します。ファイル名はtailwind.config.ts/tailwind.config.js/tailwind.config.mjsのいずれかである必要があります。

// tailwind.config.ts
import type { Config } from "tailwindcss";

export default {
  content: [
    "{routes,islands}/**/*.{ts,tsx}",
  ],
} as Config;

次にstatic/styles.cssに配置します。

/* 必要に応じてカスタマイズします... */
@tailwind base;
@tailwind components;
@tailwind utilities;

このCSSファイルを読み込ませるため、routes/_app.tsxなどに<link>を記載します。

import { PageProps } from "$fresh/server.ts";

export default function App({ Component }: PageProps) {
  return (
    <html>
      <head>
        <link rel="stylesheet" href="/styles.css" />
        {/* ...省略... */}
      </head>
      {/* ...省略... */
    </html>
  );
}

次にfresh.config.tsからTailwind CSSプラグインを読み込むよう設定をします。

// fresh.config.ts
import { defineConfig } from "$fresh/server.ts";

import tailwind from "$fresh/plugins/tailwind.ts";

export default defineConfig({
  plugins: [tailwind()],
});

これでTailwind CSSが動作するようになるはずです。開発時はこれ以外には特に作業は不要で、本番環境(Deno Deploy)で使用する際は事前ビルド(deno task build)の導入が必要になります。

型定義の単純化

FreshContextの導入

FreshContextという新しい型が導入されています。

これに伴い、HandlerMiddlewareHandlerなどのctx引数に渡される型がFreshContextに変更されています。

また、以下の各型がFreshContextをベースに再定義されました。(以下のうちRouteContext以外の型は非推奨化されていて、今後はFreshContextの使用が推奨されます)

FreshContextについて

FreshContextconfigプロパティを持っており、freshの設定を参照することができます。

また、FreshContextpatternプロパティは非推奨化されており、今後はrouteプロパティの使用が推奨されます。

各コンポーネントのpropsの単純化

PagePropsが先程のFreshContextをベースに再定義されています。(render/next/renderNotFoundプロパティは除外されています)

また、AppProps/LayoutProps/UnknownPageProps/ErrorPagePropsの4つの型がPagePropsに統合されました。(これら4つの型は非推奨化されています)

Partials

fresh v1.5で導入されたPartialsに関する改善が実施されています。

<form>のサポート

submitイベントが発生したとき、対象の<form>またはsubmitterf-client-navが有効化されていれば、Partialによるクライアントサイドナビゲーションが適用されます。

この際、以下の優先度に従って、submitを処理するためのエンドポイントが決定されます。

  1. submitterf-partial属性
  2. submitterformaction属性
  3. <form>f-partial属性
  4. <form>action属性

エンドポイントへHTTPリクエストを送信する際は、<form>またはsubmitterで指定されたHTTPメソッドに応じて、GETまたはPOSTのいずれかが送信されます。(GETの場合はクエリパラメータ、POSTの場合はリクエストボディに<form>の内容が設定されます)

<Partial>のネストがサポート

<Partial>をネストできるようになりました。親側の<Partial>が更新されると、子側の<Partial>も更新されます。

ただし、子側の<Partial>が更新された際は、親側は<Partial>は更新されないようです。

function Page() {
  return (
    <Partial name="outer">
      <Partial name="inner">
        {/* ... */}
      </Partial>
    </Partial>
  );
}

isPartial

PagePropsなどのFreshContextをベースとした型にisPartialプロパティが追加されています。

<Partial>によるクライアントナビゲーション時のリクエストではtrueが設定されるため、レスポンスの内容を最適化する際などに利用できそうです。

export default function DocPage(props: PageProps<Data>) {
  if (props.isPartial) {
    return <Content content={props.data.content} />;
  } else {
    return (
      <>
        <Head>
          <link rel="stylesheet" href="/styles.css" />
        </Head>
        <Content content={props.data.content} />
      </>
    );
  }
}

function Content(props: { content: string }) {
  return (
    <Partial name="docs-content">
      <article
        data-color-mode="auto"
        data-dark-theme="dark"
        class="p-4 mx-auto w-full markdown-body"
        dangerouslySetInnerHTML={{ __html: props.content }}
      />
    </Partial>
  );
}

その他の改善

  • <Partial>によるクライアントサイドナビゲーションの発生時に、リダイレクトが処理されるように改善されました (fetch()の実行時にredirect: "follow"が設定されます)
  • エラーページで<Partial>が動作するように改善されました。例えば、存在しないページにアクセスした際に、現在のページの<Partial name="xxx">に該当する<Partial>_404.tsxが返却していれば、その内容で差し替えられます。

プラグインシステム

プラグインからのIslandコンポーネントの提供がサポート

プラグインからIslandコンポーネントの提供がサポートされました。

islandsオプションにIslandコンポーネントのリンクを記載しておくことで、FreshがそれらのコンポーネントをIslandとして扱ってくれます。

import type { Plugin } from "$fresh/server.ts";

const counterPlugin: Plugin = {
  name: "counter",
  islands: {
    baseLocation: "https://deno.land/x/fresh@1.6.0/demo/islands/",
    paths: ["./islands/Counter.tsx"]
  }
};

buildStart/configResolvedフックにconfigが渡されるように

PluginconfigResolvedフックが追加されました。startに渡されたconfigの内容を受け取ることができ、configが解決されたタイミングで実行されます。また、事前ビルド時はbuildStartよりも前のタイミングで呼ばれます。

また、buildStartフックでも同様にconfigを受け取れるように改善されています。

const somePlugin: Plugin = {
  name: "some-plugin",
  buildStart(config) {
    console.info("buildStart", config);
  },
  configResolved(config) {
    console.info("configResolved", config);
  },
};

PluginRenderResultlinks/htmlTextが追加

Plugin#render(Async)が返却するPluginRenderResultに以下のプロパティが追加されました。

プロパティ説明
linksこのプロパティが設定されていたら、その内容を元に<head><link>が挿入されます。
htmlTextこのプロパティが設定されていた場合、最終的に返却される<body>がその内容で置き換わります。

事前ビルド (deno task build)

_fresh/staticのサポート

freshが_fresh/staticに配置された静的なアセットを認識してくれるようになりました。

ここに置かれたファイルはstatic/ディレクトリに置かれたファイルよりも優先して配信してくれます。 基本的にはプラグイン(Plugin#buildStart)が静的ファイルを作成したい場合に利用されることが想定されているようで、前述したTailwind CSSプラグインでも、事前ビルド(deno task build)を実行すると、Tailwind CSSの本番ビルドによって生成されたCSSファイルが_fresh/staticに生成されます。

Server

  • handlerなどからDeno.errors.NotFoundthrowされた際も404ページ(_404.tsx)が表示されるように改善されました。
  • ルーティングのパフォーマンスが最適化されています。URLPatternではなく正規表現を使用することなどにより改善されているようです。
  • エラー発生時にfreshが表示するコードフレームを閉じられるように改善されました。(#2074)
    • 開発時にユーザーが用意した_500.tsxがレンダリングされなくなってしまう課題があったようです。

Islands

  • islands/配下のファイルで関数以外の要素がexportされていた際にエラーが発生する問題が解消されました。
  • islands/配下のファイルをバンドルする際に、各Islandコンポーネントごとにバンドルを作成するのではなく、各ファイルごとにバンドルを作成するように修正されました。

fresh.config.ts

router.basePathオプションが追加

アプリケーションがserveされる基準となるパスを変更可能で、例えば、以下の場合は、/subdir配下からアプリケーションが配信されます。

import { defineConfig } from "$fresh/server.ts";

export default defineConfig({
  router: { basePath: "/subdir" },
});

Manifest (fresh.gen.ts)

  • fresh.gen.tsでのコンフリクトの発生を減らすように、routesislandsに設定されるキーの名前が改善されました。

参考