diff --git a/README.md b/README.md index 4322b79..fe31fb0 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,20 @@ # HonoX -HonoX is a simple and fast meta framework for creating Web APIs and websites with Server-Side Rendering - (formerly _Sonik_). It stands on the shoulders of giants; built on [Hono](https://hono.dev/), [Vite](https://hono.dev/), and UI libraries. +**HonoX** is a simple and fast meta framework for creating websites and Web APIs with Server-Side Rendering - (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. **Note**: _HonoX is currently in a "beta stage". There will be breaking changes without any announcement. Don't use it in production. However, feel free to try it in your hobby project and give us your feedback!_ ## Features -- **File-based routing** - Now, you can create a large app by separating concerns. -- **Fast SSR** - Supports only Server-Side Rendering. Rendering is ultra-fast thanks to Hono. -- **No JavaScript** - By default, there's no need for JavaScript. Nothing loads. -- **Any UI library** - You can use any UI library besides hono/jsx, such as React, Preact, etc. -- **Island hydration** - If you want interactions, create an island. JavaScript is hydrated only for that island. -- **Easy API creation** - You can create APIs using Hono's syntax. -- **Middleware** - It works just like Hono, so you can use many of Hono's middleware. +- **File-based routing** - You can create a large app by separating concerns. +- **Fast SSR** - Rendering is ultra-fast thanks to Hono. +- **BYOR** - You can bring your own renderer, not only one using hono/jsx. +- **Island hydration** - If you want interactions, create an island. JavaScript is hydrated only for it. +- **Middleware** - It works as Hono, so you can use a lot of Hono's middleware. ## Get Started - Basic -Let's create a basic HonoX application using hono/jsx as a renderer. +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. ### Project Structure @@ -56,7 +54,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 utilize 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 @@ -72,7 +70,11 @@ export default app ### Routes -Each route should return an array of `Handler | MiddlewareHandler`. You can write a route for a GET request with `default export`. +There are three ways to define routes. + +#### 1. `createRoute()` + +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`. ```tsx // `createRoute()` helps you create handlers @@ -113,7 +115,7 @@ export default createRoute((c) => { }) ``` -#### Using Hono instance +#### 2. Using Hono instance You can create API endpoints by exporting an instance of the Hono object. @@ -134,9 +136,19 @@ app.get('/:name', (c) => { export default app ``` +#### 3. Just return JSX + +Or simply, you can just return JSX. + +```tsx +export default function Home() { + return

Welcome!

+} +``` + ### Renderer -You can define Hono's Renderer - i.e. the middleware that does `c.setRender()` - by writing it in `_renderer.tsx`. +Define your renderer - the middleware that does `c.setRender()` - by writing it in `_renderer.tsx`. Before writing `_renderer.tsx`, write the Renderer type definition in `global.d.ts`. @@ -155,7 +167,7 @@ declare module 'hono' { } ``` -The JSX Renderer middleware allows you to define a Renderer as follows: +The JSX Renderer middleware allows you to create a Renderer as follows: ```tsx // app/routes/_renderer.tsx @@ -209,7 +221,7 @@ export default handler ## Get Started - with Client -Let's create an application that includes a client side. Here, we will use React as the UI library. +Let's create an application that includes a client side. Here, we will use hono/jsx/dom. ### Project Structure @@ -231,7 +243,86 @@ The below is the project structure of a minimal application including a client s └── vite.config.ts ``` -### React Renderer +### Renderer + +This is a `_renderer.tsx` which will load the `/app/client.ts` entry file for the client. It can also load the JavaScript file for the production according to the variable `import.meta.env.PROD`. + +```tsx +// app/routes/_renderer.tsx +import { jsxRenderer } from 'hono/jsx-renderer' + +export default jsxRenderer(({ children }) => { + return ( + + + + + {import.meta.env.PROD ? ( + + ) : ( + + )} + + {children} + + ) +}) +``` + +### Client Entry File + +A client side entry file should be in `app/client.ts`. Simply, write `createClient()`. + +```ts +// app/client.ts +import { createClient } from 'honox/client' + +createClient() +``` + +### Interactions + +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: + +```tsx +// app/islands/counter.tsx +import { useState } from 'hono/jsx' + +export default function Counter() { + const [count, setCount] = useState(0) + return ( +
+

Count: {count}

+ +
+ ) +} +``` + +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. + +```tsx +// app/routes/index.tsx +import { createRoute } from 'honox/factory' +import Counter from '../islands/counter' + +export default createRoute((c) => { + return c.render( +
+

Hello

+ +
+ ) +}) +``` + +## BYOR - Bring Your Own Renderer + +You can bring your own renderer using a UI library like React, Preact, Solid, or others. + +**Note**: We cannot provide technical support for the renderer you bring. + +### React case You can define a renderer using [`@hono/react-renderer`](https://github.com/honojs/middleware/tree/main/packages/react-renderer). Install the modules first. @@ -240,7 +331,7 @@ npm i @hono/react-renderer react react-dom hono npm i -D @types/react @types/react-dom ``` -Next, define the Props that the renderer will receive in `global.d.ts`. +Define the Props that the renderer will receive in `global.d.ts`. ```ts // global.d.ts @@ -253,7 +344,7 @@ declare module '@hono/react-renderer' { } ``` -Write `_renderer.tsx`, which will load the `/app/client.ts` entry file for the client. It also loads the JavaScript file for the production according to the variable `import.meta.env.PROD`. +The following is an example of `app/routes/renderer.tsx`. ```tsx // app/routes/_renderer.tsx @@ -282,9 +373,7 @@ export default reactRenderer(({ children, title }) => { }) ``` -### Client - -A client side entry file should be in `app/client.ts`. You can specify each option in `createClient()` for each UI library. +The `app/client.ts` will be like this. ```ts // app/client.ts @@ -302,44 +391,7 @@ createClient({ }) ``` -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: - -```tsx -// app/islands/counter.tsx -import { useState } from 'react' - -export default function Counter() { - const [count, setCount] = useState(0) - const increment = () => setCount(count + 1) - return ( -
-

- Count: {count} -

- -
- ) -} -``` - -When you load the component in a route file, it is rendered as Server-Side rendering and JavaScript is also delivered to the client-side. - -```tsx -// app/routes/index.tsx -import { createRoute } from 'honox/factory' -import Counter from '../islands/counter' - -export default createRoute((c) => { - return c.render( -
-

Hello

- -
- ) -}) -``` - -## Guide +## Guides ### Using Middleware @@ -408,13 +460,9 @@ export default jsxRenderer(({ children }) => { {import.meta.env.PROD ? ( - <> - - + ) : ( - <> - - + )} {children} @@ -423,6 +471,63 @@ export default jsxRenderer(({ children }) => { }) ``` +### MDX + +MDX can also be used. Here is the `vite.config.ts`. + +```ts +import devServer from '@hono/vite-dev-server' +import mdx from '@mdx-js/rollup' +import honox from 'honox/vite' +import remarkFrontmatter from 'remark-frontmatter' +import remarkMdxFrontmatter from 'remark-mdx-frontmatter' +import { defineConfig } from '../../node_modules/vite' + +const entry = './app/server.ts' + +export default defineConfig(() => { + return { + plugins: [ + honox(), + devServer({ entry }), + mdx({ + jsxImportSource: 'hono/jsx', + remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter], + }), + ], + } +}) +``` + +Blog site can be created. + +```tsx +// app/routes/index.tsx +import type { Meta } from '../types' + +export default function Top() { + const posts = import.meta.glob<{ frontmatter: Meta }>('./posts/*.mdx', { + eager: true, + }) + return ( +
+

Posts

+ +
+ ) +} +``` + ## Deployment Since a HonoX instance is essentially a Hono instance, it can be deployed on any platform that Hono supports. @@ -434,17 +539,10 @@ Setup the `vite.config.ts`: ```ts import { defineConfig } from 'vite' import honox from 'honox/vite' +import pages from '@hono/vite-cloudflare-pages' export default defineConfig({ - build: { - minify: true, - rollupOptions: { - output: { - entryFileNames: '_worker.js', - }, - }, - }, - plugins: [honox()], + plugins: [honox(), pages()], }) ``` @@ -454,61 +552,63 @@ If you want to include client side scripts and assets: // vite.config.ts import { defineConfig } from 'vite' import honox from 'honox/vite' +import pages from '@hono/vite-cloudflare-pages' export default defineConfig(({ mode }) => { if (mode === 'client') { return { build: { rollupOptions: { - input: ['./app/style.css'], + input: ['./app/client.ts'], output: { entryFileNames: 'static/client.js', chunkFileNames: 'static/assets/[name]-[hash].js', - assetFileNames: `static/assets/[name].[ext]`, + assetFileNames: 'static/assets/[name].[ext]', }, }, emptyOutDir: false, + copyPublicDir: false, }, } } else { return { - build: { - minify: true, - rollupOptions: { - output: { - entryFileNames: '_worker.js', - }, - }, - }, - plugins: [honox()], + plugins: [honox(), build()], } } }) ``` -To serve static files for Cloudflare Pages, edit `app/server.ts`: +Build command (including a client): -```ts -// app/server.ts -import { createApp } from 'honox/server' -import { serveStatic } from 'hono/cloudflare-pages' +```txt +vite build && vite build --mode client +``` -const app = createApp({ - init: (app) => { - app.get('/static/*', serveStatic()) - }, -}) +Deploy with the following commands after build. Ensure you have [Wrangler](https://developers.cloudflare.com/workers/wrangler/) installed: -export default app +```txt +wrangler pages deploy ./dist ``` -Build command (including a client): +### SSG - Static Site Generation -```txt -vite build && vite build --mode client +Using Hono's SSG feature, you can generate static HTML for each route. + +```ts +import { defineConfig } from 'vite' +import honox from 'honox/vite' +import ssg from '@hono/vite-ssg' + +const entry = './app/server.ts' + +export default defineConfig(() => { + return { + plugins: [honox(), devServer({ entry }), ssg({ entry })], + } +}) ``` -Deploy with the following commands after build. Ensure you have [Wrangler](https://developers.cloudflare.com/workers/wrangler/) installed: +You can also deploy it to Cloudflare Pages. ```txt wrangler pages deploy ./dist @@ -516,6 +616,7 @@ wrangler pages deploy ./dist ## Examples +- [/example](./examples/) - https://github.com/yusukebe/honox-examples ## Related projects