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 License
Bot releases are hidden (Show)
Published by sjc5 6 months ago
Published by sjc5 6 months ago
Published by sjc5 6 months ago
Published by sjc5 6 months ago
Published by sjc5 6 months ago
Published by sjc5 6 months ago
Published by sjc5 6 months ago
This release is the result of a major re-write of the framework and represents what is essentially a pivot. New documentation is forthcoming.
Published by sjc5 10 months ago
This release adds support for a watchInclusions
array in your hwyConfig.dev
object. This is helpful if you want to re-compile your Hwy project based on upstream activity in a broader monorepo. It works just like watchExclusions
, except it's the opposite.
Published by sjc5 11 months ago
Make experimental, HTMX-aware Suspense component asynchronous.
Published by sjc5 11 months ago
v0.7.0 release notes
Hono recently released their highly anticipated version 3.10.0 release, which includes first class support for async components using normal syntax (no more {await outlet()}
hacks!) as well as experimental streaming and Suspense support. Incredible stuff!
Here's how to bring your app up to date:
Before making the changes described below, upgrade to the latest version of Hwy (all packages, >=0.7.0
) and the latest version of Hono (>=3.10.0
). If you're using the node server, also make sure to update @hono/node-server
to at least >=1.2.3
.
rootOutlet
no longer needs to be rendered like {await rootOutlet({ ...props })}
and instead is now called RootOutlet
(traditional JSX component capitalization) and can simply be used as <RootOutlet {...props} />
(no await needed).renderRoot
now takes a single object argument instead of three ordered arguments. Before, the three ordered arguments were (in order), c
(Hono Context), next
(Hono Next), and rootMarkup
. Now it's an object with c
, next
, and root
(same fundamental shape as before) as required properties, as well as an optional property experimentalStreaming
property, which defaults to false.app.all("*", async (c, next) => {
return await renderRoot(c, next, async ({ activePathData }) => {
return (
<html lang="en">
...
<body {...props}>
{await rootOutlet({
activePathData,
c,
fallbackErrorBoundary: () => {
return <div>Something went wrong.</div>;
},
})}
</body>
</html>
);
});
});
app.all("*", async (c, next) => {
return await renderRoot({
c,
next,
experimentalStreaming: true, // optional, defaults to false
root: ({ activePathData }) => {
return (
<html lang="en">
...
<body {...props}>
<RootOutlet
c={c}
activePathData={activePathData}
fallbackErrorBoundary={() => {
return <div>Something went wrong.</div>;
}}
/>
</body>
</html>
);
},
});
});
Additionally, outlet components (returned in PageProps
and used to render nested child routes) are similarly now capitalized and no longer need the weird async call syntax.
export default async function ({ outlet }: PageProps) {
return (
<Parent>
{await outlet()}
</Parent>
);
}
export default async function ({ Outlet }: PageProps) {
return (
<Parent>
<Outlet />
</Parent>
);
}
Much better! Thanks Hono!
experimentalStreaming
property mentioned above to true
(e.g., renderRoot({ ...props, experimentalStreaming: true})
). This will let you add Suspense (experimental!) from Hono to your app. Streaming doesn't work well with HTMX or hx-boost at the moment, so we exported our own Suspense
component from hwy
core, which simply uses Hono's Suspense component for non-HTMX requests, and directly returns children for HTMX requests. Effectively, this means that you can use suspense for initial page loads, and any subsequent navigations (assuming you're using hx-boost) will be fully rendered before being swapped out by HTMX. If you want to try it, you can do this:import { Suspense, type PageProps } from "hwy";
export default async function({ c }: PageProps) {
return (
<Suspense c={c} fallback={<div>Loading...</div>}>
<SomeAsyncChild />
</Suspense>
);
}
Renames DataFunctionArgs
type to DataProps
. This name is more consistent with its sibling type PageProps
.
export function loader({ params }: DataFunctionArgs) {
return <div />
}
export function loader({ params }: DataProps) {
return <div />
}
As a reminder, DataProps
(previously DataFunctionArgs
) is the appropriate type for the single object parameter to both Hwy loaders and actions. It takes the same generics as before.
We added a new package called @hwy-js/utils
that includes some new goodies. They should be considered experimental.
We added a utility called getFormStrings
to @hwy-js/utils
. getFormStrings
takes a generic of all the string literal types you expect in form data (essentially, the names of the inputs you submitted in a form) and returns them in an object. Very handy. An earlier version of this was included in templates as extractSimpleFormData
. We made it a little more robust, renamed it, and tossed it in a utils package.
const data = await getFormStrings<"email" | "password">({ c });
console.log(data);
// { email: "[email protected]", password: "123" }
We also added a brand new client cookie event helper system to the new @hwy-js/utils
package. Very useful for triggering client-side toasts and stuff like that from server loaders or anywhere you have access to the Hono context object. This approach (cookie-based RPC) is a bit more robust than HX-Trigger
because it will always persist across redirects (because it's cookie-based, not header-based). We'll document it eventually, but you can probably figure it out just by peaking into the source code. It's pretty cool!
Adds an option to the redirect
helper to trigger a redirect using HX-Location
instead of redirecting with Hono's c.redirect
method. This will likely mostly be useful when you are calling an endpoint manually using fetch
or htmx.ajax
or similar. To opt in to that behavior, pass useHxRedirect: true
to the redirect
options arg.
redirect
helper, as well as some bugs in the data function handlers which resulted in thrown redirects not being sent. Now fixed.EADDRINUSE
when trying to restart your dev server. Very annoying, now fixed.3000
. The prior setting (5555
) is the prisma studio default port, and it's not really necessary to try to use a unique one. The traditional 3000
is best, and users can change to whatever they want.Published by sjc5 12 months ago
This release:
getDefaultBodyProps
@hwy-js/client
package, which allows us to drastically simplify the default client.entry.{js,ts}
filecreate-hwy
To upgrade your existing Hwy app to use idiomorph, do the following:
npm i [email protected]
npm i -D @hwy-js/[email protected] @hwy-js/[email protected] @hwy-js/[email protected] idiomorph
Why is idiomorph a dev dependency? Because it's bundled into your client entry at build time (no need for idiomorph to live next to Hono/Hwy on your server other than as part of your static assets).
Then, add { idiomorph: true }
to your getDefaultBodyProps
call (which returns the props you spread into your app's root body tag). For example:
<body {...getDefaultBodyProps({ idiomorph: true })}>
Or, if you're using NProgress, you can do this:
<body {...getDefaultBodyProps({ idiomorph: true, nProgress: true })}>
Then, update your client.entry.{ts,js}
file to the following:
import { initHtmx, initIdiomorph } from "@hwy-js/client";
initHtmx().then(initIdiomorph);
Or, if you're using NProgress, you can do this:
import { initHtmx, initIdiomorph, initNProgress } from "@hwy-js/client";
initHtmx().then(initIdiomorph).then(initNProgress);
Just run:
npx create-hwy@latest
Idiomorph will be included by default for new apps created with create-hwy
.
create-hwy
! Closes #40.Published by sjc5 12 months ago
runBuildTasks
.key={something}
code in components. Probably originated from co-pilot thinking these were React components and me not catching it 🙃.get-matching-path-data-internal.ts
to fix a routing bug where in certain narrow situations, index files would incorrectly render when the ultimate catch should have rendered, plus another bug (caught by new tests 🥳) where sometimes a non-ultimate splat route would be discarded when it shouldn't be, which caused the ultimate splat to render when it didn't need to.paths.js
output and getMatchingPathData
API.loader_args
and action_args
are now loaderArgs
and actionArgs
).create-hwy
templates, including adding an example of some-page.client.ts
usage (classic incrementing counter button), plus some necessary changes to the create-hwy
scripts to facilitate that.create-hwy
dev deps so their versions can be read at runtime.charSet
that should have been charset
.watchExclusions
, which now belongs in hwy.config.{ts,js}
.nodejs_compat
compatibility flags in Cloudflare dashboard.create-hwy
.theme
function calls).test_dirname
to make router more easily testable.getMatchingPathData
, the function that needs testing the most (a very good place to start).testers/routes
code to catch more edge cases.Published by sjc5 12 months ago
First off, huge shoutout to @jharrell for his debugging and research assistance on this PR related to adding Cloudflare Pages support! Thank you!
Cloudflare Pages is now supported! Closes #6.
Added changesets. Closes #14.
Adds a hwy.config.ts
/ hwy.config.js
file to the root of your project for setting up dev settings and deployment target, and probably other things in the future. As annoying as config files are, this simplifies a lot of things and is not too much to ask for a framework.
Removes a lot of complexity / variance in build commands and deploy target hacks, as now we can just read the deployment target from the Hwy config and handle everything in Hwy's centralized build step.
Adds a new @hwy-js/build
package, splitting up the live refresh stuff (stays in @hwy-js/dev
) from the build stuff.
In your src/main.tsx
file:
{hwyDev?.DevLiveRefreshScript()}
is now just <DevLiveRefreshScript />
<ClientEntryScript />
is now <ClientScripts activePathData={activePathData} />
. This is to enable the new client scripts functionality mentioned below.Added an option to ship client-side JS (including from TS files if you want) by adding a sibling page-name.client.ts
or page-name.client.js
file to your page. This becomes basically global JS for that page, and anything imported will be bundled into that page's script, which is built into the public folder and referenced in the document head when you visit that page. This will be better documented later. Closes #15.