react-simple-ssr/SSRComponent.js

171 lines
4.5 KiB
JavaScript

// Component capable of saving & restoring state during render
// (c) Vitaliy Filippov 2019+
// Version: 2021-08-08
// License: Dual-license MPL 1.1+ or GNU LGPL 3.0+
import React from 'react';
export default class SSRComponent extends React.PureComponent
{
constructor(props)
{
super(props);
if (props.store && props.store.state)
{
this.unserializeState(props.store.state);
delete props.store.state;
}
else
{
this.state = {};
this.init(props);
}
}
init(props)
{
}
static serializeStore(store)
{
store = { ...store };
if (store.instance)
{
store.state = store.instance.serializeState();
delete store.instance;
}
if (store.children)
{
store.children = { ...store.children };
for (const key in store.children)
{
store.children[key] = SSRComponent.serializeStore(store.children[key]);
}
}
return store;
}
serializeState()
{
return { ...this.state };
}
unserializeState(state)
{
this.state = state;
}
passStore(children)
{
walkTree(children, (child, key) =>
{
if (child && (child.type instanceof Object) && (child.type.prototype instanceof SSRComponent))
{
this.props.store.idx[key] = true;
let chstore = (this.props.store.children ||= {});
chstore = (chstore[key] ||= {});
child.props.store = chstore;
}
});
}
render()
{
if (this.props.store)
{
this.props.store.instance = this;
this.props.store.idx = {};
}
let children;
if (process.env.NODE_ENV === 'production')
{
children = this.doRender();
}
else
{
// Monkey-patch Object.freeze so fucking React can't freeze props
const freeze = Object.freeze;
Object.freeze = function() {};
children = this.doRender();
Object.freeze = freeze;
}
if (this.props.store)
{
this.passStore(children);
// Clear unused keys
for (let key in this.props.store.children)
{
if (!this.props.store.idx[key])
{
delete this.props.store.children[key];
}
}
delete this.props.store.idx;
}
return children;
}
doRender()
{
}
}
// Preact puts children directly on element, and React via props
const getChildren = element =>
element.props && element.props.children ? element.props.children : element.children ? element.children : undefined;
// Preact uses "nodeName", React uses "type"
const getType = element => element.type || element.nodeName;
const isReactElement = element => !!getType(element);
// Recurse a React Element tree, running the provided visitor against each element.
// Similar to React.Children.map(), but tracks keys.
function walkTree(tree, visitor, path = '')
{
if (!tree)
{
return;
}
if (tree instanceof Array)
{
// Process array, remembering keys and indices within series of elements of the same type
let index = 0, series = 0;
let lastType = null, lastHasKey = false;
for (let item of tree)
{
if (item)
{
let type = getType(item);
let key = item.key;
if (lastType == type && lastHasKey == (key != null))
index++;
else
{
series++;
index = 0;
lastType = type;
lastHasKey = (key != null);
}
if (key == null)
key = ':'+series+':'+index;
else
key = '['+key+']';
let typeName;
if (type && type.name)
typeName = type.name;
else if (typeof type == 'symbol')
typeName = type.toString();
else
typeName = type;
walkTree(item, visitor, path+'/'+typeName+key);
}
}
}
if (isReactElement(tree))
{
visitor(tree, path);
walkTree(getChildren(tree), visitor, path);
}
}