Open1

Deno FreshでPanda CSSを使う

RasRas

Fresh

https://fresh.deno.dev/
https://fresh.deno.dev/

Deno公式のWeb Framework
SSR & Island Architectureを採用している

Panda CSS

https://panda-css.com/
https://panda-css.com/

Zero Runtime CSS in JS
ビルド時に明示的にコマンドを実行してプロジェクト内のファイルを解析することで必要最低限の静的CSSを出力する
様々なフレームワークへのインストール方法が書いてあるがFresh (Deno)に関する記述はない (Bunは少しだけある)

FreshでPanda CSSを使う

ここに書いていきます

https://github.com/ras0q/deno-fresh-pandacss-sample
https://github.com/ras0q/deno-fresh-pandacss-sample

  • Deno 1.45.4
  • Fresh 1.6.8 (もうすぐv2が出るらしいので方法が変わるかも)
  • Panda CSS 0.44.0

Freshのセットアップ (Denoはインストールしている前提)

$ deno run -A -r https://fresh.deno.dev .

 🍋 Fresh: The next-gen web framework.

Let's set up your new Fresh project.

Do you want to use a styling library? [y/N]
Do you use VS Code? [y/N]
The manifest has been generated for 5 routes and 1 islands.

Project initialized!

Run deno task start to start the project. CTRL-C to stop.

Stuck? Join our Discord https://discord.gg/deno

Happy hacking! 🦕
$ /bin/ls -a
.   .git        README.md   deno.json  fresh.config.ts  islands  routes
..  .gitignore  components  dev.ts     fresh.gen.ts     main.ts  static

Panda CSSを依存に追加

$ deno add npm:@pandacss/dev
Add @pandacss/dev - npm:@pandacss/dev@^0.44.0

設定ファイルpanda.config.mjsのセットアップ

--node-modules-dirを付ける or deno.jsonnodeModulesDir: trueを設定する必要がある
Panda CSSがローカルのnode_modules/を読みに行くため
参考: https://docs.deno.com/runtime/manual/node/npm_specifiers/#--node-modules-dir-flag

$ deno run -A --node-modules-dir npm:@pandacss/dev init
warning: Packages contained npm lifecycle scripts (preinstall/install/postinstall) that were not executed.
    This may cause the packages to not work correctly. To run them, use the `--allow-scripts` flag with `deno cache`
    (e.g. `deno cache --allow-scripts=pkg1,pkg2 <entrypoint>`):
      npm:esbuild@0.20.2
🐼 info [cli] Panda v0.44.0

🐼 info [init:config] creating panda config file: `panda.config.mjs`

🚀 Thanks for choosing Panda to write your css.
...

panda.config.mjsが作成された

import { defineConfig } from "@pandacss/dev";

export default defineConfig({
  // Whether to use css reset
  preflight: true,

  // Where to look for your css declarations
  include: ["./src/**/*.{js,jsx,ts,tsx}", "./pages/**/*.{js,jsx,ts,tsx}"],

  // Files to exclude
  exclude: [],

  // Useful for theme customization
  theme: {
    extend: {},
  },

  // The output directory for your css system
  outdir: "styled-system",
});

解析対象を変更する

-  include: ["./src/**/*.{js,jsx,ts,tsx}", "./pages/**/*.{js,jsx,ts,tsx}"],
+  include: ["./{components,islands,routes}/**/*.{js,jsx,ts,tsx}"],

Freshは静的ファイルを/static以下で配信するので合わせる

-  outdir: "styled-system",
+  outdir: "./static/styled-system",

Denoはimportの拡張子が必須なので拡張子を強制する

+  forceConsistentTypeExtension: true,

Panda CSSをFreshに組み込む

routes/_app.tsxに生成ファイルの参照を追加
最終的には/styles.css (static/styles.css) は消して完全にPanda CSSに移行したい

         <link rel="stylesheet" href="/styles.css" />
+        <link rel="stylesheet" href="/styled-system/styles.css" />

一旦走らせて確認
お好みでこれをdeno taskに追加する (がdeno.jsonに入れた@pandacss/devとバージョンが同期できない?)

$ deno run -A --node-modules-dir npm:@pandacss/dev
🐼 info [css] /path/to/deno-fresh-pandacss-sample/static/styled-system/styles.css
🐼 info [hrtime] Successfully extracted css from 7 file(s)(179.34ms)

まだ何も入れていないので普通にFreshプロジェクトの初期画面が表示される

$ deno task start
Task start deno run -A --watch=static/,routes/ dev.ts
Watcher Process started.
The manifest has been generated for 5 routes and 1 islands.

 🍋 Fresh ready
    Local: http://localhost:8000/

TODO: このままだとスタイルを変更してもPanda CSSのホットリロードが走らないのでmain.tsdev.tsに処理を組み込む

Panda CSSを使う

試しにcomponents/Button.tsxを変更する

変更前はこれ
ここに書いてあるutility classは/static/styles.cssに直接記述されている

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-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
    />
  );
}

import時にmjsの方をimportした上でd.mtsを@deno-typesに指定しないと正しく型が付く&動く状態にならないので注意
毎回2行書くの辛くて別ファイルからexportしてみたが解析が上手くいかなかった

 import { IS_BROWSER } from "$fresh/runtime.ts";
+// @deno-types="../static/styled-system/css/index.d.mts"
+import { css } from "../static/styled-system/css/index.mjs";
-      class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
+      class={css({
+        px: 2,
+        py: 1,
+        borderColor: "gray.500",
+        borderWidth: 2,
+        borderRadius: "sm",
+        bg: "white",
+        _hover: {
+          bg: "gray.200",
+        },
+        transitionProperty: "background-color, border-color, color, fill, stroke",
+        transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
+        transitionDuration: "150ms",
+      })}

transitionのところとかはstatic/styles.cssから直接持ってきた
もちろん設定ファイルにスタイルをまとめてこれらをtransition-colorsとして定義することもできるはず

余談
Panda CSSを使うのは初めてだったがここでPanda CSS自体がちょっと微妙か、、?と思い始める
生成されるクラスはこんな感じ
使わない色のCSS Variablesなども用意されるため(これはpresetを編集すればいいらしい)、元のstatic/styles.cssよりもサイズがかなり大きくなってしまった、、、

<button class="px_2 py_1 bd-c_gray.500 bd-w_2 bdr_sm bg_white hover:bg_gray.200 trs-prop_background-color,_border-color,_color,_fill,_stroke trs-tmf_cubic-bezier(0.4,_0,_0.2,_1) trs-dur_150ms">-1</button>
@layer utilities {

  .px_2 {
    padding-inline: var(--spacing-2);
  }

  .py_1 {
    padding-block: var(--spacing-1);
  }

  .bdr_sm {
    border-radius: var(--radii-sm);
  }

  .bg_white {
    background: var(--colors-white);
  }

  .bd-c_gray\.500 {
    border-color: var(--colors-gray-500);
  }

  .bd-w_2 {
    border-width: 2px;
  }

  .trs-prop_background-color\,_border-color\,_color\,_fill\,_stroke {
    --transition-prop: background-color, border-color, color, fill, stroke;
    transition-property: background-color, border-color, color, fill, stroke;
  }

  .trs-tmf_cubic-bezier\(0\.4\,_0\,_0\.2\,_1\) {
    --transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  }

  .trs-dur_150ms {
    --transition-duration: 150ms;
    transition-duration: 150ms;
  }

  .trs-dur_1500ms {
    --transition-duration: 1500ms;
    transition-duration: 1500ms;
  }

  .hover\:bg_gray\.200:is(:hover, [data-hover]) {
    background: var(--colors-gray-200);
  }
}