├── .gitignore ├── LICENSE ├── README.md ├── app ├── client.ts ├── components │ └── ui │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ └── navigation-menu.tsx ├── global.d.ts ├── islands │ ├── Calendar.tsx │ ├── NavigationMenuDemo.tsx │ └── counter.tsx ├── lib │ └── utils.ts ├── routes │ ├── _renderer.tsx │ ├── index.tsx │ └── nested │ │ ├── _renderer.tsx │ │ └── index.tsx ├── server.ts └── tailwind.css ├── components.json ├── package.json ├── postcss.config.js ├── renovate.json ├── tailwind.config.js ├── tsconfig.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .wrangler 4 | .dev.vars 5 | .hono 6 | 7 | # Change them to your taste: 8 | wrangler.toml 9 | package-lock.json 10 | yarn.lock 11 | pnpm-lock.yaml 12 | bun.lockb 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Yuto Yoshino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HonoX + shadcn/ui 2 | 3 | This repository is for those who want to use shadcn/ui with HonoX. Basically, it's very easy to set up, just build the environment and add a few configurations. 4 | Please check my blog for more details. https://yossy.dev/posts/hono-shadcn-ui 5 | -------------------------------------------------------------------------------- /app/client.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "honox/client"; 2 | 3 | createClient({ 4 | hydrate: async (elem, root) => { 5 | const { hydrateRoot } = await import("react-dom/client"); 6 | hydrateRoot(root, elem); 7 | }, 8 | createElement: async (type: any, props: any) => { 9 | const { createElement } = await import("react"); 10 | return createElement(type, props); 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /app/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | }, 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button"; 45 | return ( 46 | 51 | ); 52 | }, 53 | ); 54 | Button.displayName = "Button"; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /app/components/ui/calendar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ChevronLeft, ChevronRight } from "lucide-react"; 3 | import { DayPicker } from "react-day-picker"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | import { buttonVariants } from "@/components/ui/button"; 7 | 8 | export type CalendarProps = React.ComponentProps; 9 | 10 | function Calendar({ 11 | className, 12 | classNames, 13 | showOutsideDays = true, 14 | ...props 15 | }: CalendarProps) { 16 | return ( 17 | , 56 | IconRight: ({ ...props }) => , 57 | }} 58 | {...props} 59 | /> 60 | ); 61 | } 62 | Calendar.displayName = "Calendar"; 63 | 64 | export { Calendar }; 65 | -------------------------------------------------------------------------------- /app/components/ui/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" 3 | import { cva } from "class-variance-authority" 4 | import { ChevronDown } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const NavigationMenu = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 20 | {children} 21 | 22 | 23 | )) 24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName 25 | 26 | const NavigationMenuList = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, ...props }, ref) => ( 30 | 38 | )) 39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName 40 | 41 | const NavigationMenuItem = NavigationMenuPrimitive.Item 42 | 43 | const navigationMenuTriggerStyle = cva( 44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" 45 | ) 46 | 47 | const NavigationMenuTrigger = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, children, ...props }, ref) => ( 51 | 56 | {children}{" "} 57 | 62 | )) 63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName 64 | 65 | const NavigationMenuContent = React.forwardRef< 66 | React.ElementRef, 67 | React.ComponentPropsWithoutRef 68 | >(({ className, ...props }, ref) => ( 69 | 77 | )) 78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName 79 | 80 | const NavigationMenuLink = NavigationMenuPrimitive.Link 81 | 82 | const NavigationMenuViewport = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 |
87 | 95 |
96 | )) 97 | NavigationMenuViewport.displayName = 98 | NavigationMenuPrimitive.Viewport.displayName 99 | 100 | const NavigationMenuIndicator = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 112 |
113 | 114 | )) 115 | NavigationMenuIndicator.displayName = 116 | NavigationMenuPrimitive.Indicator.displayName 117 | 118 | export { 119 | navigationMenuTriggerStyle, 120 | NavigationMenu, 121 | NavigationMenuList, 122 | NavigationMenuItem, 123 | NavigationMenuContent, 124 | NavigationMenuTrigger, 125 | NavigationMenuLink, 126 | NavigationMenuIndicator, 127 | NavigationMenuViewport, 128 | } 129 | -------------------------------------------------------------------------------- /app/global.d.ts: -------------------------------------------------------------------------------- 1 | import {} from "hono"; 2 | 3 | import "@hono/react-renderer"; 4 | 5 | declare module "@hono/react-renderer" { 6 | interface Props { 7 | title?: string; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/islands/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Calendar } from "@/components/ui/calendar"; 3 | 4 | export default function CalendarDemo() { 5 | const [date, setDate] = React.useState(new Date()); 6 | 7 | return ( 8 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /app/islands/NavigationMenuDemo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { 5 | NavigationMenu, 6 | NavigationMenuContent, 7 | NavigationMenuItem, 8 | NavigationMenuLink, 9 | NavigationMenuList, 10 | NavigationMenuTrigger, 11 | navigationMenuTriggerStyle, 12 | } from "@/components/ui/navigation-menu"; 13 | 14 | const components: { title: string; href: string; description: string }[] = [ 15 | { 16 | title: "Alert Dialog", 17 | href: "/docs/primitives/alert-dialog", 18 | description: 19 | "A modal dialog that interrupts the user with important content and expects a response.", 20 | }, 21 | { 22 | title: "Hover Card", 23 | href: "/docs/primitives/hover-card", 24 | description: 25 | "For sighted users to preview content available behind a link.", 26 | }, 27 | { 28 | title: "Progress", 29 | href: "/docs/primitives/progress", 30 | description: 31 | "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.", 32 | }, 33 | { 34 | title: "Scroll-area", 35 | href: "/docs/primitives/scroll-area", 36 | description: "Visually or semantically separates content.", 37 | }, 38 | { 39 | title: "Tabs", 40 | href: "/docs/primitives/tabs", 41 | description: 42 | "A set of layered sections of content—known as tab panels—that are displayed one at a time.", 43 | }, 44 | { 45 | title: "Tooltip", 46 | href: "/docs/primitives/tooltip", 47 | description: 48 | "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.", 49 | }, 50 | ]; 51 | 52 | export function NavigationMenuDemo() { 53 | return ( 54 | 55 | 56 | 57 | Getting started 58 | 59 | 87 | 88 | 89 | 90 | Components 91 | 92 |
    93 | {components.map((component) => ( 94 | 99 | {component.description} 100 | 101 | ))} 102 |
103 |
104 |
105 | 106 | 107 | 108 | Documentation 109 | 110 | 111 | 112 |
113 |
114 | ); 115 | } 116 | 117 | const ListItem = React.forwardRef< 118 | React.ElementRef<"a">, 119 | React.ComponentPropsWithoutRef<"a"> 120 | >(({ className, title, children, ...props }, ref) => { 121 | return ( 122 |
  • 123 | 124 | 132 |
    {title}
    133 |

    134 | {children} 135 |

    136 |
    137 |
    138 |
  • 139 | ); 140 | }); 141 | ListItem.displayName = "ListItem"; 142 | -------------------------------------------------------------------------------- /app/islands/counter.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function Counter() { 4 | const [count, setCount] = useState(0); 5 | 6 | return ( 7 |
    8 |

    {count}

    9 | 10 |
    11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /app/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /app/routes/_renderer.tsx: -------------------------------------------------------------------------------- 1 | import { NavigationMenuDemo } from "@/islands/NavigationMenuDemo"; 2 | import { reactRenderer } from "@hono/react-renderer"; 3 | import { useRequestContext } from "@hono/react-renderer"; 4 | import { FC, PropsWithChildren } from "react"; 5 | 6 | const HasIslands: FC = ({ children }) => { 7 | const IMPORTING_ISLANDS_ID = "__importing_islands" as const; 8 | const c = useRequestContext(); 9 | return <>{c.get(IMPORTING_ISLANDS_ID) ? children : <>}; 10 | }; 11 | 12 | export default reactRenderer(({ children, title }) => { 13 | return ( 14 | 15 | 16 | 17 | 18 | {import.meta.env.PROD ? ( 19 | <> 20 | 21 | 22 | 23 | 24 | 25 | ) : ( 26 | <> 27 | 28 | 29 | 30 | )} 31 | {title ? {title} : ""} 32 | 33 | 34 | 35 | {children} 36 | 37 | 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoute } from "honox/factory"; 2 | import { Button } from "@/components/ui/button"; 3 | import Counter from "@/islands/counter"; 4 | import Calendar from "@/islands/Calendar"; 5 | 6 | export default createRoute((c) => { 7 | const name = c.req.query("name") ?? "Hono"; 8 | return c.render( 9 |
    10 |

    Hello, {name}!

    11 | 12 | 13 | 14 |
    , 15 | { title: name }, 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /app/routes/nested/_renderer.tsx: -------------------------------------------------------------------------------- 1 | import { reactRenderer } from "@hono/react-renderer"; 2 | 3 | export default reactRenderer(({ children, Layout }) => { 4 | return ( 5 | 6 | {children} 7 | 8 | ); 9 | }); 10 | -------------------------------------------------------------------------------- /app/routes/nested/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoute } from "honox/factory"; 2 | import { Button } from "@/components/ui/button"; 3 | 4 | export default createRoute((c) => { 5 | const name = c.req.query("name") ?? "Hono"; 6 | return c.render( 7 |
    8 |

    here is nested page!!

    9 | 10 |
    , 11 | { title: name }, 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /app/server.ts: -------------------------------------------------------------------------------- 1 | import { showRoutes } from 'hono/dev' 2 | import { createApp } from 'honox/server' 3 | 4 | const app = createApp() 5 | 6 | showRoutes(app) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /app/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/tailwind.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build --mode client && vite build", 7 | "preview": "wrangler pages dev ./dist", 8 | "deploy": "$npm_execpath run build && wrangler pages deploy ./dist" 9 | }, 10 | "private": true, 11 | "dependencies": { 12 | "@hono/react-renderer": "^0.2.0", 13 | "@radix-ui/react-navigation-menu": "^1.1.4", 14 | "@radix-ui/react-slot": "^1.0.2", 15 | "class-variance-authority": "^0.7.0", 16 | "clsx": "^2.1.0", 17 | "date-fns": "^3.6.0", 18 | "hono": "^4.1.3", 19 | "honox": "^0.1.9", 20 | "lucide-react": "^0.376.0", 21 | "react": "^18.2.0", 22 | "react-day-picker": "^8.10.0", 23 | "react-dom": "^18.2.0", 24 | "tailwind-merge": "^2.2.2", 25 | "tailwindcss-animate": "^1.0.7" 26 | }, 27 | "devDependencies": { 28 | "@cloudflare/workers-types": "^4.20240208.0", 29 | "@hono/vite-cloudflare-pages": "^0.4.0", 30 | "@types/react": "^18.2.67", 31 | "@types/react-dom": "^18.2.22", 32 | "autoprefixer": "^10.4.19", 33 | "postcss": "^8.4.38", 34 | "tailwindcss": "^3.4.1", 35 | "vite": "^5.0.12", 36 | "wrangler": "^3.32.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended"] 4 | } 5 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: ["./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}"], 5 | prefix: "", 6 | theme: { 7 | container: { 8 | center: true, 9 | padding: "2rem", 10 | screens: { 11 | "2xl": "1400px", 12 | }, 13 | }, 14 | extend: { 15 | colors: { 16 | border: "hsl(var(--border))", 17 | input: "hsl(var(--input))", 18 | ring: "hsl(var(--ring))", 19 | background: "hsl(var(--background))", 20 | foreground: "hsl(var(--foreground))", 21 | primary: { 22 | DEFAULT: "hsl(var(--primary))", 23 | foreground: "hsl(var(--primary-foreground))", 24 | }, 25 | secondary: { 26 | DEFAULT: "hsl(var(--secondary))", 27 | foreground: "hsl(var(--secondary-foreground))", 28 | }, 29 | destructive: { 30 | DEFAULT: "hsl(var(--destructive))", 31 | foreground: "hsl(var(--destructive-foreground))", 32 | }, 33 | muted: { 34 | DEFAULT: "hsl(var(--muted))", 35 | foreground: "hsl(var(--muted-foreground))", 36 | }, 37 | accent: { 38 | DEFAULT: "hsl(var(--accent))", 39 | foreground: "hsl(var(--accent-foreground))", 40 | }, 41 | popover: { 42 | DEFAULT: "hsl(var(--popover))", 43 | foreground: "hsl(var(--popover-foreground))", 44 | }, 45 | card: { 46 | DEFAULT: "hsl(var(--card))", 47 | foreground: "hsl(var(--card-foreground))", 48 | }, 49 | }, 50 | borderRadius: { 51 | lg: "var(--radius)", 52 | md: "calc(var(--radius) - 2px)", 53 | sm: "calc(var(--radius) - 4px)", 54 | }, 55 | keyframes: { 56 | "accordion-down": { 57 | from: { height: "0" }, 58 | to: { height: "var(--radix-accordion-content-height)" }, 59 | }, 60 | "accordion-up": { 61 | from: { height: "var(--radix-accordion-content-height)" }, 62 | to: { height: "0" }, 63 | }, 64 | }, 65 | animation: { 66 | "accordion-down": "accordion-down 0.2s ease-out", 67 | "accordion-up": "accordion-up 0.2s ease-out", 68 | }, 69 | }, 70 | }, 71 | plugins: [require("tailwindcss-animate")], 72 | }; 73 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "lib": ["ESNext", "DOM"], 8 | "types": ["@cloudflare/workers-types", "vite/client"], 9 | "jsx": "react-jsx", 10 | "jsxImportSource": "react", 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/*": ["./app/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import pages from "@hono/vite-cloudflare-pages"; 3 | import honox from "honox/vite"; 4 | import { defineConfig } from "vite"; 5 | import client from "honox/vite/client"; 6 | 7 | export default defineConfig(({ mode }) => { 8 | const common = { 9 | resolve: { 10 | alias: { 11 | "@": path.resolve(__dirname, "./app"), 12 | }, 13 | }, 14 | }; 15 | 16 | if (mode === "client") { 17 | return { 18 | ...common, 19 | plugins: [client({ jsxImportSource: "react" })], 20 | build: { 21 | rollupOptions: { 22 | input: ["./app/client.ts", "/app/tailwind.css"], 23 | output: { 24 | entryFileNames: "static/client.js", 25 | chunkFileNames: "static/assets/[name]-[hash].js", 26 | assetFileNames: "static/assets/[name].[ext]", 27 | }, 28 | }, 29 | }, 30 | }; 31 | } else { 32 | return { 33 | ...common, 34 | ssr: { external: ["react", "react-dom"] }, 35 | plugins: [honox(), pages()], 36 | }; 37 | } 38 | }); 39 | --------------------------------------------------------------------------------