Skip to content

Commit

Permalink
Merge branch 'main' into feat/export-named-component
Browse files Browse the repository at this point in the history
  • Loading branch information
usualoma committed May 4, 2024
2 parents ab23d39 + 481e900 commit 8597e66
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 14 deletions.
5 changes: 4 additions & 1 deletion mocks/app/islands/Counter.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { PropsWithChildren } from 'hono/jsx'
import type { PropsWithChildren, Child } from 'hono/jsx'
import { useState } from 'hono/jsx'
import Badge from './Badge'

export default function Counter({
children,
initial = 0,
id = '',
slot,
}: PropsWithChildren<{
initial?: number
id?: string
slot?: Child
}>) {
const [count, setCount] = useState(initial)
const increment = () => setCount(count + 1)
Expand All @@ -18,6 +20,7 @@ export default function Counter({
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
{children}
{slot && <div>{slot}</div>}
</div>
)
}
3 changes: 3 additions & 0 deletions mocks/app/routes/interaction/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default function Interaction() {
<Counter initial={15} />
</Counter>
<NamedCounter initial={30} id='named' />
<Counter initial={20} slot={<Counter id='slot' initial={25} />}>
<Counter initial={30} />
</Counter>
</>
)
}
16 changes: 10 additions & 6 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ export const createClient = async (options?: ClientOptions) => {
const hydrate = options?.hydrate ?? render
const createElement = options?.createElement ?? jsxFn

const maybeTemplate = element.childNodes[element.childNodes.length - 1]
if (
maybeTemplate?.nodeName === 'TEMPLATE' &&
(maybeTemplate as HTMLElement)?.attributes.getNamedItem(DATA_HONO_TEMPLATE) !== null
) {
let maybeTemplate = element.childNodes[element.childNodes.length - 1]
while (maybeTemplate?.nodeName === 'TEMPLATE') {
const propKey = (maybeTemplate as HTMLElement).getAttribute(DATA_HONO_TEMPLATE)
if (propKey == null) {
break
}

let createChildren = options?.createChildren
if (!createChildren) {
const { buildCreateChildrenFn } = await import('./runtime')
Expand All @@ -74,9 +76,11 @@ export const createClient = async (options?: ClientOptions) => {
async (name: string) => (await (FILES[`${root}${name}`] as FileCallback)()).default
)
}
props.children = await createChildren(
props[propKey] = await createChildren(
(maybeTemplate as HTMLTemplateElement).content.childNodes
)

maybeTemplate = maybeTemplate.previousSibling as ChildNode
}

const newElem = await createElement(Component, props)
Expand Down
29 changes: 23 additions & 6 deletions src/vite/components/honox-island.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useContext } from 'hono/jsx'
import { createContext, useContext, isValidElement } from 'hono/jsx'
import {
COMPONENT_NAME,
COMPONENT_EXPORT,
Expand All @@ -13,6 +13,11 @@ const IslandContext = createContext({
[inChildren]: false,
})

const isElementPropValue = (value: unknown): boolean =>
Array.isArray(value)
? value.some(isElementPropValue)
: typeof value === 'object' && isValidElement(value)

export const HonoXIsland = ({
componentName,
componentExport,
Expand All @@ -25,27 +30,39 @@ export const HonoXIsland = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
props: any
}) => {
const { children, ...rest } = props
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const elementProps: Record<string, any> = {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const restProps: Record<string, any> = {}
for (const key in props) {
const value = props[key]
if (isElementPropValue(value)) {
elementProps[key] = value
} else {
restProps[key] = value
}
}

const islandState = useContext(IslandContext)
return islandState[inChildren] || !islandState[inIsland] ? (
// top-level or slot content
<honox-island
{...{
[COMPONENT_NAME]: componentName,
[COMPONENT_EXPORT]: componentExport || undefined,
[DATA_SERIALIZED_PROPS]: JSON.stringify(rest),
[DATA_SERIALIZED_PROPS]: JSON.stringify(restProps),
}}
>
<IslandContext.Provider value={{ ...islandState, [inIsland]: true }}>
<Component {...props} />
</IslandContext.Provider>
{children && (
<template {...{ [DATA_HONO_TEMPLATE]: '' }}>
{Object.entries(elementProps).map(([key, children]) => (
<template {...{ [DATA_HONO_TEMPLATE]: key }}>
<IslandContext.Provider value={{ ...islandState, [inChildren]: true }}>
{children}
</IslandContext.Provider>
</template>
)}
))}
</honox-island>
) : (
// nested component
Expand Down
2 changes: 1 addition & 1 deletion test-integration/apps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ describe('With preserved', () => {
expect(res.status).toBe(200)
// hono/jsx escape a single quote to &#39;
expect(await res.text()).toBe(
'<!DOCTYPE html><html><head><title></title></head><body><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:5,&quot;id&quot;:&quot;first&quot;}"><div id="first"><p>Counter</p><p>Count: 5</p><button>Increment</button></div></honox-island><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:10}"><div id=""><p>Counter</p><p>Count: 10</p><button>Increment</button><div id=""><p>Counter</p><p>Count: 15</p><button>Increment</button></div></div><template data-hono-template=""><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:15}"><div id=""><honox-island component-name="/islands/Badge.tsx" data-serialized-props="{&quot;name&quot;:&quot;Counter&quot;}"><p>Counter</p></honox-island><p>Count: 15</p><button>Increment</button></div></honox-island></template></honox-island><honox-island component-name="/islands/NamedCounter.tsx" component-export="NamedCounter" data-serialized-props="{&quot;initial&quot;:30,&quot;id&quot;:&quot;named&quot;}"><div id="named"><p>Counter</p><p>Count: 30</p><button>Increment</button></div></honox-island><script type="module" async="" src="/app/client.ts"></script></body></html>'
'<!DOCTYPE html><html><head><title></title></head><body><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:5,&quot;id&quot;:&quot;first&quot;}"><div id="first"><p>Counter</p><p>Count: 5</p><button>Increment</button></div></honox-island><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:10}"><div id=""><p>Counter</p><p>Count: 10</p><button>Increment</button><div id=""><p>Counter</p><p>Count: 15</p><button>Increment</button></div></div><template data-hono-template="children"><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:15}"><div id=""><honox-island component-name="/islands/Badge.tsx" data-serialized-props="{&quot;name&quot;:&quot;Counter&quot;}"><p>Counter</p></honox-island><p>Count: 15</p><button>Increment</button></div></honox-island></template></honox-island><honox-island component-name="/islands/NamedCounter.tsx" component-export="NamedCounter" data-serialized-props="{&quot;initial&quot;:30,&quot;id&quot;:&quot;named&quot;}"><div id="named"><p>Counter</p><p>Count: 30</p><button>Increment</button></div></honox-island><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:20}"><div id=""><p>Counter</p><p>Count: 20</p><button>Increment</button><div id=""><p>Counter</p><p>Count: 30</p><button>Increment</button></div><div><div id="slot"><p>Counter</p><p>Count: 25</p><button>Increment</button></div></div></div><template data-hono-template="slot"><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;id&quot;:&quot;slot&quot;,&quot;initial&quot;:25}"><div id="slot"><honox-island component-name="/islands/Badge.tsx" data-serialized-props="{&quot;name&quot;:&quot;Counter&quot;}"><p>Counter</p></honox-island><p>Count: 25</p><button>Increment</button></div></honox-island></template><template data-hono-template="children"><honox-island component-name="/islands/Counter.tsx" data-serialized-props="{&quot;initial&quot;:30}"><div id=""><honox-island component-name="/islands/Badge.tsx" data-serialized-props="{&quot;name&quot;:&quot;Counter&quot;}"><p>Counter</p></honox-island><p>Count: 30</p><button>Increment</button></div></honox-island></template></honox-island><script type="module" async="" src="/app/client.ts"></script></body></html>'
)
})

Expand Down

0 comments on commit 8597e66

Please sign in to comment.