honox 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +650 -0
- package/dist/client/client.d.ts +11 -0
- package/dist/client/client.js +32 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +4 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +8 -0
- package/dist/factory/factory.d.ts +19 -0
- package/dist/factory/factory.js +11 -0
- package/dist/factory/index.d.ts +3 -0
- package/dist/factory/index.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +0 -0
- package/dist/server/components.d.ts +5 -0
- package/dist/server/components.js +10 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +6 -0
- package/dist/server/server.d.ts +36 -0
- package/dist/server/server.js +131 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +0 -0
- package/dist/utils/file.d.ts +7 -0
- package/dist/utils/file.js +77 -0
- package/dist/vite/index.d.ts +15 -0
- package/dist/vite/index.js +41 -0
- package/dist/vite/inject-has-islands.d.ts +5 -0
- package/dist/vite/inject-has-islands.js +52 -0
- package/dist/vite/inject-importing-islands.d.ts +5 -0
- package/dist/vite/inject-importing-islands.js +52 -0
- package/dist/vite/island-components.d.ts +6 -0
- package/dist/vite/island-components.js +129 -0
- package/dist/vite/remove-elements.d.ts +8 -0
- package/dist/vite/remove-elements.js +52 -0
- package/package.json +119 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 - present, Yusuke Wada and Hono contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
# HonoX
|
|
2
|
+
|
|
3
|
+
**HonoX** is a simple and fast - _supersonic_ - meta framework for creating full-stack websites or Web APIs - (formerly _[Sonik](https://github.com/sonikjs/sonik)_). It stands on the shoulders of giants; built on [Hono](https://hono.dev/), [Vite](https://hono.dev/), and UI libraries.
|
|
4
|
+
|
|
5
|
+
**Note**: _HonoX is currently in a "beta stage". Breaking changes are introduced without following semantic versioning._
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **File-based routing** - You can create a large application like Next.js.
|
|
10
|
+
- **Fast SSR** - Rendering is ultra-fast thanks to Hono.
|
|
11
|
+
- **BYOR** - You can bring your own renderer, not only one using hono/jsx.
|
|
12
|
+
- **Islands hydration** - If you want interactions, create an island. JavaScript is hydrated only for it.
|
|
13
|
+
- **Middleware** - It works as Hono, so you can use a lot of Hono's middleware.
|
|
14
|
+
|
|
15
|
+
## Get Started - Basic
|
|
16
|
+
|
|
17
|
+
Let's create a basic HonoX application using hono/jsx as a renderer. This application has no client JavaScript and renders JSX on the server side.
|
|
18
|
+
|
|
19
|
+
### Project Structure
|
|
20
|
+
|
|
21
|
+
Below is a typical project structure for a HonoX application.
|
|
22
|
+
|
|
23
|
+
```txt
|
|
24
|
+
.
|
|
25
|
+
├── app
|
|
26
|
+
│ ├── global.d.ts // global type definitions
|
|
27
|
+
│ ├── routes
|
|
28
|
+
│ │ ├── _404.tsx // not found page
|
|
29
|
+
│ │ ├── _error.tsx // error page
|
|
30
|
+
│ │ ├── _renderer.tsx // renderer definition
|
|
31
|
+
│ │ ├── about
|
|
32
|
+
│ │ │ └── [name].tsx // matches `/about/:name`
|
|
33
|
+
│ │ └── index.tsx // matches `/`
|
|
34
|
+
│ └── server.ts // server entry file
|
|
35
|
+
├── package.json
|
|
36
|
+
├── tsconfig.json
|
|
37
|
+
└── vite.config.ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `vite.config.ts`
|
|
41
|
+
|
|
42
|
+
The minimum Vite setup for development is as follows:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { defineConfig } from 'vite'
|
|
46
|
+
import honox from 'honox/vite'
|
|
47
|
+
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
plugins: [honox()],
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Server Entry File
|
|
54
|
+
|
|
55
|
+
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.
|
|
56
|
+
|
|
57
|
+
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`.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// app/server.ts
|
|
61
|
+
import { createApp } from 'honox/server'
|
|
62
|
+
import { showRoutes } from 'hono/dev'
|
|
63
|
+
|
|
64
|
+
const app = createApp()
|
|
65
|
+
|
|
66
|
+
showRoutes(app)
|
|
67
|
+
|
|
68
|
+
export default app
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Routes
|
|
72
|
+
|
|
73
|
+
There are three ways to define routes.
|
|
74
|
+
|
|
75
|
+
#### 1. `createRoute()`
|
|
76
|
+
|
|
77
|
+
Each route should return an array of `Handler | MiddlewareHandler`. `createRoute()` is a helper function to return it. You can write a route for a GET request with `default export`.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// `createRoute()` helps you create handlers
|
|
81
|
+
import { createRoute } from 'honox/factory'
|
|
82
|
+
|
|
83
|
+
export default createRoute((c) => {
|
|
84
|
+
return c.render(
|
|
85
|
+
<div>
|
|
86
|
+
<h1>Hello!</h1>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can also handle methods other than GET by `export` `POST`, `PUT`, and `DELETE`.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { createRoute } from 'honox/factory'
|
|
96
|
+
import { getCookie, setCookie } from 'hono/cookie'
|
|
97
|
+
|
|
98
|
+
export const POST = createRoute(async (c) => {
|
|
99
|
+
const { name } = await c.req.parseBody<{ name: string }>()
|
|
100
|
+
setCookie(c, 'name', name)
|
|
101
|
+
return c.redirect('/')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
export default createRoute((c) => {
|
|
105
|
+
const name = getCookie(c, 'name') ?? 'no name'
|
|
106
|
+
return c.render(
|
|
107
|
+
<div>
|
|
108
|
+
<h1>Hello, {name}!</h1>
|
|
109
|
+
<form method='POST'>
|
|
110
|
+
<input type='text' name='name' placeholder='name' />
|
|
111
|
+
<input type='submit' />
|
|
112
|
+
</form>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### 2. Using Hono instance
|
|
119
|
+
|
|
120
|
+
You can create API endpoints by exporting an instance of the Hono object.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// app/routes/about/index.ts
|
|
124
|
+
import { Hono } from 'hono'
|
|
125
|
+
|
|
126
|
+
const app = new Hono()
|
|
127
|
+
|
|
128
|
+
// matches `/about/:name`
|
|
129
|
+
app.get('/:name', (c) => {
|
|
130
|
+
const name = c.req.param('name')
|
|
131
|
+
return c.json({
|
|
132
|
+
'your name is': name,
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
export default app
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### 3. Just return JSX
|
|
140
|
+
|
|
141
|
+
Or simply, you can just return JSX.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
export default function Home() {
|
|
145
|
+
return <h1>Welcome!</h1>
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Renderer
|
|
150
|
+
|
|
151
|
+
Define your renderer - the middleware that does `c.setRender()` - by writing it in `_renderer.tsx`.
|
|
152
|
+
|
|
153
|
+
Before writing `_renderer.tsx`, write the Renderer type definition in `global.d.ts`.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
// app/global.d.ts
|
|
157
|
+
import type {} from 'hono'
|
|
158
|
+
|
|
159
|
+
type Head = {
|
|
160
|
+
title?: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
declare module 'hono' {
|
|
164
|
+
interface ContextRenderer {
|
|
165
|
+
(content: string | Promise<string>, head?: Head): Response | Promise<Response>
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The JSX Renderer middleware allows you to create a Renderer as follows:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// app/routes/_renderer.tsx
|
|
174
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
175
|
+
|
|
176
|
+
export default jsxRenderer(({ children, title }) => {
|
|
177
|
+
return (
|
|
178
|
+
<html lang='en'>
|
|
179
|
+
<head>
|
|
180
|
+
<meta charset='UTF-8' />
|
|
181
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
|
182
|
+
{title ? <title>{title}</title> : <></>}
|
|
183
|
+
</head>
|
|
184
|
+
<body>{children}</body>
|
|
185
|
+
</html>
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The `_renderer.tsx` is applied under each directory, and the `app/routes/posts/_renderer.tsx` is applied in `app/routes/posts/*`.
|
|
191
|
+
|
|
192
|
+
### Not Found page
|
|
193
|
+
|
|
194
|
+
You can write a custom Not Found page in `_404.tx`.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// app/routes/_404.tsx
|
|
198
|
+
import { NotFoundHandler } from 'hono'
|
|
199
|
+
|
|
200
|
+
const handler: NotFoundHandler = (c) => {
|
|
201
|
+
return c.render(<h1>Sorry, Not Found...</h1>)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default handler
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Error Page
|
|
208
|
+
|
|
209
|
+
You can write a custom Error page in `_error.tx`.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
// app/routes/_error.ts
|
|
213
|
+
import { ErrorHandler } from 'hono'
|
|
214
|
+
|
|
215
|
+
const handler: ErrorHandler = (e, c) => {
|
|
216
|
+
return c.render(<h1>Error! {e.message}</h1>)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export default handler
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Get Started - with Client
|
|
223
|
+
|
|
224
|
+
Let's create an application that includes a client side. Here, we will use hono/jsx/dom.
|
|
225
|
+
|
|
226
|
+
### Project Structure
|
|
227
|
+
|
|
228
|
+
The below is the project structure of a minimal application including a client side:
|
|
229
|
+
|
|
230
|
+
```txt
|
|
231
|
+
.
|
|
232
|
+
├── app
|
|
233
|
+
│ ├── client.ts // client entry file
|
|
234
|
+
│ ├── global.d.ts
|
|
235
|
+
│ ├── islands
|
|
236
|
+
│ │ └── counter.tsx // island component
|
|
237
|
+
│ ├── routes
|
|
238
|
+
│ │ ├── _renderer.tsx
|
|
239
|
+
│ │ └── index.tsx
|
|
240
|
+
│ └── server.ts
|
|
241
|
+
├── package.json
|
|
242
|
+
├── tsconfig.json
|
|
243
|
+
└── vite.config.ts
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Renderer
|
|
247
|
+
|
|
248
|
+
This is a `_renderer.tsx`, which will load the `/app/client.ts` entry file for the client. It will load the JavaScript file for the production according to the variable `import.meta.env.PROD`. And renders the inside of `HasIslands` if there are islands on that page.
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
// app/routes/_renderer.tsx
|
|
252
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
253
|
+
|
|
254
|
+
export default jsxRenderer(({ children }) => {
|
|
255
|
+
return (
|
|
256
|
+
<html lang='en'>
|
|
257
|
+
<head>
|
|
258
|
+
<meta charset='UTF-8' />
|
|
259
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
|
260
|
+
{import.meta.env.PROD ? (
|
|
261
|
+
<HasIslands>
|
|
262
|
+
<script type='module' src='/static/client.js'></script>
|
|
263
|
+
</HasIslands>
|
|
264
|
+
) : (
|
|
265
|
+
<script type='module' src='/app/client.ts'></script>
|
|
266
|
+
)}
|
|
267
|
+
</head>
|
|
268
|
+
<body>{children}</body>
|
|
269
|
+
</html>
|
|
270
|
+
)
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Client Entry File
|
|
275
|
+
|
|
276
|
+
A client side entry file should be in `app/client.ts`. Simply, write `createClient()`.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
// app/client.ts
|
|
280
|
+
import { createClient } from 'honox/client'
|
|
281
|
+
|
|
282
|
+
createClient()
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Interactions
|
|
286
|
+
|
|
287
|
+
Function components placed in `app/islands/*` are also sent to the client side. For example, you can write interactive component such as the following counter:
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
// app/islands/counter.tsx
|
|
291
|
+
import { useState } from 'hono/jsx'
|
|
292
|
+
|
|
293
|
+
export default function Counter() {
|
|
294
|
+
const [count, setCount] = useState(0)
|
|
295
|
+
return (
|
|
296
|
+
<div>
|
|
297
|
+
<p>Count: {count}</p>
|
|
298
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
When you load the component in a route file, it is rendered as Server-Side rendering and JavaScript is also send to the client-side.
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
// app/routes/index.tsx
|
|
308
|
+
import { createRoute } from 'honox/factory'
|
|
309
|
+
import Counter from '../islands/counter'
|
|
310
|
+
|
|
311
|
+
export default createRoute((c) => {
|
|
312
|
+
return c.render(
|
|
313
|
+
<div>
|
|
314
|
+
<h1>Hello</h1>
|
|
315
|
+
<Counter />
|
|
316
|
+
</div>
|
|
317
|
+
)
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## BYOR - Bring Your Own Renderer
|
|
322
|
+
|
|
323
|
+
You can bring your own renderer using a UI library like React, Preact, Solid, or others.
|
|
324
|
+
|
|
325
|
+
**Note**: We may not provide supports for the renderer you bring.
|
|
326
|
+
|
|
327
|
+
### React case
|
|
328
|
+
|
|
329
|
+
You can define a renderer using [`@hono/react-renderer`](https://github.com/honojs/middleware/tree/main/packages/react-renderer). Install the modules first.
|
|
330
|
+
|
|
331
|
+
```txt
|
|
332
|
+
npm i @hono/react-renderer react react-dom hono
|
|
333
|
+
npm i -D @types/react @types/react-dom
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Define the Props that the renderer will receive in `global.d.ts`.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
// global.d.ts
|
|
340
|
+
import '@hono/react-renderer'
|
|
341
|
+
|
|
342
|
+
declare module '@hono/react-renderer' {
|
|
343
|
+
interface Props {
|
|
344
|
+
title?: string
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The following is an example of `app/routes/renderer.tsx`.
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
// app/routes/_renderer.tsx
|
|
353
|
+
import { reactRenderer } from '@hono/react-renderer'
|
|
354
|
+
|
|
355
|
+
export default reactRenderer(({ children, title }) => {
|
|
356
|
+
return (
|
|
357
|
+
<html lang='en'>
|
|
358
|
+
<head>
|
|
359
|
+
<meta charSet='UTF-8' />
|
|
360
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
|
361
|
+
{import.meta.env.PROD ? (
|
|
362
|
+
<script type='module' src='/static/client.js'></script>
|
|
363
|
+
) : (
|
|
364
|
+
<script type='module' src='/app/client.ts'></script>
|
|
365
|
+
)}
|
|
366
|
+
{title ? <title>{title}</title> : ''}
|
|
367
|
+
</head>
|
|
368
|
+
<body>{children}</body>
|
|
369
|
+
</html>
|
|
370
|
+
)
|
|
371
|
+
})
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The `app/client.ts` will be like this.
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
// app/client.ts
|
|
378
|
+
import { createClient } from 'honox/client'
|
|
379
|
+
|
|
380
|
+
createClient({
|
|
381
|
+
hydrate: async (elem, root) => {
|
|
382
|
+
const { hydrateRoot } = await import('react-dom/client')
|
|
383
|
+
hydrateRoot(root, elem)
|
|
384
|
+
},
|
|
385
|
+
createElement: async (type: any, props: any, ...children: any[]) => {
|
|
386
|
+
const { createElement } = await import('react')
|
|
387
|
+
return createElement(type, props, ...children)
|
|
388
|
+
},
|
|
389
|
+
})
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Guides
|
|
393
|
+
|
|
394
|
+
### Nested Layouts
|
|
395
|
+
|
|
396
|
+
If you are using the JSX Renderer middleware, you can nest layouts using ` <Layout />`.
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
// app/routes/posts/_renderer.tsx
|
|
400
|
+
|
|
401
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
402
|
+
|
|
403
|
+
export default jsxRenderer(({ children, Layout }) => {
|
|
404
|
+
return (
|
|
405
|
+
<Layout>
|
|
406
|
+
<nav>Posts Menu</nav>
|
|
407
|
+
<div>{children}</div>
|
|
408
|
+
</Layout>
|
|
409
|
+
)
|
|
410
|
+
})
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Using Middleware
|
|
414
|
+
|
|
415
|
+
You can use Hono's Middleware in each root file with the same syntax as Hono. For example, to validate a value with the [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator), do the following:
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
import { z } from 'zod'
|
|
419
|
+
import { zValidator } from '@hono/zod-validator'
|
|
420
|
+
|
|
421
|
+
const schema = z.object({
|
|
422
|
+
name: z.string().max(10),
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
export const POST = createRoute(zValidator('form', schema), async (c) => {
|
|
426
|
+
const { name } = c.req.valid('form')
|
|
427
|
+
setCookie(c, 'name', name)
|
|
428
|
+
return c.redirect('/')
|
|
429
|
+
})
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Using Tailwind CSS
|
|
433
|
+
|
|
434
|
+
Given that HonoX is Vite-centric, if you wish to utilize [Tailwind CSS](https://tailwindcss.com/), simply adhere to the official instructions.
|
|
435
|
+
|
|
436
|
+
Prepare `tailwind.config.js` and `postcss.config.js`:
|
|
437
|
+
|
|
438
|
+
```js
|
|
439
|
+
// tailwind.config.js
|
|
440
|
+
export default {
|
|
441
|
+
content: ['./app/**/*.tsx'],
|
|
442
|
+
theme: {
|
|
443
|
+
extend: {},
|
|
444
|
+
},
|
|
445
|
+
plugins: [],
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```js
|
|
450
|
+
// postcss.config.js
|
|
451
|
+
export default {
|
|
452
|
+
plugins: {
|
|
453
|
+
tailwindcss: {},
|
|
454
|
+
autoprefixer: {},
|
|
455
|
+
},
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Write `app/style.css`:
|
|
460
|
+
|
|
461
|
+
```css
|
|
462
|
+
@tailwind base;
|
|
463
|
+
@tailwind components;
|
|
464
|
+
@tailwind utilities;
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Finally, import it in a renderer file:
|
|
468
|
+
|
|
469
|
+
```tsx
|
|
470
|
+
// app/routes/_renderer.tsx
|
|
471
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
472
|
+
|
|
473
|
+
export default jsxRenderer(({ children }) => {
|
|
474
|
+
return (
|
|
475
|
+
<html lang='en'>
|
|
476
|
+
<head>
|
|
477
|
+
<meta charset='UTF-8' />
|
|
478
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
|
479
|
+
{import.meta.env.PROD ? (
|
|
480
|
+
<link href='static/assets/style.css' rel='stylesheet' />
|
|
481
|
+
) : (
|
|
482
|
+
<link href='/app/style.css' rel='stylesheet' />
|
|
483
|
+
)}
|
|
484
|
+
</head>
|
|
485
|
+
<body>{children}</body>
|
|
486
|
+
</html>
|
|
487
|
+
)
|
|
488
|
+
})
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### MDX
|
|
492
|
+
|
|
493
|
+
MDX can also be used. Here is the `vite.config.ts`.
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
import devServer from '@hono/vite-dev-server'
|
|
497
|
+
import mdx from '@mdx-js/rollup'
|
|
498
|
+
import honox from 'honox/vite'
|
|
499
|
+
import remarkFrontmatter from 'remark-frontmatter'
|
|
500
|
+
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
|
|
501
|
+
import { defineConfig } from '../../node_modules/vite'
|
|
502
|
+
|
|
503
|
+
const entry = './app/server.ts'
|
|
504
|
+
|
|
505
|
+
export default defineConfig(() => {
|
|
506
|
+
return {
|
|
507
|
+
plugins: [
|
|
508
|
+
honox(),
|
|
509
|
+
devServer({ entry }),
|
|
510
|
+
mdx({
|
|
511
|
+
jsxImportSource: 'hono/jsx',
|
|
512
|
+
remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
|
|
513
|
+
}),
|
|
514
|
+
],
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Blog site can be created.
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
// app/routes/index.tsx
|
|
523
|
+
import type { Meta } from '../types'
|
|
524
|
+
|
|
525
|
+
export default function Top() {
|
|
526
|
+
const posts = import.meta.glob<{ frontmatter: Meta }>('./posts/*.mdx', {
|
|
527
|
+
eager: true,
|
|
528
|
+
})
|
|
529
|
+
return (
|
|
530
|
+
<div>
|
|
531
|
+
<h2>Posts</h2>
|
|
532
|
+
<ul class='article-list'>
|
|
533
|
+
{Object.entries(posts).map(([id, module]) => {
|
|
534
|
+
if (module.frontmatter) {
|
|
535
|
+
return (
|
|
536
|
+
<li>
|
|
537
|
+
<a href={`${id.replace(/\.mdx$/, '')}`}>{module.frontmatter.title}</a>
|
|
538
|
+
</li>
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
})}
|
|
542
|
+
</ul>
|
|
543
|
+
</div>
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Deployment
|
|
549
|
+
|
|
550
|
+
Since a HonoX instance is essentially a Hono instance, it can be deployed on any platform that Hono supports.
|
|
551
|
+
|
|
552
|
+
### Cloudflare Pages
|
|
553
|
+
|
|
554
|
+
Setup the `vite.config.ts`:
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
import { defineConfig } from 'vite'
|
|
558
|
+
import honox from 'honox/vite'
|
|
559
|
+
import pages from '@hono/vite-cloudflare-pages'
|
|
560
|
+
|
|
561
|
+
export default defineConfig({
|
|
562
|
+
plugins: [honox(), pages()],
|
|
563
|
+
})
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
If you want to include client side scripts and assets:
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
// vite.config.ts
|
|
570
|
+
import { defineConfig } from 'vite'
|
|
571
|
+
import honox from 'honox/vite'
|
|
572
|
+
import pages from '@hono/vite-cloudflare-pages'
|
|
573
|
+
|
|
574
|
+
export default defineConfig(({ mode }) => {
|
|
575
|
+
if (mode === 'client') {
|
|
576
|
+
return {
|
|
577
|
+
build: {
|
|
578
|
+
rollupOptions: {
|
|
579
|
+
input: ['./app/client.ts'],
|
|
580
|
+
output: {
|
|
581
|
+
entryFileNames: 'static/client.js',
|
|
582
|
+
chunkFileNames: 'static/assets/[name]-[hash].js',
|
|
583
|
+
assetFileNames: 'static/assets/[name].[ext]',
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
emptyOutDir: false,
|
|
587
|
+
copyPublicDir: false,
|
|
588
|
+
},
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
return {
|
|
592
|
+
plugins: [honox(), pages()],
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
})
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Build command (including a client):
|
|
599
|
+
|
|
600
|
+
```txt
|
|
601
|
+
vite build && vite build --mode client
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Deploy with the following commands after build. Ensure you have [Wrangler](https://developers.cloudflare.com/workers/wrangler/) installed:
|
|
605
|
+
|
|
606
|
+
```txt
|
|
607
|
+
wrangler pages deploy ./dist
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### SSG - Static Site Generation
|
|
611
|
+
|
|
612
|
+
Using Hono's SSG feature, you can generate static HTML for each route.
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
import { defineConfig } from 'vite'
|
|
616
|
+
import honox from 'honox/vite'
|
|
617
|
+
import ssg from '@hono/vite-ssg'
|
|
618
|
+
|
|
619
|
+
const entry = './app/server.ts'
|
|
620
|
+
|
|
621
|
+
export default defineConfig(() => {
|
|
622
|
+
return {
|
|
623
|
+
plugins: [honox(), devServer({ entry }), ssg({ entry })],
|
|
624
|
+
}
|
|
625
|
+
})
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
You can also deploy it to Cloudflare Pages.
|
|
629
|
+
|
|
630
|
+
```txt
|
|
631
|
+
wrangler pages deploy ./dist
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
## Examples
|
|
635
|
+
|
|
636
|
+
- [/example](./examples/)
|
|
637
|
+
- https://github.com/yusukebe/honox-examples
|
|
638
|
+
|
|
639
|
+
## Related projects
|
|
640
|
+
|
|
641
|
+
- [Hono](https://hono.dev/)
|
|
642
|
+
- [Vite](https://vitejs.dev/)
|
|
643
|
+
|
|
644
|
+
## Authors
|
|
645
|
+
|
|
646
|
+
- Yusuke Wada <https://github.com/yusukebe>
|
|
647
|
+
|
|
648
|
+
## License
|
|
649
|
+
|
|
650
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Hydrate, CreateElement } from '../types.js';
|
|
2
|
+
|
|
3
|
+
type ClientOptions = {
|
|
4
|
+
hydrate?: Hydrate;
|
|
5
|
+
createElement?: CreateElement;
|
|
6
|
+
ISLAND_FILES?: Record<string, () => Promise<unknown>>;
|
|
7
|
+
island_root?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const createClient: (options?: ClientOptions) => Promise<void>;
|
|
10
|
+
|
|
11
|
+
export { type ClientOptions, createClient };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as jsxFn } from "hono/jsx";
|
|
2
|
+
import { render } from "hono/jsx/dom";
|
|
3
|
+
import { COMPONENT_NAME, DATA_SERIALIZED_PROPS } from "../constants.js";
|
|
4
|
+
const createClient = async (options) => {
|
|
5
|
+
const FILES = options?.ISLAND_FILES ?? import.meta.glob("/app/islands/**/[a-zA-Z0-9[-]+.(tsx|ts)");
|
|
6
|
+
const root = options?.island_root ?? "/app/islands/";
|
|
7
|
+
const hydrateComponent = async () => {
|
|
8
|
+
const filePromises = Object.keys(FILES).map(async (filePath) => {
|
|
9
|
+
const componentName = filePath.replace(root, "");
|
|
10
|
+
const elements = document.querySelectorAll(`[${COMPONENT_NAME}="${componentName}"]`);
|
|
11
|
+
if (elements) {
|
|
12
|
+
const elementPromises = Array.from(elements).map(async (element) => {
|
|
13
|
+
const fileCallback = FILES[filePath];
|
|
14
|
+
const file = await fileCallback();
|
|
15
|
+
const Component = await file.default;
|
|
16
|
+
const serializedProps = element.attributes.getNamedItem(DATA_SERIALIZED_PROPS)?.value;
|
|
17
|
+
const props = JSON.parse(serializedProps ?? "{}");
|
|
18
|
+
const hydrate = options?.hydrate ?? render;
|
|
19
|
+
const createElement = options?.createElement ?? jsxFn;
|
|
20
|
+
const newElem = await createElement(Component, props);
|
|
21
|
+
await hydrate(newElem, element);
|
|
22
|
+
});
|
|
23
|
+
await Promise.all(elementPromises);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
await Promise.all(filePromises);
|
|
27
|
+
};
|
|
28
|
+
await hydrateComponent();
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
createClient
|
|
32
|
+
};
|