|
1 | | -# react-ssr-prepass 🌲 |
| 1 | +# react-ssr-prepass |
2 | 2 |
|
3 | | -`react-ssr-prepass` is a partial server-side React renderer that is meant |
4 | | -to do a prepass on a React tree and await all suspended promises. It also |
5 | | -accepts a visitor function that can be used to await on custom promises. |
| 3 | +<p> |
| 4 | + <a href="https://travis-ci.org/kitten/react-ssr-prepass"> |
| 5 | + <img alt="Build Status" src="https://travis-ci.org/kitten/react-ssr-prepass.svg?branch=master" /> |
| 6 | + </a> |
| 7 | + <a href="https://coveralls.io/github/kitten/react-ssr-prepass?branch=master"> |
| 8 | + <img alt="Test Coverage" src="https://coveralls.io/repos/github/kitten/react-ssr-prepass/badge.svg?branch=master" /> |
| 9 | + </a> |
| 10 | + <a href="https://npmjs.com/package/react-ssr-prepass"> |
| 11 | + <img alt="NPM Version" src="https://img.shields.io/npm/v/react-ssr-prepass.svg" /> |
| 12 | + </a> |
| 13 | +</p> |
6 | 14 |
|
7 | | -It's meant to be used for fetching data before executing `renderToString` |
8 | | -or `renderToNodeStream` and provides a crude way to support suspense during |
9 | | -SSR today. ✨ |
| 15 | +<p> |
| 16 | + <code>react-dom/server</code> does not have support for suspense yet.<br /> |
| 17 | + <code>react-ssr-prepass</code> offers suspense on the server-side today, until it does. ✨ |
| 18 | +</p> |
10 | 19 |
|
11 | | -> ⚠️ **Disclaimer:** Suspense is unstable and experimental. Its API may change |
12 | | -> and hence this library may break. Awaiting promises is only part of the |
13 | | -> story and does not mean that any data will be rehydrated on the client |
14 | | -> automatically. There be dragons! |
| 20 | +`react-ssr-prepass` is a **partial server-side React renderer** that does a prepass |
| 21 | +on a React element tree and suspends when it finds thrown promises. It also |
| 22 | +accepts a visitor function that can be used to suspend on anything. |
| 23 | + |
| 24 | +You can use it to fetch data before your SSR code calls `renderToString` or |
| 25 | +`renderToNodeStream`. |
| 26 | + |
| 27 | +> ⚠️ **Note:** Suspense is unstable and experimental. This library purely |
| 28 | +> exists since `react-dom/server` does not support data fetching or suspense |
| 29 | +> yet. This two-pass approach should just be used until server-side suspense |
| 30 | +> support lands in React. |
| 31 | +
|
| 32 | +## The Why & How |
| 33 | + |
| 34 | +It's quite common to have some data that needs to be fetched before |
| 35 | +server-side rendering and often it's inconvenient to specifically call |
| 36 | +out to random fetch calls to get some data. Instead **Suspense** |
| 37 | +offers a practical way to automatically fetch some required data, |
| 38 | +but is currently only supported in client-side React. |
| 39 | + |
| 40 | +`react-ssr-prepass` offers a solution by being a "prepass" function |
| 41 | +that walks a React element tree and executing suspense. It finds all |
| 42 | +thrown promises (a custom visitor can also be provided) and waits for |
| 43 | +those promises to resolve before continuing to walk that particular |
| 44 | +suspended subtree. Hence, it attempts to offer a practical way to |
| 45 | +use suspense and complex data fetching logic today. |
| 46 | + |
| 47 | +A two-pass React render is already quite common for in other libraries |
| 48 | +that do implement data fetching. This has however become quite impractical. |
| 49 | +While it was trivial to previously implement a primitive React renderer, |
| 50 | +these days a lot more moving parts are involved to make such a renderer |
| 51 | +correct and stable. This is why some implementations now simply rely |
| 52 | +on calling `renderToStaticMarkup` repeatedly. |
| 53 | + |
| 54 | +`react-ssr-prepass` on the other hand is a custom implementation |
| 55 | +of a React renderer. It attempts to stay true and correct to the |
| 56 | +React implementation by: |
| 57 | + |
| 58 | +- Mirroring some of the implementation of `ReactPartialRenderer` |
| 59 | +- Leaning on React elements' symbols from `react-is` |
| 60 | +- Providing only the simplest support for suspense |
15 | 61 |
|
16 | 62 | ## Quick Start Guide |
17 | 63 |
|
@@ -40,14 +86,62 @@ const renderApp = async App => { |
40 | 86 | } |
41 | 87 | ``` |
42 | 88 |
|
43 | | -You should also be aware that `react-ssr-prepass` does not handle any |
| 89 | +Additionally you can also pass a "visitor function" as your second argument. |
| 90 | +This function is called for every React class or function element that is |
| 91 | +encountered. |
| 92 | + |
| 93 | +```js |
| 94 | +ssrPrepass(<App />, (element, instance) => { |
| 95 | + if (element.type === SomeData) { |
| 96 | + return fetchData() |
| 97 | + } else if (instance && instance.fetchData) { |
| 98 | + return instance.fetchData() |
| 99 | + } |
| 100 | +}) |
| 101 | +``` |
| 102 | + |
| 103 | +The first argument of the visitor is the React element. The second is |
| 104 | +the instance of a class component or undefined. When you return |
| 105 | +a promise from this function `react-ssr-prepass` will suspend before |
| 106 | +rendering this element. |
| 107 | + |
| 108 | +You should be aware that `react-ssr-prepass` does not handle any |
44 | 109 | data rehydration. In most cases it's fine to collect data from your cache |
45 | 110 | or store after running `ssrPrepass`, turn it into JSON, and send it |
46 | 111 | down in your HTML result. |
47 | 112 |
|
| 113 | +## Examples & Recipes |
| 114 | + |
| 115 | +### Usage with `react-apollo` |
| 116 | + |
| 117 | +Instead of using `react-apollo`'s own `getDataFromTree` function, `react-ssr-prepass` |
| 118 | +can be used instead. For this to work, we will have to write a visitor function |
| 119 | +that knows how to suspend on `react-apollo`'s `Query` component. |
| 120 | + |
| 121 | +Luckily this is quite simple, since all we need to do is call the `fetchData` |
| 122 | +method on the `Query` component's instance. |
| 123 | + |
| 124 | +```js |
| 125 | +ssrPrepass(<App />, (_element, instance) => { |
| 126 | + if (instance !== undefined && typeof instance.fetchData === 'function') { |
| 127 | + return instance.fetchData() |
| 128 | + } |
| 129 | +}) |
| 130 | +``` |
| 131 | + |
| 132 | +Since we're now calling `fetchData` when it exists, which returns a `Promise` |
| 133 | +already, `ssrPrepass` will suspend on `<Query>` components. |
| 134 | + |
| 135 | +[More information can be found in Apollo's own docs](https://www.apollographql.com/docs/react/features/server-side-rendering.html#getDataFromTree) |
| 136 | + |
48 | 137 | ## Prior Art |
49 | 138 |
|
50 | | -This library is mostly based on `react-dom`'s `ReactPartialRenderer` |
51 | | -implementation. Its API and purpose is based on `react-apollo`'s |
52 | | -`getDataFromTree` function and hence it's also a successor to |
53 | | -`react-tree-walker`. |
| 139 | +This library is (luckily) not a reimplementation from scratch of |
| 140 | +React's server-side rendering. Instead it's mostly based on |
| 141 | +React's own server-side rendering logic that resides in its |
| 142 | +[`ReactPartialRenderer`](https://github.com/facebook/react/blob/13645d2/packages/react-dom/src/server/ReactPartialRenderer.js). |
| 143 | + |
| 144 | +The approach of doing an initial "data fetching pass" is inspired by: |
| 145 | + |
| 146 | +- [`react-apollo`'s `getDataFromTree`](https://github.com/apollographql/react-apollo/blob/master/src/getDataFromTree.ts) |
| 147 | +- [`react-tree-walker`](https://github.com/ctrlplusb/react-tree-walker) |
0 commit comments