Using Preact
Update the configuration
First, you'll need an Import Map. Frugal uses bare specifiers internally to avoid locking you with a specific version of peer dependencies: import * as preact from 'preact'
. Those kinds of imports need to be "mapped" to actual URLs where you can choose the specific version you wish to use.
{
"imports": {
"preact": "https://esm.sh/preact@10.13.1",
"preact/": "https://esm.sh/preact@10.13.1/",
"preact-render-to-string": "https://esm.sh/preact-render-to-string@5.2.6?external=preact"
}
}
1234567
You'll also need a deno.json
config file to configure jsx
:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"importMap": "./import_map.json"
}
1234567
Now that deno is configured to understand jsx
correctly, we need to update the frugal.config.ts
file :
import { Config } from "https://deno.land/x/frugal@0.9.5/mod.ts"
export default {
self: import.meta.url,
pages: ['pages/home.ts', 'pages/posts.ts'],
importMap: "./import_map.json",
esbuild: {
jsx: "automatic",
jsxImportSource: "preact",
},
} satisfies Config;
1234567891011
Frugal uses esbuild under the hood. The esbuild
entry allows you to pass some options to esbuild. Here we are configuring esbuild the same way we did Deno for it to understand jsx
syntax correctly.
Since we use an Import Map, we must also pass it to Frugal.
Update the post page
First, we move our markup in a jsx component in a pages/PostPage.tsx
module :
import { PageProps, useData, Head } from "https://deno.land/x/frugal@0.9.5/runtime/preact.server.ts"
export function PostPage({ assets }: PageProps) {
const data = useData<{ title:string, content: string }>()
return <>
<Head>
{assets.get('style').map(href => {
return <link rel="stylesheet" href={href}>
})}
</Head>
<h1>{data.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.content }} />
</>
}
12345678910111213141516
In this component :
PageProps
is the standard props object passed to page components (the top-level component of your page).useData
is the hook used to access the data object. This hook can be used in any component with some caveats.Head
is the component used to modify the document's<head>
. We use it to link to the page stylesheet.
Now we need to modify the page module :
...
import { getRenderFrom } from "https://deno.land/x/frugal@0.9.5/runtime/preact.server.ts"
import { PostPage } from "./PostPage.tsx"
...
export const render = getRenderFrom(PostPage)
12345678
And that's it. We now have a static page that can be designed with jsx components. But remember that the homepage still uses js templates to output raw HTML. This means that you can mix any UI framework you want on different pages.
For now, Frugal still outputs static markup for our components. To have a client-side component, we'll have to use islands.
First client-side island
We will add a counter to the homepage. To do so, we first need to migrate it to preact, like we did for the posts page (this is left as an exercise for you, dear reader).
Once the page works with preact, we create our stateful counter component :
import * as hooks from "preact/hooks";
export type CounterProps = {
initialValue: number;
};
export function Counter({ initialValue }: CounterProps) {
const [count, setCount] = hooks.useState(initialValue)
return (
<div>
<button onClick={() => setCount(Math.max(0, count - 1))}>
decrement
</button>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>
increment
</button>
</div>
);
}
1234567891011121314151617181920212223
Next, we create a hydration script Counter-hydrate.script.ts
:
import { hydrate } from "https://deno.land/x/frugal@0.9.5/runtime/preact.client.ts";
import { Counter } from "./Counter.tsx";
export const NAME = "Counter";
if (import.meta.environment === "client") {
hydrate(NAME, () => Counter);
}
12345678
Since it is a client-side script
using import.meta.environment
, the hydrate
function will execute only client-side. The function will hydrate with the Counter
component all instances of islands with the name "Counter"
.
Now we create a CounterIsland.tsx
component to create island instances of our Counter
component :
import { Island } from "https://deno.land/x/frugal@0.9.5/runtime/preact.client.ts";
import { NAME } from "./Counter-hydrate.script.ts";
import { Counter, CounterProps } from "./Counter.tsx";
export function CounterIsland(props: CounterProps) {
return <Island name={NAME} Component={Counter} props={props} />;
}
12345678
The Island
component will render the Counter
components in the static markup outputted by Frugal and embed any data necessary to the hydration process in that markup. The name of the island is also embedded in the markup.
We can now use our CounterIsland
inside our Page
component :
import { PageProps, Head } from "https://deno.land/x/frugal@0.9.5/runtime/preact.server.ts"
import "./post.css";
import { TITLE_ID } from "hello.script.ts";
import { CounterIsland } from "./CounterIsland.tsx"
export const route = '/'
export function Page({ assets, descriptor }: PageProps) {
const styleHref = assets["style"][descriptor]}
const scriptSrc = assets["script"][descriptor]
return <>
<Head>
<link rel="stylesheet" href={styleHref}>
<script type="module" src={scriptSrc}></script>
</Head>
<h1 id={TITLE_ID}>My blog</h1>
<CounterIsland initialValue={3}/>
</>
}
123456789101112131415161718192021
Inside your browser's dev tools, you can see the generated js bundle containing our first vanilla script (changing the style of the title) and our Counter.ts
component and its dependencies (hydrate
, preact
etc...).
To infinity and beyond
The getting-started section is done; you had a first overview of Frugal's capacities. Check out the references and guides to learn more about Frugal!