Perfect Todos
AlpineJS was not playing nicely with JSX. Syntax like @click.stop="something"
was nearly impossible. So, I dropped JSX.
I converted everything to be template literals using Hono's html
tag. Sure, not quite as pleasant to use as JSX/TSX, but it feels closer to the metal to me, and I like that. Just functions and strings. WebStorm treats HTML in strings just like you are in an HMTL file, so I can now easily use all the HTMX and AlpineJS I want. I also get a couple nice bonuses, like cmd + click
a onclick
handler to get taken to to the JS file.
I now have a function for each page. The page function takes in some context and passes it to a layout, plus it's own content.
interface PageContext extends BaseContext {
reason: string | undefined;
}
function LoginPage(context: PageContext) {
const { reason } = context;
return BaseLayout(
html`<div class="container-narrow">
<h1>Login</h1>
${LoginForm(undefined, reason)}
</div>`,
context,
);
}
app.get('/login', async (c) => {
const reason = c.req.query('reason');
return c.html(LoginPage({ title: 'Login', reason }));
});
Before, I was using Hono's JSX renderer middleware. That middleware was nice to use, but required configuring some global types and spread render logic around. Encapsulating everything in a single page function feels more declarative to me. It removes any doubt about where things are coming from. Yeah...I also could have done this with JSX...but sometimes you just need a kick to see things differently.
TL;DR, HTML template literals let me use the syntax and attributes I want while only being slightly less convenient than JSX (for me.)
I spent a few days just cleaning up my codebase with this change. It's feeling nice and tidy right now. Ready for me to muck it up.
Another little Hono nicety I found, if I chain .use()
when I create the app, I don't need to type the Env myself.
Before:
const app = new Hono<{
Variables: { appContext: AppContext; user: User };
}>();
app.use(sessionMiddleware, appContextMiddleware);
app.get('/', async (c) => {
return c.html(AppPage(c.var.appContext));
});
After:
const app = new Hono().use(sessionMiddleware, appContextMiddleware);
app.get('/', async (c) => {
return c.html(AppPage(c.var.appContext));
});
I always feel weird having to explicitly pass in types, so this is a win in my book. But, between you and me, part of me is wondering if I should drop TS too...