sjc5 / hwy

Hwy is a fullstack web framework for driving a (p)react frontend with a Go backend. Includes end-to-end typesafety, file-based nested UI routing, and much more.
BSD 3-Clause "New" or "Revised" License
317 stars 3 forks source link

Experimental preact #53

Closed sjc5 closed 6 months ago

sjc5 commented 9 months ago

v0.8.0 – Preparing For A More Stable and Flexible Future

This is an exciting release, but it comes with some important breaking changes. Make sure you read these release notes carefully, and let me know if you have questions.

We are breaking things now, so that Hwy can get more stable, faster. If you stick around, it will be worth it.

NEW FEATURE: Hot Reloading for All Framework-Managed CSS

We now support hot reloading for CSS files in your styles folder!

This means that you will now see changes to your CSS files hot update in your browser (without a hard reload) in just milliseconds.

Changes to your JavaScript files will still do a good old-fashioned hard reload, like before.

If you want to shut off hot CSS reloading for some reason, you can do so in your Hwy config by setting hwyConfig.dev.hotReloadStyles to false.

BREAKING: Preact Is Now Our Core JSX Rendering Engine

We are moving to Preact for our core JSX renderer, in lieu of Hono's JSX renderer.

While the core Hono server has been absolutely wonderful to build on – and with the utmost respect to the fantastically talented Hono team – I have found Hono's JSX renderer specifically to be a bit too much of a moving target to be a core primitive to build a framework on.

Moving to Preact will alleviate this instability, and it will further open the door to a future where Hwy can officially support client-side Preact (as an alternative to HTMX).

This move to Preact for our core JSX renderer means that any Hono-specific JSX features that aren't directly supported by Preact will also not be supported by Hwy moving forward (namely, async components and streaming suspense). While these experimental Hono JSX features are cool, they are not stable, and at the end of the day, I am much more a believer in the ultimate simplicity and easy-to-reason-about nature of parallel loaders paired with synchronous nested components—i.e., the hugely successful pattern introduced by Remix 1.0.

I strongly considered officially supporting both Preact JSX rendering and Hono JSX rendering, but making the framework JSX-renderer agnostic would add all kinds of complexity that would be harmful to our long-term mission. We need to consolidate on a single, stable, mature solution for our JSX rendering, and that solution is Preact.

My top goal for Hwy is for Hwy itself to become boring and stable as fast as possible. This goal is more aligned with Preact than Hono JSX, and that's OK.

Migration Instructions

1. jsxImportSource should now be preact

In your tsconfig.json, change your jsxImportSource to preact.

OLD:

{
  "compilerOptions": {
    "jsxImportSource": "hono/jsx"
  }
}

NEW:

{
  "compilerOptions": {
    "jsxImportSource": "preact"
  }
}

2. Refactor Any Async Components

If you have any async components in your project, refactor them to be synchronous, by moving any necessary asynchronous tasks into the route's associated loader function.

Make sure the components no longer use the async keyword.

DON'T DO THIS:

export default async function MyAsyncComponent() {
  const something = await doSomethingAsync();
  return <div>{something}</div>;
}

DO THIS:

export async function loader() {
  return await doSomethingAsync();
}

export default function MySyncComponent({ loaderData }) {
  return <div>{loaderData}</div>;
}

3. Renamed / moved HTMX-specific utilities

OLD:

import { getDefaultBodyProps, redirect } from "hwy";

NEW:

import { getDefaultHtmxBodyProps, htmxRedirect } from "@hwy-js/utils/htmx";

4. defaultHeadBlocks is now a prop of renderRoot

Before, your HeadElements component would take a prop called defaults.

This has been lifted up to the parent renderRoot function, and is now called defaultHeadBlocks.

OLD:

<HeadElements default={defaultHeadBlocks} {...otherProps} />

NEW:

return await renderRoot({
  defaultHeadBlocks,
  ...otherProps,
});

5. HeadBlock["props"] is now HeadBlock["attributes"]

HeadBlock["props"] is now HeadBlock["attributes"].

This lines up better with HTML semantics.

OLD:

{
  tag: "meta",
  props: {
    name: "description",
    content: "Take the Hwy!",
  },
}

NEW:

{
  tag: "meta",
  attributes: {
    name: "description",
    content: "Take the Hwy!",
  },
}

6. RouteData

The component you define in the root property in the object argument to renderRoot now takes a more complex shape and is called, collectively, RouteData.

This non-destructured object should be spread into your HeadElements, ClientScripts, and RootOutlet components.

The end result should look something like this:

return await renderRoot({
  ...otherProps,
  root: (routeData) => {
    return (
      <html lang="en">
        <head>
          <HeadElements {...routeData} />
          <CssImports />
          <ClientScripts {...routeData} />
          <DevLiveRefreshScript />
        </head>
        <body>
          <RootOutlet {...routeData} />
        </body>
      </html>
    );
  },
});

7. Client Entry Point Changes

There are a couple changes to the client entry point.

First, it will now be named entry.client.ts instead of client.entry.ts. We'll probably make this configurable in the future, but for now, just swap the words.

OLD:

Filename: client.entry.ts

NEW:

Filename: entry.client.ts

Second, your client entry will now be injected into your document head as a type="module" script, instead of being injected as a classic script. We need to make it a proper module to support automatic client code splitting in the future. This has implications for how you import more old-fashioned scripts, like HTMX, Idiomorph, and NProgress, into your project, which is discussed below.


8. Move "Classic" Scripts Out of Client Entry Point

It's always been a bit of a hack to get HTMX, Idiomorph, and NProgress to play nicely with our client entry point. They really weren't meant to be used that way. Rather, they're designed to just be tossed into your document head as a classic script.

Now, that's exactly what you'll do.

You can now do this via a new property in your hwyConfig called scriptsToInject. This takes an array of strings, which are paths (relative from your project root) to any distribution-ready scripts you want to inject into your document head. They will be injected as classic scripts (with defer on), in the order you specify, in your document head.

OLD:

// client.entry.ts
import { initHtmx, initIdiomorph } from "@hwy-js/client";

await initHtmx();
await initIdiomorph();

NEW:

// hwy.config.ts
import type { HwyConfig } from "@hwy-js/build";

export default {
  ...,
  scriptsToInject: [
    "node_modules/htmx.org/dist/htmx.min.js",
    "node_modules/htmx.org/dist/ext/head-support.js",
    "node_modules/idiomorph/dist/idiomorph-ext.min.js",
  ],
} satisfies HwyConfig;

Although this looks more complex, it's actually simpler, since we were doing some hacky stuff under the hood before to get these scripts to work with our client entry point.

Note that these don't need to point to node_modules (for example, if you are concerned about the export location changing). You can point to any file in your project, as long as it's a distribution-ready JavaScript file.

SIDE NOTE: In case you weren't aware, another way to self-vendor these would be to to just copy them into your public folder, and then use getPublicUrl("some-script.js") to get the hashed URL to the file, like this: <script src={getPublicUrl("some-script.js")} />.


9. RootOutlet is now exported from @hwy-js/client

RootOutlet is now exported from @hwy-js/client instead of hwy.

OLD:

import { RootOutlet } from "hwy";

NEW:

import { RootOutlet } from "@hwy-js/client";

That's it! Enjoy, and let me know if you hit any snags.

vercel[bot] commented 9 months ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
hwy-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 25, 2024 4:02am