Skip to content

Commit

Permalink
feat: $ prefix named file as island component (#171)
Browse files Browse the repository at this point in the history
* feat: `$` prefix named file as island component

* fixed regexp and added tests

Co-authored-by: Taku Amano <taku@taaas.jp>
  • Loading branch information
yusukebe and usualoma authored May 10, 2024
1 parent 8abdc53 commit 4ab674d
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 10 deletions.
21 changes: 21 additions & 0 deletions mocks/app/routes/directory/$counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { PropsWithChildren } from 'hono/jsx'
import { useState } from 'hono/jsx'

export default function Counter({
children,
initial = 0,
id = '',
}: PropsWithChildren<{
initial?: number
id?: string
}>) {
const [count, setCount] = useState(initial)
const increment = () => setCount(count + 1)
return (
<div id={id}>
<p>DollarCount: {count}</p>
<button onClick={increment}>Dollar Increment</button>
{children}
</div>
)
}
4 changes: 2 additions & 2 deletions mocks/app/routes/directory/_Counter.island.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default function Counter({
const increment = () => setCount(count + 1)
return (
<div id={id}>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<p>UnderScoreCount: {count}</p>
<button onClick={increment}>UnderScore Increment</button>
{children}
</div>
)
Expand Down
10 changes: 8 additions & 2 deletions mocks/app/routes/directory/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import Counter from './_Counter.island'
import DollarCounter from './$counter'
import UnderScoreCounter from './_Counter.island'

export default function Interaction() {
return <Counter initial={5} />
return (
<>
<UnderScoreCounter id='under-score' initial={5} />
<DollarCounter id='dollar' initial={5} />
</>
)
}
39 changes: 38 additions & 1 deletion src/vite/island-components.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
import fs from 'fs'
import os from 'os'
import path from 'path'
import { transformJsxTags, islandComponents } from './island-components'
import { matchIslandComponentId, transformJsxTags, islandComponents } from './island-components'

describe('matchIslandComponentId', () => {
describe('match', () => {
const paths = [
'/islands/counter.tsx',
'/islands/directory/counter.tsx',
'/routes/$counter.tsx',
'/routes/directory/$counter.tsx',
'/routes/_counter.island.tsx',
'/routes/directory/_counter.island.tsx',
]

paths.forEach((path) => {
it(`Should match ${path}`, () => {
const match = matchIslandComponentId(path)
expect(match).not.toBeNull()
expect(match![0]).toBe(path)
})
})
})

describe('not match', () => {
const paths = [
'/routes/directory/component.tsx',
'/routes/directory/foo$component.tsx',
'/routes/directory/foo_component.island.tsx',
'/routes/directory/component.island.tsx',
]

paths.forEach((path) => {
it(`Should not match ${path}`, () => {
const match = matchIslandComponentId(path)
expect(match).toBeNull()
})
})
})
})

describe('transformJsxTags', () => {
it('Should add component-wrapper and component-name attribute', () => {
Expand Down
14 changes: 13 additions & 1 deletion src/vite/island-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ function isComponentName(name: string) {
return /^[A-Z][A-Z0-9]*[a-z][A-Za-z0-9]*$/.test(name)
}

/**
* Matches when id is the filename of Island component
*
* @param id - The id to match
* @returns The result object if id is matched or null
*/
export function matchIslandComponentId(id: string) {
return id.match(
/\/islands\/.+?\.tsx$|\/routes\/(?:.*\/)?(?:\_[a-zA-Z0-9-]+\.island\.tsx$|\$[a-zA-Z0-9-]+\.tsx$)/
)
}

function addSSRCheck(funcName: string, componentName: string, componentExport?: string) {
const isSSR = memberExpression(
memberExpression(identifier('import'), identifier('meta')),
Expand Down Expand Up @@ -263,7 +275,7 @@ export function islandComponents(options?: IslandComponentsOptions): Plugin {
if (!matchIslandPath(id)) {
return
}
const match = id.match(/(\/islands\/.+?\.tsx$)|(\/routes\/.*\_[a-zA-Z0-9[-]+\.island\.tsx$)/)
const match = matchIslandComponentId(id)
if (match) {
const componentName = match[0]
const contents = await fs.readFile(id, 'utf-8')
Expand Down
10 changes: 8 additions & 2 deletions test-e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ test('test counter - island in the same directory', async ({ page }) => {
await page.goto('/directory')
await page.waitForSelector('body[data-client-loaded]')

await page.getByText('Count: 5').click()
await page.getByRole('button', { name: 'Increment' }).click({
await page.getByText('UnderScoreCount: 5').click()
await page.getByRole('button', { name: 'UnderScore Increment' }).click({
clickCount: 1,
})
await page.getByText('Count: 6').click()

await page.getByText('DollarCount: 5').click()
await page.getByRole('button', { name: 'Dollar Increment' }).click({
clickCount: 1,
})
await page.getByText('Count: 6').click()
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 @@ -343,7 +343,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="/routes/directory/_Counter.island.tsx" data-serialized-props="{&quot;initial&quot;:5}"><div id=""><p>Count: 5</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="/routes/directory/_Counter.island.tsx" data-serialized-props="{&quot;id&quot;:&quot;under-score&quot;,&quot;initial&quot;:5}"><div id="under-score"><p>UnderScoreCount: 5</p><button>UnderScore Increment</button></div></honox-island><honox-island component-name="/routes/directory/$counter.tsx" data-serialized-props="{&quot;id&quot;:&quot;dollar&quot;,&quot;initial&quot;:5}"><div id="dollar"><p>DollarCount: 5</p><button>Dollar Increment</button></div></honox-island><script type="module" async="" src="/app/client.ts"></script></body></html>'
)
})

Expand Down
2 changes: 1 addition & 1 deletion test-integration/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineConfig({
isIsland: (id) => {
const resolvedPath = path.resolve(root).replace(/\\/g, '\\\\')
const regexp = new RegExp(
`${resolvedPath}[\\\\/]app[^\\\\/]*[\\\\/]islands[\\\\/].+\.tsx?$|${resolvedPath}[\\\\/]app[^\\\\/]*[\\\\/]routes[\\\\/].+\.island\.tsx?$`
`${resolvedPath}[\\\\/]app[^\\\\/]*[\\\\/]islands[\\\\/].+\.tsx?$|${resolvedPath}[\\\\/]app[^\\\\/]*[\\\\/]routes[\\\\/].+\.island\.tsx?$|${resolvedPath}[\\\\/]app[^\\\\/]*[\\\\/]routes[\\\\/].*\\$.+\.tsx?$`
)
return regexp.test(path.resolve(id))
},
Expand Down

0 comments on commit 4ab674d

Please sign in to comment.