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...