Add docs for renderer
plugins (#302)
* docs: add renderer doc * doc: update intro * doc: reorganize renderers doc * docs: update renderer intro
This commit is contained in:
parent
24d7057dad
commit
316b4a1652
1 changed files with 163 additions and 0 deletions
163
docs/renderers.md
Normal file
163
docs/renderers.md
Normal file
|
@ -0,0 +1,163 @@
|
|||
# 🪄 Renderers
|
||||
|
||||
Astro is able to render [React](https://npm.im/@astrojs/renderer-react), [Svelte](https://npm.im/@astrojs/renderer-svelte), [Vue](https://npm.im/@astrojs/renderer-vue), and [Preact](https://npm.im/@astrojs/renderer-preact) components out of the box. This is because Astro's [default configuration][astro-config] relies on **renderers** for those frameworks.
|
||||
|
||||
If you'd like to add support for another framework, you can build a **renderer** plugin using the same interface as Astro's official renderers.
|
||||
|
||||
## What is a renderer?
|
||||
|
||||
A renderer is an NPM package that has two responsiblities—the first is to _render a component to a static string of HTML_ at build time and the second is to _rehydrate that HTML_ to an interactive component on the client.
|
||||
|
||||
Without getting too much further, it might be helpful to take a look at Astro's built-in [`renderers`](https://github.com/snowpackjs/astro/tree/main/packages/renderers). We'll go into more detail in the following sections.
|
||||
|
||||
## Enabling a new renderer
|
||||
|
||||
To enable a new renderer, add the dependency to your project and update the `renderers` array to include it.
|
||||
|
||||
```diff
|
||||
export default {
|
||||
renderers: [
|
||||
+ 'my-custom-renderer',
|
||||
'@astrojs/renderer-svelte',
|
||||
'@astrojs/renderer-vue',
|
||||
'@astrojs/renderer-react',
|
||||
'@astrojs/renderer-preact',
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Building a new renderer
|
||||
|
||||
A simple renderer only needs a few files.
|
||||
```
|
||||
/renderer-xxx/
|
||||
├── package.json
|
||||
├── index.js
|
||||
├── server.js
|
||||
└── client.js
|
||||
```
|
||||
|
||||
Two quick notes before we dive into these files individually.
|
||||
1. We'd love for you to contribute any renderer you build directly to the Astro repo. This will allow us to publish it under `@astrojs/renderer-xxx`! Feel free to open a pull request.
|
||||
2. Your renderer doesn't need to be written in ESM, but it's pretty straightforward! Add `"type": "module"` to your `package.json` file and be sure to [define a valid `export` map](https://nodejs.org/api/packages.html#packages_package_entry_points).
|
||||
|
||||
## Renderer Entrypoint (`index.js`)
|
||||
|
||||
The main entrypoint of a renderer is a simple JS file which exports a manifest for the renderer. The required values are `name`, `server`, and `client`.
|
||||
|
||||
Additionally, this entrypoint can optionally define a [Snowpack plugin](https://www.snowpack.dev/guides/plugins) that should be used to load non-JavaScript files.
|
||||
|
||||
|
||||
```js
|
||||
export default {
|
||||
name: '@astrojs/renderer-xxx', // the renderer name
|
||||
client: './client.js', // relative path to the client entrypoint
|
||||
server: './server.js', // relative path to the server entrypoint
|
||||
snowpackPlugin: '@snowpack/plugin-xxx', // optional, the name of a snowpack plugin to inject
|
||||
snowpackPluginOptions: { example: true }, // optional, any options to be forwarded to the snowpack plugin
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
## Server entrypoint (`server.js`)
|
||||
|
||||
The server entrypoint of a renderer is responsible for checking if a component should use this renderer, and if so, how that component should be rendered to a string of static HTML.
|
||||
|
||||
```js
|
||||
export default {
|
||||
// should Component use this renderer?
|
||||
check(Component, props, childHTML) {},
|
||||
// Component => string of static HTML
|
||||
renderToStaticMarkup(Component, props, childHTML) {}
|
||||
}
|
||||
```
|
||||
|
||||
### `check`
|
||||
|
||||
`check` is a function that determines whether a Component should be "claimed" by this renderer.
|
||||
|
||||
In it's simplest form, it can check for the existence of a flag on Object-based components.
|
||||
|
||||
```js
|
||||
function check(Component) {
|
||||
return Component.isMyFrameworkComponent;
|
||||
}
|
||||
```
|
||||
|
||||
In more complex scenarios, like when a Component is a `Function` without any flags, you may need to use `try/catch` to attempt a full render. This result is cached so that it only runs once per-component.
|
||||
|
||||
```js
|
||||
function check(Component, props, childHTML) {
|
||||
try {
|
||||
const { html } = renderToStaticMarkup(Component, props, childHTML);
|
||||
return Boolean(html);
|
||||
} catch (e) {}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### `renderToStaticMarkup`
|
||||
|
||||
`renderToStaticMarkup` is a function that renders a Component to a static string of HTML. You don't need to worry about hydration
|
||||
|
||||
```js
|
||||
import { renderToString } from 'xxx';
|
||||
|
||||
function renderToStaticMarkup(Component, props, childHTML) {
|
||||
const html = renderToString(h(Component, { ...props, innerHTML: childHTML }));
|
||||
return { html };
|
||||
}
|
||||
```
|
||||
|
||||
Note that `childHTML` is an HTML string representing this component's children. If your framework does not support rendering HTML directly, you are welcome to use a wrapper component. By convention Astro uses a custom element, `astro-fragment`, to inject `childHTML` into.
|
||||
|
||||
```js
|
||||
import { h, renderToString } from 'xxx';
|
||||
|
||||
const Wrapper = ({ value }) => h('astro-fragment', { dangerouslySetInnerHTML: { __html: value } });
|
||||
|
||||
function renderToStaticMarkup(Component, props, childHTML) {
|
||||
const html = renderToString(
|
||||
h(Component, props,
|
||||
h(Wrapper, { value: childHTML })
|
||||
)
|
||||
);
|
||||
return { html };
|
||||
}
|
||||
```
|
||||
|
||||
## Client entrypoint (`client.js`)
|
||||
|
||||
The client entrypoint of a renderer is responsible for rehydrating a static HTML (the result of `renderToStaticMarkup`) back into a fully interactive component. Its `default` export should be a `function` which accepts the host element of the Component, an `astro-root` custom element.
|
||||
|
||||
> If your framework supports non-destructive component hydration (as opposed to a destructive `render` method), be sure to use that! Following your framework's Server Side Rendering (SSR) guide should point you in the right direction.
|
||||
|
||||
```js
|
||||
import { hydrate } from 'xxx';
|
||||
|
||||
export default (element) => {
|
||||
return (Component, props, childHTML) => {
|
||||
hydrate(h(Component, { ...props, innerHTML: childHTML }));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that `childHTML` is an HTML string representing this component's children. If your framework does not support rendering HTML directly, you should use the same wrapper component you used for the server entrypoint.
|
||||
|
||||
```js
|
||||
import { h, hydrate } from 'xxx';
|
||||
import SharedWrapper from './SharedWrapper.js';
|
||||
|
||||
export default (element) => {
|
||||
return (Component, props, childHTML) => {
|
||||
hydrate(
|
||||
h(Component, props,
|
||||
h(SharedWrapper, { value: childHTML })
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
[astro-config]: ./config.md
|
Loading…
Reference in a new issue