hono icon indicating copy to clipboard operation
hono copied to clipboard

Hono serving Bun's HTML imports

Open TheComputerM opened this issue 10 months ago • 7 comments

What is the feature you are proposing?

You can directly import HTML files in Bun, currently you can only serve them using Bun's serve API.

It would be really helpful if you can serve the same imported HTML routes using Hono.

Reference: https://bun.sh/docs/bundler/fullstack#html-imports-are-routes

TheComputerM avatar May 07 '25 10:05 TheComputerM

This is something I have been trying to figure out and work out how. The code is very rough and not production worthy but the prototype works fine. The docs for it in Bun are somewhat obscure https://bun.sh/docs/bundler#outdir

If outdir is not passed to the JavaScript API, bundled code will not be written to disk. Bundled files are returned in an array of BuildArtifact objects.

import type { HTMLBundle } from "bun";
import { Hono } from "hono";
import index from "./index.html";

const app = new Hono();

async function bundle(entrypoint: HTMLBundle, config?: Bun.BuildConfig) {
	let router = new Hono();
	const build = await Bun.build({
		...config,
		entrypoints: [entrypoint.index],
		outdir: undefined,
	});
	if (!build.success) {
		build.logs.forEach((log) => console.log(log));
		throw new Error("Build failed");
	}
	for (const file of build.outputs) {
		const path = file.path.slice(2);
		const handler = () =>
			new Response(file.stream(), {
				headers: {
					"Content-Type": file.type,
					"Cache-Control": "no-store",
				},
				status: 200,
			});

		if (path === "index.html" || path === "index.htm") {
			router.get("/", handler);
		}
		router.get(path, handler);
	}
	return router;
}

app.route('/', await bundle(index));

app.get("/hello", (c) => {
	return c.json({ message: "Hello from Bun!" });
});

export default app;

maca134 avatar May 20 '25 16:05 maca134

I have been trying to make Bun and Hono work hand in hand and that's my best result:

https://github.com/chneau/bun-hono-react-template/blob/master/server.ts

It uses "/api/*": app.fetch, + .basePath("/api") and on the client export const api = hc<ServerType>("/").api (with on the server "/*": index, and .notFound((c) => c.redirect("/")) to have a SPA like experience).

It's more of a "routing" solution than a technical solution - where basically Hono is set on /api/* and the rest /* serves the HTML. Your SPA router will do the job for redirecting when needed (Using wouter I can just have a <Route>404: No such page!</Route>)

1 process to run both client/server (and loving bun's client console.log redirected to the server) - HMR on, Hono's RPC style, bundle that works - not far from paradise in my view.

Interestingly I'm looking at this issue on Bun: https://github.com/oven-sh/bun/issues/2726 This issue would be nice for Bun to be able to handle compression.

But this issue here would be awesome for dev/prod if we can offload the compression to Hono and have Hono handle Bun dev/prod style.

chneau avatar Jul 28 '25 18:07 chneau

isn't easy? like apis using Hono and if you wants to serve HTML then do Bun.serve({ routes:{ '/': HTML }, port:300, fetch: app.fetch }) ?? @yusukebe

pradeepbgs avatar Sep 22 '25 07:09 pradeepbgs

@pradeepbgs I tried with my example there https://github.com/chneau/bun-hono-react-template and it does not work. As well doing so prevent you from refreshing from a "client side routed route". eg. you go to "/" to see your index.html, you go to "/hello" using your client side router (there I am using wouter). Refreshing the page from this URL will hit bun server instead of your bundled page, hence why the use of "/*": HTML.

Edit: I'm hitting a bit more specific problem that initial poster, I am trying to make it feels like a react SPA

chneau avatar Sep 22 '25 10:09 chneau

Can confirm the following code works:

import { serve } from "bun";
import { Hono } from "hono";
import index from "./index.html";

const app = new Hono();

app.get("/api/hello", (c) => {
  return c.json({ message: "Hello, World!" });
});

const server = serve({
  routes: {
    "/*": index,
    "/api/*": app.fetch,
  },

  development: process.env.NODE_ENV !== "production" && {
    hmr: true,
    console: true,
  },
});

console.log(`🚀 Server running at ${server.url}`);

maca134 avatar Sep 22 '25 11:09 maca134

@pradeepbgs I tried with my example there https://github.com/chneau/bun-hono-react-template and it does not work. As well doing so prevent you from refreshing from a "client side routed route". eg. you go to "/" to see your index.html, you go to "/hello" using your client side router (there I am using wouter). Refreshing the page from this URL will hit bun server instead of your bundled page, hence why the use of "/*": HTML.

Edit: I'm hitting a bit more specific problem that initial poster, I am trying to make it feels like a react SPA

try to upgrade bun

pradeepbgs avatar Sep 22 '25 11:09 pradeepbgs

The problem is the route paths. "/": index, will only match the html file and non of the assets (js, css, etc) You have to bind hono to a specific route, /api/* route will get matched before /* route. https://bun.com/docs/api/http#route-precedence

I guess one issue with this approach is no hono middleware on your static content.

maca134 avatar Sep 22 '25 11:09 maca134