react-async-ssr
Advanced tools
Comparing version 0.4.4 to 0.4.5
# Changelog | ||
## 0.4.5 | ||
Features: | ||
* Call `[ON_MOUNT]` on promises | ||
Other: | ||
* Tests: Refactor with expect extensions | ||
## 0.4.4 | ||
@@ -4,0 +14,0 @@ |
@@ -12,3 +12,4 @@ /* -------------------- | ||
TYPE_PROMISE: 'promise', | ||
TYPE_FALLBACK: 'fallback', | ||
TYPE_TEXT: 'text' | ||
}; |
@@ -90,3 +90,12 @@ /* -------------------- | ||
if (suspenseNode.suspended) { | ||
// Abort promise | ||
followAndAbort(promise); | ||
// Create tree node | ||
if (!this.fallbackFast) { | ||
const node = this.createNode(TYPE_PROMISE); | ||
node.resolved = true; | ||
node.promise = promise; | ||
} | ||
return; | ||
@@ -126,2 +135,3 @@ } | ||
node.resolved = true; | ||
node.promise = promise; | ||
@@ -140,5 +150,5 @@ node.frame = frame; | ||
node = this.createNodeWithStackState(TYPE_PROMISE, frame); | ||
node.resolved = false; | ||
node.promise = promise; | ||
node.element = element; | ||
node.promise = promise; | ||
node.resolved = false; | ||
@@ -176,5 +186,12 @@ this.numAwaiting++; | ||
handleNoSsrPromise(promise) { | ||
// Abort this promise | ||
// Abort promise | ||
followAndAbort(promise); | ||
// Create tree node | ||
if (!this.fallbackFast) { | ||
const node = this.createNode(TYPE_PROMISE); | ||
node.resolved = true; | ||
node.promise = promise; | ||
} | ||
// Suspend suspense | ||
@@ -181,0 +198,0 @@ const {suspenseNode} = this; |
@@ -12,3 +12,3 @@ /* -------------------- | ||
// Imports | ||
const {TYPE_SUSPENSE, TYPE_PROMISE, TYPE_TEXT} = require('./constants'), | ||
const {TYPE_SUSPENSE, TYPE_PROMISE, TYPE_FALLBACK, TYPE_TEXT} = require('./constants'), | ||
{ReactDOMServerRenderer, isDev} = require('./rendererSuper'), | ||
@@ -156,5 +156,6 @@ shimMethods = require('./shim'), | ||
convertNodeToFallback(node) { | ||
node.type = null; | ||
node.type = TYPE_FALLBACK; | ||
node.fallback = null; | ||
node.children.length = 0; | ||
node.suspendedChildren = node.children; | ||
node.children = []; | ||
} | ||
@@ -229,3 +230,3 @@ | ||
// NB Do not leak `this` to callback | ||
const out = treeToHtml(this.tree, this.makeStaticMarkup); | ||
const out = treeToHtml(this.tree, this.makeStaticMarkup, this.fallbackFast); | ||
const {callback} = this; | ||
@@ -232,0 +233,0 @@ callback(null, out); |
@@ -11,3 +11,4 @@ /* -------------------- | ||
NO_SSR: '__reactAsyncSsrNoSsr', | ||
ABORT: '__reactAsyncSsrAbort' | ||
ABORT: '__reactAsyncSsrAbort', | ||
ON_MOUNT: '__reactAsyncSsrOnMount' | ||
}; |
@@ -9,30 +9,73 @@ /* -------------------- | ||
// Imports | ||
const {TYPE_TEXT} = require('./constants'), | ||
walkTree = require('./walkTree'); | ||
const {TYPE_TEXT, TYPE_PROMISE, TYPE_FALLBACK} = require('./constants'), | ||
{ON_MOUNT} = require('./symbols'); | ||
// Exports | ||
class Walker { | ||
constructor(tree, makeStaticMarkup, fallbackFast) { | ||
this.tree = tree; | ||
this.makeStaticMarkup = makeStaticMarkup; | ||
this.fallbackFast = fallbackFast; | ||
this.out = ''; | ||
this.lastText = false; | ||
} | ||
walk() { | ||
this.process(this.tree, false); | ||
return this.out; | ||
} | ||
process(node, suspended) { | ||
const {type} = node; | ||
if (type === TYPE_TEXT) { | ||
// Flush output. | ||
// If is within suspended Suspense boundary, do not output as will not render. | ||
if (!suspended) { | ||
const {out} = node; | ||
if (this.lastText && out.slice(0, 1) !== '<') this.out += '<!-- -->'; | ||
this.out += out; | ||
if (!this.makeStaticMarkup) this.lastText = out.slice(-1) !== '>'; | ||
} | ||
return; | ||
} | ||
if (type === TYPE_PROMISE) { | ||
const {promise} = node; | ||
// Call `[ON_MOUNT]()` on promise with `true` if being rendered, | ||
// or `false` if not (inside suspensed Suspense boundary) | ||
if (typeof promise[ON_MOUNT] === 'function') promise[ON_MOUNT](!suspended); | ||
// If inside suspended boundary, exit - promise is not resolved, | ||
// so children will not render | ||
if (suspended) return; | ||
} else if (type === TYPE_FALLBACK && !this.fallbackFast) { | ||
// Is a suspended suspense node. | ||
// Iterate over suspended children to call `[ON_MOUNT]()` callbacks on promises. | ||
for (let child of node.suspendedChildren) { | ||
this.process(child, true); | ||
} | ||
} | ||
for (let child of node.children) { | ||
this.process(child, suspended); | ||
} | ||
} | ||
} | ||
/** | ||
* Render node tree to HTML. | ||
* `[ON_MOUNT]()` is called on all promises that have this method. | ||
* All promise nodes must be resolved before calling this. | ||
* @param {Object} tree - Node tree | ||
* @param {boolean} makeStaticMarkup - `true` for `renderToStaticMarkup()`-style rendering | ||
* @param {boolean} fallbackFast - `fallbackFast` option | ||
* @returns {string} - HTML output | ||
*/ | ||
function treeToHtml(tree, makeStaticMarkup) { | ||
let out = '', | ||
lastText = false; | ||
walkTree(tree, node => { | ||
if (node.type !== TYPE_TEXT) return; | ||
const thisOut = node.out; | ||
if (lastText && thisOut.slice(0, 1) !== '<') out += '<!-- -->'; | ||
out += thisOut; | ||
if (!makeStaticMarkup) lastText = thisOut.slice(-1) !== '>'; | ||
}); | ||
return out; | ||
function treeToHtml(tree, makeStaticMarkup, fallbackFast) { | ||
const walker = new Walker(tree, makeStaticMarkup, fallbackFast); | ||
return walker.walk(); | ||
} | ||
module.exports = treeToHtml; |
{ | ||
"name": "react-async-ssr", | ||
"version": "0.4.4", | ||
"version": "0.4.5", | ||
"description": "Render React Suspense on server", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -186,2 +186,16 @@ [![NPM version](https://img.shields.io/npm/v/react-async-ssr.svg)](https://www.npmjs.com/package/react-async-ssr) | ||
### Tracking components being used | ||
If promises thrown have an `[ON_MOUNT]()` method, they are called. | ||
`[ON_MOUNT]` is a symbol which can be imported from `react-async-ssr/symbols`. | ||
`[ON_MOUNT]()` is called in the order components will be rendered on the client during hydration. This may not be the same order as the components are rendered on the server, if lazy components are nested within each other. In some cases, a component may render on the server, but not at all on the client during hydration, due to a Suspense fallback being triggered (see below). | ||
`[ON_MOUNT]()` is called with `true` if the element will be rendered on client, or `false` if it will not. `false` happens if the promise was thrown by a component which ends up being inside a Suspense boundary whose fallback is triggered, so the component is not rendered. | ||
Only components whose promise's `[ON_MOUNT]()` method has been called with `true` should have their imported file/data provided on client side so they can be rehydrated synchronously. Those called with `false` should be allowed to load file/data asynchronously. | ||
This is to prevent unnecessary files/data being loaded on the client prior to hydration, when they won't actually be used in hydration. Doing that would increase the time user has to wait before hydration. | ||
### Preventing components rendering on server side | ||
@@ -188,0 +202,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47593
981
316