From cb25e303168db4bac44f06f3a21ba8a04b95dd94 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:37:25 +0900 Subject: [PATCH 1/5] chore: initialize @zag-js/hono-jsx package --- packages/frameworks/hono-jsx/package.json | 49 +++++++++++++++++++++ packages/frameworks/hono-jsx/tsconfig.json | 9 ++++ packages/frameworks/hono-jsx/tsup.config.ts | 6 +++ 3 files changed, 64 insertions(+) create mode 100644 packages/frameworks/hono-jsx/package.json create mode 100644 packages/frameworks/hono-jsx/tsconfig.json create mode 100644 packages/frameworks/hono-jsx/tsup.config.ts diff --git a/packages/frameworks/hono-jsx/package.json b/packages/frameworks/hono-jsx/package.json new file mode 100644 index 0000000000..37cadfb312 --- /dev/null +++ b/packages/frameworks/hono-jsx/package.json @@ -0,0 +1,49 @@ +{ + "name": "@zag-js/hono-jsx", + "version": "1.18.3", + "description": "The hono jsx wrapper for zag", + "keywords": [ + "ui-machines", + "state-machines", + "zag", + "hono", + "use-machine", + "hook" + ], + "author": "Segun Adebayo ", + "homepage": "https://github.com/chakra-ui/zag#readme", + "license": "MIT", + "repository": "https://github.com/chakra-ui/zag/tree/main/packages/frameworks/hono-jsx", + "sideEffects": false, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://github.com/chakra-ui/zag/issues" + }, + "dependencies": { + "@zag-js/core": "workspace:*", + "@zag-js/store": "workspace:*", + "@zag-js/types": "workspace:*", + "@zag-js/utils": "workspace:*" + }, + "devDependencies": { + "hono": "4.8.5" + }, + "peerDependencies": { + "hono": ">=4.8.0" + }, + "scripts": { + "build": "tsup", + "lint": "eslint src", + "typecheck": "tsc --noEmit", + "prepack": "clean-package", + "postpack": "clean-package restore", + "test": "vitest" + }, + "clean-package": "../../../clean-package.config.json", + "main": "src/index.ts" +} diff --git a/packages/frameworks/hono-jsx/tsconfig.json b/packages/frameworks/hono-jsx/tsconfig.json new file mode 100644 index 0000000000..3bc5cc1c04 --- /dev/null +++ b/packages/frameworks/hono-jsx/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["src", "tests"], + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo" + } +} diff --git a/packages/frameworks/hono-jsx/tsup.config.ts b/packages/frameworks/hono-jsx/tsup.config.ts new file mode 100644 index 0000000000..da00695af0 --- /dev/null +++ b/packages/frameworks/hono-jsx/tsup.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "tsup" +import rootConfig from "../../../tsup.config" + +export default defineConfig({ + ...rootConfig, +}) From cc9264a2c2c950ad27207c78b1e36525e5f7cbde Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Fri, 18 Jul 2025 05:55:59 +0900 Subject: [PATCH 2/5] feat: implement Hono JSX wrapper --- packages/frameworks/hono-jsx/package.json | 3 +- packages/frameworks/hono-jsx/src/bindable.ts | 72 ++++ packages/frameworks/hono-jsx/src/index.ts | 4 + packages/frameworks/hono-jsx/src/machine.ts | 321 ++++++++++++++++++ .../hono-jsx/src/normalize-props.ts | 19 ++ packages/frameworks/hono-jsx/src/portal.tsx | 21 ++ packages/frameworks/hono-jsx/src/refs.ts | 13 + packages/frameworks/hono-jsx/src/track.ts | 20 ++ .../hono-jsx/src/use-layout-effect.ts | 3 + 9 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 packages/frameworks/hono-jsx/src/bindable.ts create mode 100644 packages/frameworks/hono-jsx/src/index.ts create mode 100644 packages/frameworks/hono-jsx/src/machine.ts create mode 100644 packages/frameworks/hono-jsx/src/normalize-props.ts create mode 100644 packages/frameworks/hono-jsx/src/portal.tsx create mode 100644 packages/frameworks/hono-jsx/src/refs.ts create mode 100644 packages/frameworks/hono-jsx/src/track.ts create mode 100644 packages/frameworks/hono-jsx/src/use-layout-effect.ts diff --git a/packages/frameworks/hono-jsx/package.json b/packages/frameworks/hono-jsx/package.json index 37cadfb312..8390d0a44c 100644 --- a/packages/frameworks/hono-jsx/package.json +++ b/packages/frameworks/hono-jsx/package.json @@ -31,7 +31,8 @@ "@zag-js/utils": "workspace:*" }, "devDependencies": { - "hono": "4.8.5" + "hono": "4.8.5", + "clean-package": "2.2.0" }, "peerDependencies": { "hono": ">=4.8.0" diff --git a/packages/frameworks/hono-jsx/src/bindable.ts b/packages/frameworks/hono-jsx/src/bindable.ts new file mode 100644 index 0000000000..7d69cfc0c8 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/bindable.ts @@ -0,0 +1,72 @@ +import type { Bindable, BindableParams } from "@zag-js/core" +import { identity, isFunction } from "@zag-js/utils" +import { useEffect, useRef, useState } from "hono/jsx" +import { flushSync } from "hono/jsx/dom" +import { useSafeLayoutEffect } from "./use-layout-effect" + +export function useBindable(props: () => BindableParams): Bindable { + const initial = props().value ?? props().defaultValue + + const eq = props().isEqual ?? Object.is + + const [initialValue] = useState(initial) + const [value, setValue] = useState(initialValue) + + const controlled = props().value !== undefined + + const valueRef = useRef(value) + valueRef.current = controlled ? props().value : value + + const prevValue = useRef(valueRef.current) + useSafeLayoutEffect(() => { + prevValue.current = valueRef.current + }, [value, props().value]) + + const setFn = (value: T | ((prev: T) => T)) => { + const prev = prevValue.current === null ? undefined : prevValue.current + const next = isFunction(value) ? value(prev as T) : value + + if (props().debug) { + console.log(`[bindable > ${props().debug}] setValue`, { next, prev }) + } + + if (!controlled) setValue(next) + if (!eq(next, prev)) { + props().onChange?.(next, prev) + } + } + + function get(): T { + return (controlled ? props().value : value) as T + } + + return { + initial: initialValue, + ref: valueRef, + get, + set(value: T | ((prev: T) => T)) { + const exec = props().sync ? flushSync : identity + exec(() => setFn(value)) + }, + invoke(nextValue: T, prevValue: T) { + props().onChange?.(nextValue, prevValue) + }, + hash(value: T) { + return props().hash?.(value) ?? String(value) + }, + } +} + +useBindable.cleanup = (fn: VoidFunction) => { + useEffect(() => fn, []) +} + +useBindable.ref = (defaultValue: T) => { + const value = useRef(defaultValue) + return { + get: () => value.current as T, + set: (next: T) => { + value.current = next + }, + } +} diff --git a/packages/frameworks/hono-jsx/src/index.ts b/packages/frameworks/hono-jsx/src/index.ts new file mode 100644 index 0000000000..c3d9f65f8d --- /dev/null +++ b/packages/frameworks/hono-jsx/src/index.ts @@ -0,0 +1,4 @@ +export { mergeProps } from "@zag-js/core" +export * from "./machine" +export * from "./normalize-props" +export * from "./portal" diff --git a/packages/frameworks/hono-jsx/src/machine.ts b/packages/frameworks/hono-jsx/src/machine.ts new file mode 100644 index 0000000000..7bfd01a9a7 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/machine.ts @@ -0,0 +1,321 @@ +import type { + ActionsOrFn, + Bindable, + BindableContext, + BindableRefs, + ChooseFn, + ComputedFn, + EffectsOrFn, + GuardFn, + Machine, + MachineSchema, + Params, + Service, +} from "@zag-js/core" +import { createScope, INIT_STATE, MachineStatus } from "@zag-js/core" +import { compact, ensure, isFunction, isString, toArray, warn } from "@zag-js/utils" +import { useMemo, useRef } from "hono/jsx" +import { flushSync } from "hono/jsx/dom" +import { useBindable } from "./bindable" +import { useRefs } from "./refs" +import { useTrack } from "./track" +import { useSafeLayoutEffect } from "./use-layout-effect" + +export function useMachine( + machine: Machine, + userProps: Partial = {}, +): Service { + const scope = useMemo(() => { + const { id, ids, getRootNode } = userProps as any + return createScope({ id, ids, getRootNode }) + }, [userProps]) + + const debug = (...args: any[]) => { + if (machine.debug) console.log(...args) + } + + const props: any = machine.props?.({ props: compact(userProps), scope }) ?? userProps + const prop = useProp(props) + + const context = machine.context?.({ + prop, + bindable: useBindable, + scope, + flush, + getContext() { + return ctx as any + }, + getComputed() { + return computed as any + }, + getRefs() { + return refs as any + }, + getEvent() { + return getEvent() + }, + }) + + const contextRef = useLiveRef(context) + const ctx: BindableContext = { + get(key) { + return contextRef.current?.[key].ref.current + }, + set(key, value) { + contextRef.current?.[key].set(value) + }, + initial(key) { + return contextRef.current?.[key].initial + }, + hash(key) { + const current = contextRef.current?.[key].get() + return contextRef.current?.[key].hash(current) + }, + } + + const effects = useRef(new Map()) + const transitionRef = useRef(null) + + const previousEventRef = useRef(null) + const eventRef = useRef({ type: "" }) + + const getEvent = () => ({ + ...eventRef.current, + current() { + return eventRef.current + }, + previous() { + return previousEventRef.current + }, + }) + + const getState = () => ({ + ...state, + matches(...values: T["state"][]) { + return values.includes(state.ref.current) + }, + hasTag(tag: T["tag"]) { + return !!machine.states[state.ref.current as T["state"]]?.tags?.includes(tag) + }, + }) + + const refs: BindableRefs = useRefs(machine.refs?.({ prop, context: ctx }) ?? {}) + + const getParams = (): Params => ({ + state: getState(), + context: ctx, + event: getEvent(), + prop, + send, + action, + guard, + track: useTrack, + refs, + computed, + flush, + scope, + choose, + }) + + const action = (keys: ActionsOrFn | undefined) => { + const strs = isFunction(keys) ? keys(getParams()) : keys + if (!strs) return + const fns = strs.map((s) => { + const fn = machine.implementations?.actions?.[s] + if (!fn) warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`) + return fn + }) + for (const fn of fns) { + fn?.(getParams()) + } + } + + const guard = (str: T["guard"] | GuardFn) => { + if (isFunction(str)) return str(getParams()) + return machine.implementations?.guards?.[str](getParams()) + } + + const effect = (keys: EffectsOrFn | undefined) => { + const strs = isFunction(keys) ? keys(getParams()) : keys + if (!strs) return + const fns = strs.map((s) => { + const fn = machine.implementations?.effects?.[s] + if (!fn) warn(`[zag-js] No implementation found for effect "${JSON.stringify(s)}"`) + return fn + }) + const cleanups: VoidFunction[] = [] + for (const fn of fns) { + const cleanup = fn?.(getParams()) + if (cleanup) cleanups.push(cleanup) + } + return () => cleanups.forEach((fn) => fn?.()) + } + + const choose: ChooseFn = (transitions) => { + return toArray(transitions).find((t) => { + let result = !t.guard + if (isString(t.guard)) result = !!guard(t.guard) + else if (isFunction(t.guard)) result = t.guard(getParams()) + return result + }) + } + + const computed: ComputedFn = (key) => { + ensure(machine.computed, () => `[zag-js] No computed object found on machine`) + const fn = machine.computed[key] + return fn({ + context: ctx as any, + event: getEvent(), + prop, + refs, + scope, + computed: computed as any, + }) + } + + const state = useBindable(() => ({ + defaultValue: machine.initialState({ prop }), + onChange(nextState, prevState) { + // compute effects: exit -> transition -> enter + + // exit effects + if (prevState) { + const exitEffects = effects.current!.get(prevState) + exitEffects?.() + effects.current!.delete(prevState) + } + + // exit actions + if (prevState) { + action(machine.states[prevState]?.exit) + } + + // transition actions + action(transitionRef.current?.actions) + + // enter effect + const cleanup = effect(machine.states[nextState]?.effects) + if (cleanup) effects.current!.set(nextState as string, cleanup) + + // root entry actions + if (prevState === INIT_STATE) { + action(machine.entry) + const cleanup = effect(machine.effects) + if (cleanup) effects.current!.set(INIT_STATE, cleanup) + } + + // enter actions + action(machine.states[nextState]?.entry) + }, + })) + + // improve HMR (to restart effects) + const hydratedStateRef = useRef(undefined) + const statusRef = useRef(MachineStatus.NotStarted) + + useSafeLayoutEffect(() => { + queueMicrotask(() => { + const started = statusRef.current === MachineStatus.Started + statusRef.current = MachineStatus.Started + debug(started ? "rehydrating..." : "initializing...") + + // start the transition + const initialState = hydratedStateRef.current ?? state.initial! + state.invoke(initialState, started ? state.get() : INIT_STATE) + }) + + const fns = effects.current + const currentState = state.ref.current + return () => { + debug("unmounting...") + hydratedStateRef.current = currentState + statusRef.current = MachineStatus.Stopped + + fns!.forEach((fn) => fn?.()) + effects.current = new Map() + transitionRef.current = null + + queueMicrotask(() => { + action(machine.exit) + }) + } + }, []) + + const getCurrentState = () => { + if ("ref" in state) return state.ref.current + return (state as Bindable).get() + } + + const send = (event: any) => { + queueMicrotask(() => { + if (statusRef.current !== MachineStatus.Started) return + + previousEventRef.current = eventRef.current + eventRef.current = event + + debug("send", event) + + let currentState = getCurrentState() + + const transitions = + // @ts-ignore + machine.states[currentState].on?.[event.type] ?? + // @ts-ignore + machine.on?.[event.type] + + const transition = choose(transitions) + if (!transition) return + + // save current transition + transitionRef.current = transition + const target = transition.target ?? currentState + + debug("transition", transition) + + const changed = target !== currentState + if (changed) { + // state change is high priority + flushSync(() => state.set(target)) + } else if (transition.reenter && !changed) { + // reenter will re-invoke the current state + state.invoke(currentState, currentState) + } else { + // call transition actions + action(transition.actions ?? []) + } + }) + } + + machine.watch?.(getParams()) + + return { + state: getState(), + send, + context: ctx, + prop, + scope, + refs, + computed, + event: getEvent(), + getStatus: () => statusRef.current, + } as Service +} + +function useLiveRef(value: T) { + const ref = useRef(value) + ref.current = value + return ref +} + +function useProp(value: T) { + const ref = useLiveRef(value) + return function get(key: K): T[K] { + return ref.current![key] + } +} + +function flush(fn: VoidFunction) { + queueMicrotask(() => { + flushSync(() => fn()) + }) +} diff --git a/packages/frameworks/hono-jsx/src/normalize-props.ts b/packages/frameworks/hono-jsx/src/normalize-props.ts new file mode 100644 index 0000000000..efa9c9cf28 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/normalize-props.ts @@ -0,0 +1,19 @@ +import { createNormalizer } from "@zag-js/types" +import type { CSSProperties, JSX } from "hono/jsx" + +type WithoutRef = Omit + +type ElementsWithoutRef = { + [K in keyof JSX.IntrinsicElements]: WithoutRef +} + +export type PropTypes = ElementsWithoutRef & { + rect: any + circle: any + svg: any + path: any + element: WithoutRef + style: CSSProperties +} + +export const normalizeProps = createNormalizer((v) => v) diff --git a/packages/frameworks/hono-jsx/src/portal.tsx b/packages/frameworks/hono-jsx/src/portal.tsx new file mode 100644 index 0000000000..4892f87d49 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/portal.tsx @@ -0,0 +1,21 @@ +import type { PropsWithChildren, RefObject } from "hono/jsx" +import { Fragment, Children } from "hono/jsx" +import { createPortal } from "hono/jsx/dom" + +export interface PortalProps { + disabled?: boolean | undefined + container?: RefObject | undefined + getRootNode?: (() => ShadowRoot | Document | Node) | undefined +} + +export const Portal = (props: PropsWithChildren) => { + const { children, container, disabled, getRootNode } = props + + const isServer = typeof window === "undefined" + if (isServer || disabled) return {children} + + const doc = getRootNode?.().ownerDocument ?? document + const mountNode = container?.current ?? doc.body + + return {Children.map(Children.toArray(children), (child) => createPortal(child, mountNode))} +} diff --git a/packages/frameworks/hono-jsx/src/refs.ts b/packages/frameworks/hono-jsx/src/refs.ts new file mode 100644 index 0000000000..3913c44574 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/refs.ts @@ -0,0 +1,13 @@ +import { useRef } from "hono/jsx" + +export function useRefs(refs: T) { + const ref = useRef(refs) + return { + get(key: K): T[K] { + return ref.current![key] + }, + set(key: K, value: T[K]) { + ;(ref.current! as any)[key] = value + }, + } +} diff --git a/packages/frameworks/hono-jsx/src/track.ts b/packages/frameworks/hono-jsx/src/track.ts new file mode 100644 index 0000000000..a7515cd341 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/track.ts @@ -0,0 +1,20 @@ +import { useEffect, useRef } from "hono/jsx" + +export const useTrack = (deps: any[], effect: VoidFunction) => { + const render = useRef(false) + const called = useRef(false) + + useEffect(() => { + const mounted = render.current + const run = mounted && called.current + if (run) return effect() + called.current = true + }, [...(deps ?? []).map((d) => (typeof d === "function" ? d() : d))]) + + useEffect(() => { + render.current = true + return () => { + render.current = false + } + }, []) +} diff --git a/packages/frameworks/hono-jsx/src/use-layout-effect.ts b/packages/frameworks/hono-jsx/src/use-layout-effect.ts new file mode 100644 index 0000000000..bb06f527e9 --- /dev/null +++ b/packages/frameworks/hono-jsx/src/use-layout-effect.ts @@ -0,0 +1,3 @@ +import { useEffect, useLayoutEffect } from "hono/jsx" + +export const useSafeLayoutEffect = typeof globalThis.document !== "undefined" ? useLayoutEffect : useEffect From 17d3c2556ec7d6d78a51b2672f87f1f7b1fbfc75 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Fri, 18 Jul 2025 06:56:27 +0900 Subject: [PATCH 3/5] chore: update lockfile --- pnpm-lock.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09afe399f0..9040e3dd55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1747,6 +1747,28 @@ importers: specifier: 2.2.0 version: 2.2.0 + packages/frameworks/hono-jsx: + dependencies: + '@zag-js/core': + specifier: workspace:* + version: link:../../core + '@zag-js/store': + specifier: workspace:* + version: link:../../store + '@zag-js/types': + specifier: workspace:* + version: link:../../types + '@zag-js/utils': + specifier: workspace:* + version: link:../../utilities/core + devDependencies: + clean-package: + specifier: 2.2.0 + version: 2.2.0 + hono: + specifier: 4.8.5 + version: 4.8.5 + packages/frameworks/preact: dependencies: '@zag-js/core': @@ -9127,6 +9149,10 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} + hono@4.8.5: + resolution: {integrity: sha512-Up2cQbtNz1s111qpnnECdTGqSIUIhZJMLikdKkshebQSEBcoUKq6XJayLGqSZWidiH0zfHRCJqFu062Mz5UuRA==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -19644,6 +19670,8 @@ snapshots: dependencies: parse-passwd: 1.0.0 + hono@4.8.5: {} + hookable@5.5.3: {} hosted-git-info@7.0.2: From 0eaedfbb181dce8ef6cca4c9de367a3a7851186e Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Fri, 18 Jul 2025 20:48:01 +0900 Subject: [PATCH 4/5] chore: initialize Hono JSX example package --- examples/hono-jsx-ts/.gitignore | 34 + examples/hono-jsx-ts/app/client.ts | 3 + examples/hono-jsx-ts/app/global.d.ts | 8 + examples/hono-jsx-ts/app/islands/counter.tsx | 13 + examples/hono-jsx-ts/app/routes/_404.tsx | 8 + examples/hono-jsx-ts/app/routes/_error.tsx | 12 + examples/hono-jsx-ts/app/routes/_renderer.tsx | 17 + examples/hono-jsx-ts/app/routes/index.tsx | 13 + examples/hono-jsx-ts/app/server.ts | 8 + examples/hono-jsx-ts/app/style.css | 1 + examples/hono-jsx-ts/package.json | 97 + examples/hono-jsx-ts/public/.assetsignore | 2 + examples/hono-jsx-ts/public/favicon.ico | Bin 0 -> 15406 bytes examples/hono-jsx-ts/tsconfig.json | 23 + examples/hono-jsx-ts/vite.config.ts | 16 + examples/hono-jsx-ts/wrangler.jsonc | 37 + pnpm-lock.yaml | 2447 +++++++++++++++-- 17 files changed, 2552 insertions(+), 187 deletions(-) create mode 100644 examples/hono-jsx-ts/.gitignore create mode 100644 examples/hono-jsx-ts/app/client.ts create mode 100644 examples/hono-jsx-ts/app/global.d.ts create mode 100644 examples/hono-jsx-ts/app/islands/counter.tsx create mode 100644 examples/hono-jsx-ts/app/routes/_404.tsx create mode 100644 examples/hono-jsx-ts/app/routes/_error.tsx create mode 100644 examples/hono-jsx-ts/app/routes/_renderer.tsx create mode 100644 examples/hono-jsx-ts/app/routes/index.tsx create mode 100644 examples/hono-jsx-ts/app/server.ts create mode 100644 examples/hono-jsx-ts/app/style.css create mode 100644 examples/hono-jsx-ts/package.json create mode 100644 examples/hono-jsx-ts/public/.assetsignore create mode 100644 examples/hono-jsx-ts/public/favicon.ico create mode 100644 examples/hono-jsx-ts/tsconfig.json create mode 100644 examples/hono-jsx-ts/vite.config.ts create mode 100644 examples/hono-jsx-ts/wrangler.jsonc diff --git a/examples/hono-jsx-ts/.gitignore b/examples/hono-jsx-ts/.gitignore new file mode 100644 index 0000000000..f86f576435 --- /dev/null +++ b/examples/hono-jsx-ts/.gitignore @@ -0,0 +1,34 @@ +# prod +dist/ + +# dev +.hono/ +.wrangler/ +.yarn/ +!.yarn/releases +.vscode/* +!.vscode/launch.json +!.vscode/*.code-snippets +.idea/workspace.xml +.idea/usage.statistics.xml +.idea/shelf + +# deps +node_modules/ + +# env +.env +.env.production +.dev.vars + +# logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# misc +.DS_Store diff --git a/examples/hono-jsx-ts/app/client.ts b/examples/hono-jsx-ts/app/client.ts new file mode 100644 index 0000000000..5021a92e21 --- /dev/null +++ b/examples/hono-jsx-ts/app/client.ts @@ -0,0 +1,3 @@ +import { createClient } from "honox/client" + +createClient() diff --git a/examples/hono-jsx-ts/app/global.d.ts b/examples/hono-jsx-ts/app/global.d.ts new file mode 100644 index 0000000000..41912351e3 --- /dev/null +++ b/examples/hono-jsx-ts/app/global.d.ts @@ -0,0 +1,8 @@ +import type {} from "hono" + +declare module "hono" { + interface Env { + Variables: {} + Bindings: {} + } +} diff --git a/examples/hono-jsx-ts/app/islands/counter.tsx b/examples/hono-jsx-ts/app/islands/counter.tsx new file mode 100644 index 0000000000..e6b9325d73 --- /dev/null +++ b/examples/hono-jsx-ts/app/islands/counter.tsx @@ -0,0 +1,13 @@ +import { useState } from "hono/jsx" + +export default function Counter() { + const [count, setCount] = useState(0) + return ( +
+

{count}

+ +
+ ) +} diff --git a/examples/hono-jsx-ts/app/routes/_404.tsx b/examples/hono-jsx-ts/app/routes/_404.tsx new file mode 100644 index 0000000000..1c12d8df33 --- /dev/null +++ b/examples/hono-jsx-ts/app/routes/_404.tsx @@ -0,0 +1,8 @@ +import type { NotFoundHandler } from "hono" + +const handler: NotFoundHandler = (c) => { + c.status(404) + return c.render("404 Not Found") +} + +export default handler diff --git a/examples/hono-jsx-ts/app/routes/_error.tsx b/examples/hono-jsx-ts/app/routes/_error.tsx new file mode 100644 index 0000000000..121e07f121 --- /dev/null +++ b/examples/hono-jsx-ts/app/routes/_error.tsx @@ -0,0 +1,12 @@ +import type { ErrorHandler } from "hono" + +const handler: ErrorHandler = (e, c) => { + if ("getResponse" in e) { + return e.getResponse() + } + console.error(e.message) + c.status(500) + return c.render("Internal Server Error") +} + +export default handler diff --git a/examples/hono-jsx-ts/app/routes/_renderer.tsx b/examples/hono-jsx-ts/app/routes/_renderer.tsx new file mode 100644 index 0000000000..33946865a1 --- /dev/null +++ b/examples/hono-jsx-ts/app/routes/_renderer.tsx @@ -0,0 +1,17 @@ +import { jsxRenderer } from "hono/jsx-renderer" +import { Link, Script } from "honox/server" + +export default jsxRenderer(({ children }) => { + return ( + + + + + + +