preactjs / preact-cli

😺 Your next Preact PWA starts in 30 seconds.
MIT License
4.68k stars 376 forks source link

RFC: Custom Prerendering / SSR #1108

Open developit opened 4 years ago

developit commented 4 years ago

Feature Proposal: Custom Prerendering / SSR Entry

I'd like to propose that we add support for an optional src/prerender.js (+.ts, etc) entrypoint.

When we construct the Webpack configuration for SSR/prerendering, we would detect the existence of this entry file and use it instead of src/index.js when building ssr-bundle. Detection of this file would work the same as the existing index.js entry, inferring src/ directory existence and so on.

What stays the same:

Currently, preact-cli implements prerendering automatically. We require developers export their root Component as the default export of src/index.js, and we call renderToString on it for them. This keeps things simple and has allowed us to pass an increasing amount data to the root component as props. This behavior would remain as-is for projects with no prerender.js.

What changes:

For projects that define a prerender.js, we would still require that they export their root component from src/index.js, and we will still automatically render/hydrate that component on the client. During prerendering, however, instead of importing index.js, we will import prerender.js, which is expected to export a single (async?) function. In this mode, developers must call renderToString() themselves, which means they are free to manipulate the props passed to their root component as well as the response from renderToString. The return value of the prerender() function is an object with html, body and head properties, each of which are optional strings of HTML to be inserted into the HTML template.

Example

src/index.js

import styled from 'styled-components';
const Button = styled.button`
  font-weight: bold;
`;
export default function App() {
  return <Button>Hello</Button>
}

src/prerender.js

import renderToString from 'preact-render-to-string';
import { ServerStyleSheet } from 'styled-components';
import App from './index';

export default async function prerender({ url, ...etc }) {
  const sheet = new ServerStyleSheet();

  const html = renderToString(sheet.collectStyles(
    <App url={url} {...etc} />
  ));

  // we'll inject <style> tag HTML into <head>
  const head = sheet.getStyleTags();

  return { html, head };
}

What does this do for CLI?

Update: here's a mock version of prerender.js support for CLI. It's probably broken and can only support { html } return values.

SolarLiner commented 4 years ago

I don't know if this is something we can piggy-back onto this feature, but for SSR it'd be great to at least have reference to the generated client assets, or even have the generated client index.html to inject the server rendered HTML into, and send the resulting content to the client.

I haven't had success with SSR from scratch because of that, and this would facilitate the inclusion of Preact projects with features like embedded JavaScript in Spring Boot servers or using the SPA SSR feature in ASP.NET Core (or my own Django-based solution).

benkingcode commented 4 years ago

This would be very beneficial, I'm stuck on this currently - seems like there's no way to write 'server-only' code for the prerender... in particular to handle Styled Components