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.
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.
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.
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.
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.
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.
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
tofalse
.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 bepreact
In your
tsconfig.json
, change yourjsxImportSource
topreact
.❌ OLD:
✅ NEW:
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:
✅ DO THIS:
3. Renamed / moved HTMX-specific utilities
❌ OLD:
✅ NEW:
4.
defaultHeadBlocks
is now a prop ofrenderRoot
Before, your
HeadElements
component would take a prop calleddefaults
.This has been lifted up to the parent
renderRoot
function, and is now calleddefaultHeadBlocks
.❌ OLD:
✅ NEW:
5.
HeadBlock["props"]
is nowHeadBlock["attributes"]
HeadBlock["props"]
is nowHeadBlock["attributes"]
.This lines up better with HTML semantics.
❌ OLD:
✅ NEW:
6. RouteData
The component you define in the
root
property in the object argument torenderRoot
now takes a more complex shape and is called, collectively,RouteData
.This non-destructured object should be spread into your
HeadElements
,ClientScripts
, andRootOutlet
components.The end result should look something like this:
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 ofclient.entry.ts
. We'll probably make this configurable in the future, but for now, just swap the words.❌ OLD:
✅ NEW:
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
calledscriptsToInject
. 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 (withdefer
on), in the order you specify, in your document head.❌ OLD:
✅ NEW:
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 usegetPublicUrl("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 ofhwy
.❌ OLD:
✅ NEW:
That's it! Enjoy, and let me know if you hit any snags.