diff --git a/package.json b/package.json index 8240ab8..9b26d90 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test:unit": "vitest --run test/unit", "test:integration": "bun test:integration:api && bun test:integration:hono-jsx", "test:integration:hono-jsx": "vitest run -c ./test/hono-jsx/vitest.config.ts ./test/hono-jsx/integration.test.ts", + "test:integration:hono-jsx:watch": "vitest -c ./test/hono-jsx/vitest.config.ts ./test/hono-jsx/integration.test.ts", "test:integration:api": "vitest run -c ./test/api/vitest.config.ts ./test/api/integration.test.ts", "test:e2e": "playwright test -c ./test/hono-jsx/playwright.config.ts ./test/hono-jsx/e2e.test.ts", "typecheck": "tsc --noEmit", @@ -117,4 +118,4 @@ "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.9.6" } -} +} \ No newline at end of file diff --git a/src/server/server.ts b/src/server/server.ts index 79d6f6b..b183183 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -74,10 +74,13 @@ export const createApp = (options?: ServerOptions): Hono => eager: true, }) const rendererList = listByDirectory(RENDERER_FILE) - const applyRenderer = (rendererFile: string) => { + + const applyRenderer = (app: Hono, rendererFile: string) => { const renderer = RENDERER_FILE[rendererFile] - const path = pathToDirectoryPath(rendererFile).replace(rootRegExp, '') - app.all(`${filePathToPath(path)}*`, renderer.default) + const rendererDefault = renderer['default'] + if (rendererDefault) { + app.all('*', rendererDefault) + } } // Routes @@ -94,30 +97,26 @@ export const createApp = (options?: ServerOptions): Hono => const subApp = new Hono() // Renderer - let rendererFiles = rendererList[dir] - - if (rendererFiles) { - applyRenderer(rendererFiles[0]) - } - - if (!rendererFiles) { - const dirPaths = dir.split('/') - const getRendererPaths = (paths: string[]) => { - rendererFiles = rendererList[paths.join('/')] - if (!rendererFiles) { - paths.pop() - if (paths.length) { - getRendererPaths(paths) - } + let rendererPaths = rendererList[dir] ?? [] + + const getRendererPaths = (paths: string[]) => { + rendererPaths = rendererList[paths.join('/')] + if (!rendererPaths) { + paths.pop() + if (paths.length) { + getRendererPaths(paths) } - return rendererFiles - } - rendererFiles = getRendererPaths(dirPaths) - if (rendererFiles) { - applyRenderer(rendererFiles[0]) } + return rendererPaths ?? [] } + const dirPaths = dir.split('/') + rendererPaths = getRendererPaths(dirPaths) + rendererPaths.sort((a, b) => a.split('/').length - b.split('/').length) + rendererPaths.map((path) => { + applyRenderer(subApp, path) + }) + // Root path let rootPath = dir.replace(rootRegExp, '') rootPath = filePathToPath(rootPath) diff --git a/test/hono-jsx/app-nested/routes/nested/_renderer.tsx b/test/hono-jsx/app-nested/routes/nested/_renderer.tsx new file mode 100644 index 0000000..5e4bec3 --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/_renderer.tsx @@ -0,0 +1,14 @@ +import { jsxRenderer } from 'hono/jsx-renderer' + +export default jsxRenderer( + ({ children }) => { + return ( +
+ <>{children} +
+ ) + }, + { + docType: false, + } +) diff --git a/test/hono-jsx/app-nested/routes/nested/foo/_renderer.tsx b/test/hono-jsx/app-nested/routes/nested/foo/_renderer.tsx new file mode 100644 index 0000000..714c237 --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/foo/_renderer.tsx @@ -0,0 +1,17 @@ +import { jsxRenderer } from 'hono/jsx-renderer' + +export default jsxRenderer( + ({ children, Layout }) => { + return ( + + <> + + {children} + + + ) + }, + { + docType: false, + } +) diff --git a/test/hono-jsx/app-nested/routes/nested/foo/bar/_renderer.tsx b/test/hono-jsx/app-nested/routes/nested/foo/bar/_renderer.tsx new file mode 100644 index 0000000..a6694df --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/foo/bar/_renderer.tsx @@ -0,0 +1,17 @@ +import { jsxRenderer } from 'hono/jsx-renderer' + +export default jsxRenderer( + ({ children, Layout }) => { + return ( + + <> + + {children} + + + ) + }, + { + docType: false, + } +) diff --git a/test/hono-jsx/app-nested/routes/nested/foo/bar/baz/index.tsx b/test/hono-jsx/app-nested/routes/nested/foo/bar/baz/index.tsx new file mode 100644 index 0000000..bff8a11 --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/foo/bar/baz/index.tsx @@ -0,0 +1,3 @@ +export default function Baz() { + return

Baz

+} diff --git a/test/hono-jsx/app-nested/routes/nested/foo/bar/index.tsx b/test/hono-jsx/app-nested/routes/nested/foo/bar/index.tsx new file mode 100644 index 0000000..aa34810 --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/foo/bar/index.tsx @@ -0,0 +1,3 @@ +export default function Bar() { + return

Bar

+} diff --git a/test/hono-jsx/app-nested/routes/nested/foo/index.tsx b/test/hono-jsx/app-nested/routes/nested/foo/index.tsx new file mode 100644 index 0000000..278b227 --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/foo/index.tsx @@ -0,0 +1,3 @@ +export default function Foo() { + return

Foo

+} diff --git a/test/hono-jsx/app-nested/routes/nested/index.tsx b/test/hono-jsx/app-nested/routes/nested/index.tsx new file mode 100644 index 0000000..14900d1 --- /dev/null +++ b/test/hono-jsx/app-nested/routes/nested/index.tsx @@ -0,0 +1,3 @@ +export default function Nested() { + return

Nested

+} diff --git a/test/hono-jsx/integration.test.ts b/test/hono-jsx/integration.test.ts index fdb1122..cf64130 100644 --- a/test/hono-jsx/integration.test.ts +++ b/test/hono-jsx/integration.test.ts @@ -132,110 +132,150 @@ describe('Basic', () => { expect(res.status).toBe(500) expect(await res.text()).toBe('Internal Server Error') }) +}) - describe('With preserved', () => { - const ROUTES = import.meta.glob('./app/routes/**/[a-z[-][a-z-_[]*.(tsx|ts)', { - eager: true, - }) +describe('With preserved', () => { + const ROUTES = import.meta.glob('./app/routes/**/[a-z[-][a-z-_[]*.(tsx|ts)', { + eager: true, + }) - const RENDERER = import.meta.glob('./app/routes/**/_renderer.tsx', { - eager: true, - }) + const RENDERER = import.meta.glob('./app/routes/**/_renderer.tsx', { + eager: true, + }) - const NOT_FOUND = import.meta.glob('./app/routes/_404.tsx', { - eager: true, - }) + const NOT_FOUND = import.meta.glob('./app/routes/_404.tsx', { + eager: true, + }) - const ERROR = import.meta.glob('./app/routes/_error.tsx', { - eager: true, - }) + const ERROR = import.meta.glob('./app/routes/_error.tsx', { + eager: true, + }) - const app = createApp({ - root: './app/routes', - ROUTES: ROUTES as any, - RENDERER: RENDERER as any, - NOT_FOUND: NOT_FOUND as any, - ERROR: ERROR as any, - }) + const app = createApp({ + root: './app/routes', + ROUTES: ROUTES as any, + RENDERER: RENDERER as any, + NOT_FOUND: NOT_FOUND as any, + ERROR: ERROR as any, + }) - it('Should return 200 response - /', async () => { - const res = await app.request('/') - expect(res.status).toBe(200) - expect(await res.text()).toBe( - 'This is a title

Hello

' - ) - }) + it('Should return 200 response - /', async () => { + const res = await app.request('/') + expect(res.status).toBe(200) + expect(await res.text()).toBe( + 'This is a title

Hello

' + ) + }) - it('Should return 404 response - /foo', async () => { - const res = await app.request('/foo') - expect(res.status).toBe(404) - expect(await res.text()).toBe( - 'Not Found

Not Found

' - ) - }) + it('Should return 404 response - /foo', async () => { + const res = await app.request('/foo') + expect(res.status).toBe(404) + expect(await res.text()).toBe( + 'Not Found

Not Found

' + ) + }) - it('Should return 200 response /about/me', async () => { - const res = await app.request('/about/me') - expect(res.status).toBe(200) - // hono/jsx escape a single quote to ' - expect(await res.text()).toBe( - 'me

It's me

My name is me' - ) - }) + it('Should return 200 response /about/me', async () => { + const res = await app.request('/about/me') + expect(res.status).toBe(200) + // hono/jsx escape a single quote to ' + expect(await res.text()).toBe( + 'me

It's me

My name is me' + ) + }) - it('Should return 200 response /about/me/address', async () => { - const res = await app.request('/about/me/address') - expect(res.status).toBe(200) - // hono/jsx escape a single quote to ' - expect(await res.text()).toBe( - 'me's address

About

me's address
' - ) - }) + it('Should return 200 response /about/me/address', async () => { + const res = await app.request('/about/me/address') + expect(res.status).toBe(200) + // hono/jsx escape a single quote to ' + expect(await res.text()).toBe( + 'me's address

About

me's address
' + ) + }) - it('Should return 200 response /interaction', async () => { - const res = await app.request('/interaction') - expect(res.status).toBe(200) - // hono/jsx escape a single quote to ' - expect(await res.text()).toBe( - '

Count: 5

' - ) - }) + it('Should return 200 response /interaction', async () => { + const res = await app.request('/interaction') + expect(res.status).toBe(200) + // hono/jsx escape a single quote to ' + expect(await res.text()).toBe( + '

Count: 5

' + ) + }) - it('Should return 500 response /throw_error', async () => { - const res = await app.request('/throw_error') - expect(res.status).toBe(500) - expect(await res.text()).toBe( - 'Internal Server Error

Custom Error Message: Foo

' - ) - }) + it('Should return 500 response /throw_error', async () => { + const res = await app.request('/throw_error') + expect(res.status).toBe(500) + expect(await res.text()).toBe( + 'Internal Server Error

Custom Error Message: Foo

' + ) }) +}) - describe('API', () => { - const ROUES = import.meta.glob('./app/routes//**/[a-z[-][a-z-_[]*.(tsx|ts)', { - eager: true, - }) +describe('API', () => { + const ROUES = import.meta.glob('./app/routes//**/[a-z[-][a-z-_[]*.(tsx|ts)', { + eager: true, + }) - const app = createApp({ - root: './app/routes', - ROUTES: ROUES as any, - }) + const app = createApp({ + root: './app/routes', + ROUTES: ROUES as any, + }) - it('Should return 200 response - /api', async () => { - const res = await app.request('/api') - expect(res.status).toBe(200) - expect(res.headers.get('X-Custom')).toBe('Hello') - expect(await res.json()).toEqual({ foo: 'bar' }) - }) + it('Should return 200 response - /api', async () => { + const res = await app.request('/api') + expect(res.status).toBe(200) + expect(res.headers.get('X-Custom')).toBe('Hello') + expect(await res.json()).toEqual({ foo: 'bar' }) + }) - it('Should return 200 response - POST /api', async () => { - const res = await app.request('/api', { - method: 'POST', - }) - expect(res.status).toBe(201) - expect(await res.json()).toEqual({ - ok: true, - message: 'created', - }) + it('Should return 200 response - POST /api', async () => { + const res = await app.request('/api', { + method: 'POST', }) + expect(res.status).toBe(201) + expect(await res.json()).toEqual({ + ok: true, + message: 'created', + }) + }) +}) + +describe('Nested Layouts', () => { + const ROUTES = import.meta.glob('./app-nested/routes/**/[a-z[-][a-z-_[]*.(tsx|ts)', { + eager: true, + }) + + const RENDERER = import.meta.glob('./app-nested/routes/**/_renderer.tsx', { + eager: true, + }) + + const app = createApp({ + root: './app-nested/routes', + ROUTES: ROUTES as any, + RENDERER: RENDERER as any, + }) + + it('Should return 200 response - /nested', async () => { + const res = await app.request('/nested') + expect(res.status).toBe(200) + expect(await res.text()).toBe('

Nested

') + }) + + it('Should return 200 response - /nested/foo', async () => { + const res = await app.request('/nested/foo') + expect(res.status).toBe(200) + expect(await res.text()).toBe('

Foo

') + }) + + it('Should return 200 response - /nested/foo/bar', async () => { + const res = await app.request('/nested/foo/bar') + expect(res.status).toBe(200) + expect(await res.text()).toBe('

Bar

') + }) + + it('Should return 200 response - /nested/foo/bar/baz', async () => { + const res = await app.request('/nested/foo/bar/baz') + expect(res.status).toBe(200) + expect(await res.text()).toBe('

Baz

') }) })