HonoXでPark UIが動くか試す
$ pnpm create hono@latest
create-hono version 0.11.0
? Target directory honox-with-park-ui
? Which template do you want to use? x-basic
✔ Cloning the template
? Do you want to install project dependencies? yes
? Which package manager do you want to use? pnpm
✔ Installing project dependencies
🎉 Copied project files
Get started with: cd honox-with-park-ui
jsx/jsxImportSourceを確認。
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"lib": [
"ESNext",
"DOM"
],
"types": [
"vite/client",
"@cloudflare/workers-types"
],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
},
"include": [
"**/*.ts",
"**/*.tsx"
]
}
とりあえず、純正Reactじゃなくhono/jsx
で動くか試す。
$ pnpm install -D @pandacss/dev
WARN 2 deprecated subdependencies found: rollup-plugin-inject@3.0.2, sourcemap-codec@1.4.8
Packages: +114 -1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Progress: resolved 387, reused 264, downloaded 31, added 114, done
devDependencies:
+ @pandacss/dev 0.44.0
Done in 16.7s
$ pnpm panda init --postcss
🐼 info [cli] Panda v0.44.0
🐼 info [init:postcss] creating postcss config file: `postcss.config.cjs`
🐼 info [init:config] creating panda config file: `panda.config.ts`
🚀 Thanks for choosing Panda to write your css.
You are set up to start using Panda!
✔️ `styled-system/css`: the css function to author styles
✔️ `styled-system/tokens`: the css variables and js function to query your tokens
✔️ `styled-system/patterns`: functions to implement and apply common layout patterns
╭────────────────────────── 🐼 Sweet! ✨ ──────────────────────────╮
│ │
│ │
│ Next steps: │
│ │
│ [1] Create a `index.css` file in your project that contains: │
│ │
│ @layer reset, base, tokens, recipes, utilities; │
│ │
│ │
│ [2] Import the `index.css` file at the root of your project. │
│ │
│ │
╰──────────────────────────────────────────────────────────────────╯
🐼 info [hrtime] ✨ Panda initialized (357.65ms)
package.jsonにscripts.prepare
部分を追加。
{
"name": "basic",
"type": "module",
"scripts": {
"prepare": "panda codegen",
"dev": "vite",
"build": "vite build --mode client && vite build",
"preview": "wrangler pages dev",
"deploy": "$npm_execpath run build && wrangler pages deploy"
},
"private": true,
"dependencies": {
"hono": "^4.5.3",
"honox": "^0.1.23"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240529.0",
"@hono/vite-cloudflare-pages": "^0.4.2",
"@hono/vite-dev-server": "^0.13.1",
"@pandacss/dev": "^0.44.0",
"vite": "^5.2.12",
"wrangler": "^3.57.2"
}
}
include
オプションをHonoXのディレクトリ規約に合わせて変更。
import { defineConfig } from "@pandacss/dev";
export default defineConfig({
// Whether to use css reset
preflight: true,
// Where to look for your css declarations
include: ["./app/**/*.{js,jsx,ts,tsx}"],
// Files to exclude
exclude: [],
// Useful for theme customization
theme: {
extend: {},
},
// The output directory for your css system
outdir: "styled-system",
});
@layer reset, base, tokens, recipes, utilities;
diff --git a/app/routes/_renderer.tsx b/app/routes/_renderer.tsx
index 03b150c..94e2e9a 100644
--- a/app/routes/_renderer.tsx
+++ b/app/routes/_renderer.tsx
@@ -1,6 +1,6 @@
-import { Style } from 'hono/css'
import { jsxRenderer } from 'hono/jsx-renderer'
import { Script } from 'honox/server'
+import indexCss from './index.css?url'
export default jsxRenderer(({ children, title }) => {
return (
@@ -11,7 +11,7 @@ export default jsxRenderer(({ children, title }) => {
<title>{title}</title>
<link rel="icon" href="/favicon.ico" />
<Script src="/app/client.ts" async />
- <Style />
+ <link rel="stylesheet" href={indexCss} />
</head>
<body>{children}</body>
</html>
diff --git a/app/routes/index.tsx b/app/routes/index.tsx
index fc34955..584c847 100644
--- a/app/routes/index.tsx
+++ b/app/routes/index.tsx
@@ -1,18 +1,16 @@
-import { css } from 'hono/css'
import { createRoute } from 'honox/factory'
import Counter from '../islands/counter'
-
-const className = css`
- font-family: sans-serif;
-`
+import { css } from '../../styled-system/css'
export default createRoute((c) => {
const name = c.req.query('name') ?? 'Hono'
return c.render(
- <div class={className}>
- <h1>Hello, {name}!</h1>
+ <div>
+ <h1 class={css({ fontSize: '2xl', fontWeight: 'bold' })}>
+ Hello, {name}!
+ </h1>
<Counter />
</div>,
- { title: name }
+ { title: name },
)
})
$ pnpm dev
ここまでで、Panda CSSは動いた。
Ark UIを入れる。Reactコンポーネントがhono/jsx
で動けばいいが……
$ pnpm add @ark-ui/react
WARN 2 deprecated subdependencies found: rollup-plugin-inject@3.0.2, sourcemap-codec@1.4.8
Packages: +81
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 468, reused 304, downloaded 72, added 81, done
dependencies:
+ @ark-ui/react 3.6.2
Done in 14.2s
Park UIを入れる。
pnpm add -D @park-ui/panda-preset
WARN 2 deprecated subdependencies found: rollup-plugin-inject@3.0.2, sourcemap-codec@1.4.8
Packages: +65
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 533, reused 377, downloaded 64, added 65, done
devDependencies:
+ @park-ui/panda-preset 0.42.0
Done in 8.9s
Park UIのプリセットを使うようにPanda CSSの設定ファイルに書く。
diff --git a/panda.config.ts b/panda.config.ts
index 0ba29b6..af3f37a 100644
--- a/panda.config.ts
+++ b/panda.config.ts
@@ -4,12 +4,16 @@ export default defineConfig({
// Whether to use css reset
preflight: true,
+ presets: ['@pandacss/preset-base', '@park-ui/panda-preset'],
+
// Where to look for your css declarations
include: ['./app/**/*.{js,jsx,ts,tsx}'],
// Files to exclude
exclude: [],
+ jsxFramework: 'react',
+
// Useful for theme customization
theme: {
extend: {},
パス・エイリアス(~/*
をプロジェクトルート、@/*
をapp
ディレクトリにマッピング)とパッケージ・エイリアス(react
をhono/jsx
、react-dom
をhono/jsx/dom
にマッピング)の設定をしていく。
パス・エイリアスに必要なパッケージをインストール。
$ pnpm add -D vite-tsconfig-paths
パッケージ・エイリアスを設定する前に、TypeScript用の型ファイルを入れておく。
$ pnpm add -D @types/react @types/react-dom
tsconfig.json
にパス・エイリアスとパッケージ・エイリアス(baseUrl
& paths
)を設定。
(フォーマットかかって改行が変わってるけど許せ)
diff --git a/tsconfig.json b/tsconfig.json
index 485d881..234682a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,22 +2,21 @@
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
- "moduleResolution": "Bundler",
+ "moduleResolution": "bundler",
+ "moduleDetection": "auto",
"strict": true,
"skipLibCheck": true,
- "lib": [
- "ESNext",
- "DOM"
- ],
- "types": [
- "vite/client",
- "@cloudflare/workers-types"
- ],
+ "lib": ["ESNext", "DOM"],
+ "types": ["vite/client", "@cloudflare/workers-types"],
"jsx": "react-jsx",
- "jsxImportSource": "hono/jsx"
+ "jsxImportSource": "hono/jsx",
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./*"],
+ "@/*": ["./app/*"],
+ "react": ["./node_modules/hono/jsx"],
+ "react-dom": ["./node_modules/hono/jsx/dom"]
+ }
},
- "include": [
- "**/*.ts",
- "**/*.tsx"
- ]
+ "include": ["**/*.ts", "**/*.tsx"]
}
Viteの設定ファイルでもパス・エイリアス(tsconfigPaths
プラグイン)を有効に。
あと、パッケージ・エイリアス(resolve.alias
)も設定しておく。
diff --git a/vite.config.ts b/vite.config.ts
index 88b8cbd..891b6b5 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -2,7 +2,18 @@ import pages from '@hono/vite-cloudflare-pages'
import adapter from '@hono/vite-dev-server/cloudflare'
import honox from 'honox/vite'
import { defineConfig } from 'vite'
+import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
- plugins: [honox({ devServer: { adapter } }), pages()]
+ plugins: [
+ tsconfigPaths({ root: './' }),
+ honox({ devServer: { adapter } }),
+ pages(),
+ ],
+ resolve: {
+ alias: {
+ 'react': 'hono/jsx', // prettier-ignore
+ 'react-dom': 'hono/jsx/dom',
+ },
+ },
})
package.json
でパッケージ・エイリアスの設定。
(hono/jsx
をreact
として使う)
diff --git a/package.json b/package.json
index 7790d54..e2bd2fb 100644
--- a/package.json
+++ b/package.json
@@ -10,15 +10,22 @@
},
"private": true,
"dependencies": {
+ "@ark-ui/react": "^3.6.2",
"hono": "^4.5.3",
- "honox": "^0.1.23"
+ "honox": "^0.1.23",
+ "react": "npm:hono/jsx",
+ "react-dom": "npm:hono/jsx/dom"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240529.0",
"@hono/vite-cloudflare-pages": "^0.4.2",
"@hono/vite-dev-server": "^0.13.1",
"@pandacss/dev": "^0.44.0",
+ "@park-ui/panda-preset": "^0.42.0",
+ "@types/react": "18.3.0",
+ "@types/react-dom": "18.3.0",
"vite": "^5.2.12",
+ "vite-tsconfig-paths": "^4.3.2",
"wrangler": "^3.57.2"
}
}
Park UIコンポーネントを追加。
(欲しいのはButton
コンポーネントだが、Button
コンポーネントはSpinner
コンポーネントに依存してるためまとめてインストールする)
$ pnpm dlx @park-ui/cli components add button spinner
┌ Park UI vundefined
│
◇ Components installed.
│
└ Happy Hacking 🤞
うわあああああああ!
ん〜、ちょっと無理っぽい。アイランドも動いてないし。
次から、Honoで、付属JSXを使うのではなくReactを使うように、変更してみる。
レンダラーをReactに変える前に、上でやっていたパッケージ・エイリアスの設定を外していく。
diff --git a/tsconfig.json b/tsconfig.json
index 234682a..59ad389 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,13 +9,11 @@
"lib": ["ESNext", "DOM"],
"types": ["vite/client", "@cloudflare/workers-types"],
"jsx": "react-jsx",
- "jsxImportSource": "hono/jsx",
+ "jsxImportSource": "react",
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
- "@/*": ["./app/*"],
- "react": ["./node_modules/hono/jsx"],
- "react-dom": ["./node_modules/hono/jsx/dom"]
+ "@/*": ["./app/*"]
}
},
"include": ["**/*.ts", "**/*.tsx"]
diff --git a/vite.config.ts b/vite.config.ts
index 891b6b5..2a6401b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -10,10 +10,4 @@ export default defineConfig({
honox({ devServer: { adapter } }),
pages(),
],
- resolve: {
- alias: {
- 'react': 'hono/jsx', // prettier-ignore
- 'react-dom': 'hono/jsx/dom',
- },
- },
})
diff --git a/package.json b/package.json
index e2bd2fb..256e34e 100644
--- a/package.json
+++ b/package.json
@@ -12,9 +12,7 @@
"dependencies": {
"@ark-ui/react": "^3.6.2",
"hono": "^4.5.3",
- "honox": "^0.1.23",
- "react": "npm:hono/jsx",
- "react-dom": "npm:hono/jsx/dom"
+ "honox": "^0.1.23"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240529.0",
こんなもんかな。
レンダラーをHono JSXからReactへ差し替える。
(参考:BYOR - Bring Your Own Renderer、React Renderer Middleware)
まず、あらためてReact/ReactDOMやらをインストールする。
$ pnpm add react react-dom hono @hono/react-renderer
WARN 2 deprecated subdependencies found: rollup-plugin-inject@3.0.2, sourcemap-codec@1.4.8
Packages: +1
+
Progress: resolved 540, reused 447, downloaded 1, added 1, done
dependencies:
+ @hono/react-renderer 0.2.1
+ react 18.3.1
+ react-dom 18.3.1
Done in 3s
レンダラーへのプロパティを追加する。
diff --git a/app/global.d.ts b/app/global.d.ts
index 288f02b..6be2e3b 100644
--- a/app/global.d.ts
+++ b/app/global.d.ts
@@ -1,4 +1,5 @@
import {} from 'hono'
+import '@hono/react-renderer'
type Head = {
title?: string
@@ -10,6 +11,15 @@ declare module 'hono' {
Bindings: {}
}
interface ContextRenderer {
- (content: string | Promise<string>, head?: Head): Response | Promise<Response>
+ (
+ content: string | Promise<string>,
+ head?: Head,
+ ): Response | Promise<Response>
+ }
+}
+
+declare module '@hono/react-renderer' {
+ interface Props {
+ title?: string
}
}
レンダラー差し替えと、prodとそれ以外でクライアントスクリプトとCSSが切り替わるよう調整。
(<!DOCTYPE html>
が付くようにオプションも追加)
diff --git a/app/routes/_renderer.tsx b/app/routes/_renderer.tsx
index 94e2e9a..5ed7328 100644
--- a/app/routes/_renderer.tsx
+++ b/app/routes/_renderer.tsx
@@ -1,19 +1,32 @@
-import { jsxRenderer } from 'hono/jsx-renderer'
-import { Script } from 'honox/server'
-import indexCss from './index.css?url'
+import { reactRenderer } from '@hono/react-renderer'
-export default jsxRenderer(({ children, title }) => {
- return (
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>{title}</title>
- <link rel="icon" href="/favicon.ico" />
- <Script src="/app/client.ts" async />
- <link rel="stylesheet" href={indexCss} />
- </head>
- <body>{children}</body>
- </html>
- )
-})
+export default reactRenderer(
+ ({ children, title }) => {
+ return (
+ <html lang="en">
+ <head>
+ <meta charSet="utf-8" />
+ <meta
+ name="viewport"
+ content="width=device-width, initial-scale=1.0"
+ />
+ <title>{title}</title>
+ <link rel="icon" href="/favicon.ico" />
+ {import.meta.env.PROD ? (
+ <>
+ <script type="module" src="/static/client.js" async />
+ <link rel="stylesheet" href="/static/app.css" />
+ </>
+ ) : (
+ <>
+ <script type="module" src="/app/client.ts" async />
+ <link rel="stylesheet" href="/app/app.css" />
+ </>
+ )}
+ </head>
+ <body>{children}</body>
+ </html>
+ )
+ },
+ { docType: true },
+)
ViteのSSRでReact/ReactDOMを使えるようにする。
Viteのssr.external
オプションにReactのものとPark UIのものを追加。
honox()
のオプションのdevServer.exclude
は、そのまま追加するとマージじゃなく置き換えになって、普通のページ部分も動かなくなるので、ソースからごそっと持ってきたやつにstyled-system
ディレクトリを足してある。
diff --git a/vite.config.ts b/vite.config.ts
index 891b6b5..8b06f41 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,19 +1,46 @@
import pages from '@hono/vite-cloudflare-pages'
import adapter from '@hono/vite-dev-server/cloudflare'
-import honox from 'honox/vite'
+import honox, { devServerDefaultOptions } from 'honox/vite'
import { defineConfig } from 'vite'
import tsconfigPaths from 'vite-tsconfig-paths'
-export default defineConfig({
- plugins: [
- tsconfigPaths({ root: './' }),
- honox({ devServer: { adapter } }),
- pages(),
- ],
- resolve: {
- alias: {
- 'react': 'hono/jsx', // prettier-ignore
- 'react-dom': 'hono/jsx/dom',
- },
- },
+export default defineConfig(({ mode }) => {
+ if (mode === 'client') {
+ return {
+ plugins: [tsconfigPaths({ root: './' })],
+ build: {
+ rollupOptions: {
+ input: ['./app/client.ts', './app/app.css'],
+ output: {
+ entryFileNames: 'static/client.js',
+ chunkFileNames: 'static/[name]-[hash].js',
+ assetFileNames: 'static/[name].[ext]',
+ },
+ },
+ emptyOutDir: false,
+ },
+ }
+ } else {
+ return {
+ ssr: {
+ external: ['react', 'react-dom', 'styled-system'],
+ },
+ plugins: [
+ tsconfigPaths({ root: './' }),
+ honox({
+ devServer: {
+ adapter,
+ exclude: [
+ ...devServerDefaultOptions.exclude,
+ /^\/app\/.+/,
+ /^\/favicon.ico/,
+ /^\/static\/.+/,
+ /^\/styled-system\/.+/,
+ ],
+ },
+ }),
+ pages(),
+ ],
+ }
+ }
})
app/routes/index.css
をapp/app.css
に移動&リネーム。
(index.cssだとindex.tsxページに対応しそうだな〜と思ったので)
(root.css
にリネームの方が良かったかも)
$ mv app/routes/index.css app/app.css
HonoXが使うhydrate()
関数やcreateElement()
関数を、hono/jsx
のものからreact
のものに差し替える。
diff --git a/app/client.ts b/app/client.ts
index 16ecf96..8702afd 100644
--- a/app/client.ts
+++ b/app/client.ts
@@ -1,3 +1,12 @@
import { createClient } from 'honox/client'
-createClient()
+createClient({
+ hydrate: async (elem, root) => {
+ const { hydrateRoot } = await import('react-dom/client')
+ hydrateRoot(root, elem as any)
+ },
+ createElement: async (type: any, props: any) => {
+ const { createElement } = await import('react')
+ return createElement(type, props) as any
+ },
+})
ページでHTML属性をReact語に変換。
diff --git a/app/routes/index.tsx b/app/routes/index.tsx
index 584c847..ac1c831 100644
--- a/app/routes/index.tsx
+++ b/app/routes/index.tsx
@@ -6,7 +6,7 @@ export default createRoute((c) => {
const name = c.req.query('name') ?? 'Hono'
return c.render(
<div>
- <h1 class={css({ fontSize: '2xl', fontWeight: 'bold' })}>
+ <h1 className={css({ fontSize: '2xl', fontWeight: 'bold' })}>
Hello, {name}!
</h1>
<Counter />
Counterコンポーネントでhono/jsx
をreact
に変更。
diff --git a/app/islands/counter.tsx b/app/islands/counter.tsx
index 7e8c1e0..9a98835 100644
--- a/app/islands/counter.tsx
+++ b/app/islands/counter.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'hono/jsx'
+import { useState } from 'react'
import { Button } from '@/components/ui/button'
export default function Counter() {
これで動いた!!
react-rendererミドルウェアのソースコード見てたら、renderToReadableStream()
に対応してたので、<Suspense>
使いたいし、このプロジェクトも対応したくなった。やっていく。
renderToReadableStream()
Web APIは、Node.JSは対応してないので、DenoかBunを使わなければならない。
Denoにするとパッケージマネジメント方法がガラッと変わってしまうので、Bunでやる。
Bunインストールは、多分推奨はcurl&bashだけど、Homebrewでやった。
$ brew install oven-sh/bun/bun
色々消す。
$ rm pnpm-lock.yaml
$ rm -rf node_modules
$ rm -rf dist
package.json
書き換え。
diff --git a/package.json b/package.json
index cf1a391..adbd32f 100644
--- a/package.json
+++ b/package.json
@@ -5,21 +5,21 @@
"clean": "rimraf dist",
"prepare": "panda codegen",
"dev": "vite",
- "build": "pnpm run clean && vite build --mode client && vite build",
+ "build": "bun run clean && vite build --mode client && vite build",
"preview": "wrangler pages dev",
- "deploy": "$npm_execpath run build && wrangler pages deploy"
+ "deploy": "bun run build && wrangler pages deploy"
},
"private": true,
"dependencies": {
お試し。
$ bun install
$ bun run dev
$ bun run build
$ bun run preview
全部正常に動いた。
PnpmからBunへの移行おわり。