Skip to content

Commit

Permalink
Merge pull request #86 from preactjs/feat/prerender-plugin
Browse files Browse the repository at this point in the history
feat: prerender plugin
  • Loading branch information
rschristian committed Jan 2, 2024
2 parents f0a5302 + b99d77f commit 8f87cda
Show file tree
Hide file tree
Showing 12 changed files with 998 additions and 9 deletions.
49 changes: 47 additions & 2 deletions README.md
Expand Up @@ -45,12 +45,14 @@ export default defineConfig({

| Option | Type | Default | Description |
|---|---|---|---|
| `devtoolsInProd` | `boolean` | `false` | Inject devtools bridge in production bundle instead of only in development mode |
| `devToolsEnabled` | `boolean` | `true` | Inject devtools bridge |
| `prefreshEnabled` | `boolean` | `true` | Inject [Prefresh](https://github.com/preactjs/prefresh) for HMR |
| `devtoolsInProd` | `boolean` | `false` | Inject devtools bridge in production bundle instead of only in development mode |
| `reactAliasesEnabled` | `boolean` | `true` | Aliases `react`, `react-dom` to `preact/compat` |
| `babel` | `object` | | See [Babel configuration](#babel-configuration) |
| `prerender` | `object` | | See [Prerendering configuration](#prerendering-configuration) |

### Babel configuration
#### Babel configuration

The `babel` option lets you add plugins, presets, and [other configuration](https://babeljs.io/docs/en/options) to the Babel transformation performed on each JSX/TSX file.

Expand All @@ -68,6 +70,49 @@ preact({
})
```

#### Prerendering configuration

| Option | Type | Default | Description |
|---|---|---|---|
| `enabled` | `boolean` | `false` | Enables prerendering |
| `prerenderScript` | `string` | `undefined` | Absolute path to script containing exported `prerender()` function. If not provided, will try to find the prerender script in the scripts listed in your HTML entrypoint |
| `renderTarget` | `string` | `"body"` | Query selector for where to insert prerender result in your HTML template |
| `additionalPrerenderRoutes` | `string` | `undefined` | Prerendering will automatically discover links to prerender, but if there are unliked pages that you want to prererender (such as a `/404` page), use this option to specify them |

To prerender your app, you'll need to set `prerender.enabled` to `true` and export a `prerender()` function one of the scripts listed in your HTML entry point (or the script specified through `prerender.prerenderScript`). How precisely you generate an HTML string from your app is up to you, but you'll likely want to use [`preact-render-to-string`](https://github.com/preactjs/preact-render-to-string) or a wrapper around it such as [`preact-iso`'s `prerender`](https://github.com/preactjs/preact-iso). Whatever you choose, you simply need to return an object from your `prerender()` function containing an `html` property with your HTML string.

[For an example implementation, see our demo](./demo/src/index.tsx)

```js
import { render } from 'preact-render-to-string';

export async function prerender(data) {
const html = render(`<h1>hello world</h1>`);

return {
html,
// Optionally add additional links that should be
// prerendered (if they haven't already been)
links: new Set(['/foo', '/bar']),
// Optionally configure and add elements to the `<head>` of
// the prerendered HTML document
head: {
// Sets the "lang" attribute: `<html lang="en">`
lang: 'en',
// Sets the title for the current page: `<title>My cool page</title>`
title: 'My cool page',
// Sets any additional elements you want injected into the `<head>`:
// <link rel="stylesheet" href="foo.css">
// <meta property="og:title" content="Social media title">
elements: new Set([
{ type: 'link', props: { rel: 'stylesheet', href: 'foo.css' } },
{ type: 'meta', props: { property: 'og:title', content: 'Social media title' } }
])
}
};
}
```

## License

MIT, see [the license file](./LICENSE).
2 changes: 1 addition & 1 deletion demo/index.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
Expand Down
1 change: 1 addition & 0 deletions demo/public/local-fetch-test.txt
@@ -0,0 +1 @@
Local fetch works
32 changes: 32 additions & 0 deletions demo/src/components/LocalFetch.tsx
@@ -0,0 +1,32 @@
import { useState } from "preact/hooks";

const cache = new Map();

async function load(url: string) {
const res = await fetch(url);
return await res.text();
}

function useFetch(url: string) {
const [_, update] = useState({});

let data = cache.get(url);
if (!data) {
data = load(url);
cache.set(url, data);
data.then(
(res: string) => update((data.res = res)),
(err: Error) => update((data.err = err)),
);
}

if (data.res) return data.res;
if (data.err) throw data.err;
throw data;
}

export function LocalFetch() {
const data = useFetch("/local-fetch-test.txt");

return <p>{data.trimEnd()}</p>;
}
34 changes: 31 additions & 3 deletions demo/src/index.tsx
@@ -1,5 +1,10 @@
import { render } from "preact";
import { LocationProvider, Router, Route } from "preact-iso";
import {
LocationProvider,
Router,
Route,
hydrate,
prerender as ssr,
} from "preact-iso";

import { Header } from "./components/Header.jsx";
import { Home } from "./pages/Home/index.jsx";
Expand All @@ -20,4 +25,27 @@ export function App() {
);
}

render(<App />, document.getElementById("app")!);
if (typeof window !== "undefined") {
hydrate(<App />, document.getElementById("app"));
}

export async function prerender() {
const { html, links } = await ssr(<App />);
return {
html,
links,
head: {
lang: "en",
title: "Prerendered Preact App",
elements: new Set([
{
type: "meta",
props: {
name: "description",
content: "This is a prerendered Preact app",
},
},
]),
},
};
}
2 changes: 2 additions & 0 deletions demo/src/pages/Home/index.tsx
@@ -1,4 +1,5 @@
import { ReactComponent } from "../../components/Compat";
import { LocalFetch } from "../../components/LocalFetch";

import preactLogo from "../../assets/preact.svg";
import "./style.css";
Expand All @@ -11,6 +12,7 @@ export function Home() {
</a>
<h1>Get Started building Vite-powered Preact Apps </h1>
<ReactComponent />
<LocalFetch />
<section>
<Resource
title="Learn Preact"
Expand Down
10 changes: 9 additions & 1 deletion demo/vite.config.ts
Expand Up @@ -3,5 +3,13 @@ import preact from "../src/index";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact()],
plugins: [
preact({
prerender: {
enabled: true,
renderTarget: "#app",
additionalPrerenderRoutes: ["/404"],
},
}),
],
});

0 comments on commit 8f87cda

Please sign in to comment.