Going to full-stack.
Hono v4.0.0 is out! This major update includes some breaking changes and the addition of three major features.
- Static Site Generation
- Client Components
- File-based Routing
So Hono is going to full-stack. Let's take a look at the three features.
1. Static Site Generation
We introduce SSG Helper. With it you can generate static pages of your Hono applications.
To use this, create a separate file build.ts
from the application and call the toSSG()
function in it.
import fs from 'node:fs/promises'
import { toSSG } from 'hono/ssg'
import app from './src/index'
toSSG(app, fs)
There are adapters for Bun and Deno, so you can write shorter for Bun, for example.
import { toSSG } from 'hono/bun'
import app from './src/index'
toSSG(app)
And, just run it.
bun ./build.ts
Then HTML is generated.
$ ls static
about.html index.html
You can easily deploy this page to Cloudflare Pages, etc.
$ wrangler pages deploy static
With Vite
We have created a plugin @hono/vite-ssg
for Vite. By using this, you will be able to develop and build a static sites with just the vite
command.
The configuration is the following:
import build from '@hono/vite-ssg'
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
build(),
devServer({
entry: 'src/index.tsx'
})
]
})
If you want to develope, just run the command:
vite
If you want to build, just run the command:
vite build
In combination with the deployment mentioned above to Cloudflare Pages, you can develop, SSG build, and deploy non-stop. And each of them is extremely fast (the video is 2x faster).
2. Client Components
hono/jsx
was originally designed to run server-side as an alternative to template engines such as Mustache. Server-side JSX is an interesting experiment, creating a new stack to combine with HTMX and Alpine.js. But that's not all.
Now, hono/jsx
runs on the client as well. We call it hono/jsx/dom
or Client Components.
The exact same code as React runs with Hono's JSX.
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
function App() {
return (
<html>
<body>
<Counter />
</body>
</html>
)
}
const root = document.getElementById('root')
render(<App />, root)
The Hooks listed below are also implemented and you can create Client Components just like in React.
- useContext
- useEffect
- useState
- useCallback
- use
- startTransition
- useDeferredValue
- useMemo
- useLayoutEffect
- Memo
- isValidElement
startViewTransition
family
In addition, the original APIs, startViewTransition
family make the View Transition API easy to use.
import { useState, startViewTransition } from 'hono/jsx'
import { Style, css, keyframes } from 'hono/css'
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
const App = () => {
const [showTitleImage, setShowTitleImage] = useState(false)
return (
<>
<button onClick={() => startViewTransition(() => setShowTitleImage((state) => !state))}>Click!</button>
<div>
{!showTitleImage ? (
<img src="https://hono.dev/images/logo.png" />
) : (
<div
class={css`
animation: ${fadeIn} 1s;
background: url('https://hono.dev/images/logo-large.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 500px;
height: 200px;
`}
/>
)}
</div>
</>
)
}
You can easily create the animation.
Ultra-small
The hono/jsx/dom
is fast and ultra-small. It has a smaller JSX runtime dedicated to the DOM in addition to the common server and client ones. Just specify hono/jsx/dom
instead of hono/jsx
in tsconfig.json
.
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
The above counter example is 2.8KB with Brotli compression.
In comparison, React is 47.3 KB for the same thing.
3. File-based Routing
Last is File-based Routing. This is not included in the hono package, but is provided in a separate package.
It is named HonoX.
HonoX
HonoX has the following features.
- File-based routing - You can create a large application like Next.js.
- Fast SSR - Rendering is ultra-fast thanks to Hono.
- BYOR - You can bring your own renderer, not only one using hono/jsx.
- Islands 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.
You can try it now. One of create-hono's starter templates named "x-base" uses HonoX.
For detailed usage, please see the following HonoX repository.
https://github.com/honojs/honox
The core is still tiny
The addition of this feature has no impact on the core. "Hello World" in hono/tiny is still small, only 12KB minified.
Other new features
- feat(base): Set Default Path to
'*'
forapp.use()
- #1753 - feat(hono-base):
app.on
supports multiple paths - #1923 - feat(css): Introduce pseudo global selector and class name based extend syntax - #1928
- feat(jsx-renderer): Nested Layouts via Renderer - #1945
- feat!: validator throws error rathar than return
c.json()
- #2021 - feat: introduce Accepts Helper - #2001
- feat(serve-static):
mimes
option for serve-static - #2094 - feat!(validator): supports transformation - #2130
Breaking Changes
There are several breaking changes. Please see the Migration Guide below.
https://github.com/honojs/hono/blob/main/docs/MIGRATION.md
Thanks
Thanks to all contributors. Great job on all the hard work!
All Updates
- feat(base): Set Default Path to '*' for app.use() by @watany-dev in #1753
- feat: Introduce jsx/dom by @usualoma in #1917
- ci: enables CI in the v4 branch by @yusukebe in #1933
- feat(hono-base):
app.on
supports multiple paths by @yusukebe in #1923 - feat!: remove deprecated features by @yusukebe in #1934
- fix(jsx/dom): fix cleanup for deps by @usualoma in #1936
- refactor(jsx/dom): replace existing content by render() by @usualoma in #1938
- feat(css): Introduce pseudo global selector and class name based extend syntax by @usualoma in #1928
- feat: Introducing a New SSG Adaptor/Helper by @watany-dev in #1904
- chore(helper): Add experimental flag the SSG features by @watany-dev in #1967
- refactor: remove unnecessary
import
by @yusukebe in #1969 - refactor: remove and fix comments by @yusukebe in #1970
- ci: remove lagon runtime tests and other lagon things by @yusukebe in #1971
- refactor(ssg): SSG works without
node:path
by @nakasyou in #1965 - feat(factory): remove
deprecated
fromFactory
andcreateHandlers
by @yusukebe in #1979 - fix(ssg): fix path parser bug & refactor code by @EdamAme-x in #1976
- feat(ssg): Implement Dynamic File Extension on MIME Type and Enhanced Response Handling by @watany-dev in #1968
- feat(jsx/dom): rewrite renderer to use virtual tree by @usualoma in #1981
- refactor: faster for loop by @EdamAme-x in #1989
- feat!(cloudflare-workers): make
manifest
required by @yusukebe in #1984 - chore: enables lint and format for
.tsx
by @yusukebe in #1994 - feat(jsx/dom): provide jsx-runtime and jsx-dev-runtime via jsx/dom by @usualoma in #1986
- fix(types): correct
c.get()
inference by @yusukebe in #1995 - feat(jsx/dom): startTransition() and useTransition() by @usualoma in #1996
- refactor(jsx): export components and hook function from top level 'hono/jsx' by @usualoma in #1997
- feat(ssg): Ignore Dynamic Route by @watany-dev in #1990
- feat: Added
ssgParams
middleware by @nakasyou in #1960 - fix(ssg): fixed
isDynamicRoute
andssgParams
matter by @yusukebe in #2006 - feat(jsx/dom): support createContext and useContext in jsx/dom by @usualoma in #1999
- refactor(jsx/dom): make
useTransition()
handling more simple by @usualoma in #2003 - chore(package.json): specify the config in
test:deno
by @yusukebe in #2014 - chore(runtime-test): add
deno.lock
by @yusukebe in #2015 - fix(jsx/dom): find "insertBefore" node from next node list if not found by @usualoma in #2017
- feat!: validator throws error rathar than
return c.json()
by @yusukebe in #2021 - refactor(ssg): Removal of Libraries Dependent on Node.js by @watany-dev in #2012
- feat!(deno): move middleware to helper by @watany-dev in #2027
- fix(SSG): Correct extension of the file output by @watany-dev in #2029
- feat(jsx/dom): Introduce startViewTransition() by @usualoma in #2020
- fix(ssg): Remove ArrayBuffer from FileSystemModule's writeFile by @watany-dev in #2032
- refactor(jsx/dom): invoke update() in microtask by @usualoma in #2036
- feat: introduce Accepts Helper by @sor4chi in #2001
- feat: improve
ssgParams
flexibility by @sor4chi in #2024 - refactor(types): name them
BlankSchema
orBlankInput
by @yusukebe in #2040 - feat(deno): export accpet helper for deno by @yusukebe in #2041
- docs: Add JSDoc by @nabeken5 in #1916
- fix!(types): Fix context type when chaining routes with middlewares by @agatan in #2046
- refactor: rename accept to accepts by @EdamAme-x in #2063
- fix: use DOCTYPE by default by @EdamAme-x in #2064
- fix(ssg): Uniformly Convert Paths Ending with Slash to 'index.ext' Format by @watany-dev in #2056
- feat: Introduce
useViewTransition()
hook andviewTransition()
helper by @usualoma in #2053 - feat(ssg): support Improve Hook Handling by @watany-dev in #2054
- fix: await generate hook by @watany-dev in #2074
- fix:(ssg): make 'files' mandatory in ToSSGResult by @watany-dev in #2071
- feat(types): explicitly specify statusCode by @EdamAme-x in #2073
- chore: vitest test.pool to 'forks' by @watany-dev in #2098
- goodbye: lagon by @EdamAme-x in #2087
- refactor: faster for loop in jsx/dom by @EdamAme-x in #2092
- refactor: "if" on one line and deletion of unneeded variables by @EdamAme-x in #2093
- fix: add typesVersions of accepts helper by @sor4chi in #2096
- chore: use Bun as a package manager by @yusukebe in #2105
- docs(contributing): add
Installing dependencies
by @yusukebe in #2113 - feat(serve-static):
mimes
option for serve-static by @ryuapp in #2094 - feat(ssg): introduce
disableSSG
andonlySSG
by @yusukebe in #2104 - feat!(mime): reduce default mimes by @ryuapp in #2119
- feat(types): better
c.var
type by @Kyiro in #2121 - fix(jsx-renderer): correct nested layouts by @yusukebe in #2128
- feat!(validator): supports transformation by @yusukebe in #2130
- feat(jsx/dom): more react staff by @usualoma in #2132
- refactor(jsx): Remove unused HONO_COMPONENT feature by @usualoma in #2139
- fix(html): Remove circular dependencies in
hono/html
by @javascripter in #2143 - Merge main into v4 by @usualoma in #2145
- feat(jsx): "className" is now an alias for "class" by @usualoma in #2146
- fix!(deno): put SSG helper into
helper.ts
by @yusukebe in #2150 - refactor(jsx): Tidyup the types to be exported by @usualoma in #2151
- fix(types):
MergeSchemePath
infers param types correctly by @yusukebe in #2152 - fix(types):
MergeSchemaPath
infer inputs not only params by @yusukebe in #2154 - Fix/function for attribute by @usualoma in #2161
- fix(jsx): The third argument of jsx(), key, is optional by @usualoma in #2162
- Feat/more jsx event by @usualoma in #2165
- docs: update the migration guide for releasing v4 by @yusukebe in #2159
- perf(mime): make
getExtension()
fast by @ryuapp in #2168 - v4 by @yusukebe in #2167
New Contributors
- @EdamAme-x made their first contribution in #1976
- @nabeken5 made their first contribution in #1916
- @Kyiro made their first contribution in #2121
- @javascripter made their first contribution in #2143
Full Changelog: v3.12.10...v4.0.0