Bun is great, yes!, Lately everyone is amazed and I was able to play around with it today, and I found it is pretty awesome to use it alongside with Hono, Vite and TailwindCSS.
Without further ado, here I show you the things I have done.
First, create a project with bun, its way way faster than usual.
bun create hono my-hono-app
cd my-hono-app
bun install
For demo purpose, we'll use some popular libs e.g. jose.
bun add jose
Change the tsconfig.json
slightly to be like so:
{
"compilerOptions": {
"esModuleInterop": true,
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"types": [
"bun-types"
],
}
}
Let's put some code making use of jose
, e.g. create a jwt by ourself, and we place it in src/core.ts
:
import * as jose from "jose";
const secret = "🔥🔥🔥🔥🔥🔥🔥🔥🔥";
const algo = "HS512";
const secretBytes = new TextEncoder().encode(secret);
export const secretB64 = btoa(
Array.from(secretBytes, (x) => String.fromCodePoint(x)).join("")
);
export const createJWT = async () => {
return await new jose.SignJWT({
claims: {
userId: "ridho",
},
})
.setProtectedHeader({ alg: algo })
.setAudience("urn:audience")
.setIssuer("urn:issuer")
.setSubject("urn:subject")
.setIssuedAt()
.setExpirationTime("5m")
.sign(secretBytes);
};
Take note from the above code on how we should create base64 string based on this MDN guide. Later we can validate manually if our jwt and the secretBytes is correct by pasting the token and secret to jwt.io.
Now lets modify the server entry file in src/index.ts
.
import { Hono } from "hono";
import { createJWT, secretB64 } from "./core";
const app = new Hono();
app.get("/", async (c) =>
c.text(`Secret: ${secretB64} JWT: ${await createJWT()}`)
);
export default app;
Yes, its ready. We can now start the development server:
bun run --hot src/index.ts
Great, thats it, very simple and easy! With just very little effort and a few files below, we can serve about 100k request per seconds.
tree -I node_modules
.
├── bun.lockb
├── package.json
├── README.md
├── src
│  ├── core.ts
│  └── index.ts
└── tsconfig.json
2 directories, 6 files
Ok, lets go further. Instead of simple text response, we want to serve html. Here is how:
import { html } from "hono/html";
app.get("/hello", (c) =>
c.html(html`<!DOCTYPE html>
<html>
<head>
<title>Simple demo</title>
</head>
<body>
<h1>Ok</h1>
</body>
</html>`)
);
I want to write content with jsx you say. Ok, its easy too. First create our jsx file hello.tsx
as below:
export function Hello() {
return (
<div>
<h1>Hello there!</h1>
</div>
);
}
And use it just like usual, in this simple case, we'll put the jsx into our previous html body:
import { html } from "hono/html";
import { jsx } from "hono/jsx";
import { Hello } from "./hello";
app.get("/hello", (c) => {
const content = jsx(Hello, {}); // <-- HERE IS our JSX content
return c.html(html`<!DOCTYPE html>
<html>
<head>
<title>Simple demo</title>
</head>
<body>
${content}
</body>
</html>`);
});
Yeah, that is awesome, and there is more! We can have the great and famous Vite and all it stuffs, e.g. tailwindcss to works too.
There is @hono/vite-dev-server
that is being actively developed by the author and for sure it will become great when its ready.
During this time, I've explored how to manually integrate Vite with all of our stuff above so far. It will be just brief steps, i believe you could follow along.
Add the necessary deps:
bun add --dev vite tailwindcss postcss autoprefixer
Init tailwindcss config:
bunx tailwindcss init --esm --postcss
Create vite.config.ts
:
import { defineConfig } from "vite";
export default defineConfig({
build: {
manifest: true,
rollupOptions: {
input: "/src/client.tsx",
},
},
});
In src/client.tsx
is where our client code live and managed by vite. For now, it simply contains import to tailwind css entry file.
tailwind.config.js
:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.tsx"],
theme: {
extend: {},
},
plugins: [],
}
postcss.config.js
:
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Add type=module
to package.json
:
{
"type": "module",
"scripts": {
"dev": "bun run --hot src/index.ts",
"vite-dev": "vite",
"build": "vite build",
"serve": "NODE_ENV=production bun run src/index.ts"
},
"dependencies": {
"hono": "^3.5.4",
"jose": "latest"
},
"devDependencies": {
"autoprefixer": "^10.4.15",
"bun-types": "^0.6.2",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"vite": "^4.4.9"
}
}
Add or modify target
, module
and moduleResolution
to tsconfig.json
:
{
"compilerOptions": {
"esModuleInterop": true,
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"types": [
"bun-types"
],
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
}
}
src/style.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
src/client.tsx
:
import "./style.css"
src/hello.tsx
:
export function Hello() {
return (
<div class="text-2xl text-red-600 grid h-[100dvh] items-center justify-center text-center">
<div>
<h1 class="text-4xl font-bold">Hello there!</h1>
<p class="font-mono">
Bun, Hono, Vite, TailwindCSS <br />
🔥🔥🔥🔥🔥🔥🔥🔥🔥
</p>
</div>
</div>
);
}
src/index.ts
:
import { Hono } from "hono";
import { serveStatic } from "hono/bun";
import { html, raw } from "hono/html";
import { jsx } from "hono/jsx";
import { createJWT, secretB64 } from "./core";
import { Hello } from "./hello";
const app = new Hono();
app.use("/assets/*", serveStatic({ root: "./dist" }));
app.get("/", async (c) =>
c.text(`Secret: ${secretB64} JWT: ${await createJWT()}`)
);
const isProd = process.env.NODE_ENV === "production";
const manifestPath = "../dist/manifest.json";
const cssFile = isProd
? (await import(manifestPath)).default["src/client.tsx"]?.css?.at(0)
: null;
app.get("/hello", (c) => {
const content = jsx(Hello, {});
return c.html(html`<!DOCTYPE html>
<html>
<head>
<title>Simple demo</title>
${cssFile ? raw(`<link rel="stylesheet" href="${cssFile}">`) : null}
</head>
<body class="bg-green-200">
${content}
${isProd
? null
: raw(`<script
type="module"
src="http://localhost:5173/@vite/client"
></script>
<script
type="module"
src="http://localhost:5173/src/client.tsx"
></script>`)}
</body>
</html>`);
});
export default app;
To start development, start bun and vite on separate terminal:
bun run vite-dev
bun run dev
For production, do build and run:
bun run build
bun run serve
Thats it.
Thank you for reading, have a good weekend!
Top comments (0)