Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add trailingSlash option #125

Merged
merged 1 commit into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default defineConfig({

A server entry file is required. The file is should be placed at `app/server.ts`. This file is first called by the Vite during the development or build phase.

In the entry file, simply initialize your app using the `createApp()` function. `app` will be an instance of Hono, so you can use Hono's middleware and the `showRoutes()`in`hono/dev`.
In the entry file, simply initialize your app using the `createApp()` function. `app` will be an instance of Hono, so you can use Hono's middleware and the `showRoutes()` in `hono/dev`.

```ts
// app/server.ts
Expand Down Expand Up @@ -545,6 +545,24 @@ export default createRoute(zValidator('form', schema), ...<more-middleware>)

Note that is some scenarios, auto-complete for the request body within the route may be lost depending on how the middleware was written.

### Trailing Slash

By default, trailing slashes are removed if the root file is an index file such as `index.tsx` or `index.mdx`.
However, if you set the `trailingSlash` option to `true` as the following, the trailing slash is not removed.

```ts
import { createApp } from 'honox/server'

const app = createApp({
trailingSlash: true,
})
```

Like the followings:

- `trailingSlash` is `false` (default): `app/routes/path/index.mdx` => `/path`
- `trailingSlash` is `true`: `app/routes/path/index.mdx` => `/path/`

### Using Tailwind CSS

Given that HonoX is Vite-centric, if you wish to utilize [Tailwind CSS](https://tailwindcss.com/), simply adhere to the official instructions.
Expand Down
11 changes: 11 additions & 0 deletions src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ type BaseServerOptions<E extends Env = Env> = {
root: string
app?: Hono<E>
init?: InitFunction<E>
/**
* Appends a trailing slash to URL if the route file is an index file, e.g., `index.tsx` or `index.mdx`.
* @default false
*/
trailingSlash?: boolean
}

export type ServerOptions<E extends Env = Env> = Partial<BaseServerOptions<E>>
Expand All @@ -53,6 +58,7 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
const root = options.root
const rootRegExp = new RegExp(`^${root}`)
const app = options.app ?? new Hono()
const trailingSlash = options.trailingSlash ?? false

if (options.init) {
options.init(app)
Expand Down Expand Up @@ -178,6 +184,11 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
applyNotFound(subApp, dir, notFoundMap)
// Error
applyError(subApp, dir, errorMap)

if (trailingSlash) {
rootPath = /\/$/.test(rootPath) ? rootPath : rootPath + '/'
}

app.route(rootPath, subApp)
}
}
Expand Down
1 change: 1 addition & 0 deletions src/server/with-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
root: options?.root ?? '/app/routes',
app: options?.app,
init: options?.init,
trailingSlash: options?.trailingSlash,
NOT_FOUND:
options?.NOT_FOUND ??
import.meta.glob('/app/routes/**/_404.(ts|tsx)', {
Expand Down
3 changes: 3 additions & 0 deletions test/hono-jsx/app-nested/routes/top.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Top() {
return <h1>Top</h1>
}
33 changes: 33 additions & 0 deletions test/hono-jsx/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,36 @@ describe('Island Components with Preserved Files', () => {
)
})
})

describe('Trailing Slash', () => {
const ROUTES = import.meta.glob('./app-nested/routes/**/[a-z[-][a-z-_[]*.(tsx|ts)', {
eager: true,
})

const app = createApp({
root: './app-nested/routes',
ROUTES: ROUTES as any,
trailingSlash: true,
})

const paths = ['/nested', '/nested/foo', '/nested/foo/bar', '/nested/foo/bar/baz']
for (const path of paths) {
it(`Should return 404 response - ${path}`, async () => {
const res = await app.request(path)
expect(res.status).toBe(404)
})
it(`Should return 200 response - ${path}/`, async () => {
const res = await app.request(`${path}/`)
expect(res.status).toBe(200)
})
}

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