2021-08-08 00:46:45 +03:00
|
|
|
// Component capable of saving & restoring state during render
|
|
|
|
// (c) Vitaliy Filippov 2019+
|
2021-08-08 12:22:48 +03:00
|
|
|
// Version: 2021-08-08
|
2021-08-08 00:46:45 +03:00
|
|
|
// 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 };
|
2021-08-08 12:22:48 +03:00
|
|
|
for (const key in store.children)
|
2021-08-08 00:46:45 +03:00
|
|
|
{
|
2021-08-08 12:22:48 +03:00
|
|
|
store.children[key] = SSRComponent.serializeStore(store.children[key]);
|
2021-08-08 00:46:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return store;
|
|
|
|
}
|
|
|
|
|
|
|
|
serializeState()
|
|
|
|
{
|
|
|
|
return { ...this.state };
|
|
|
|
}
|
|
|
|
|
|
|
|
unserializeState(state)
|
|
|
|
{
|
|
|
|
this.state = state;
|
|
|
|
}
|
|
|
|
|
|
|
|
passStore(children)
|
|
|
|
{
|
2021-08-08 12:22:48 +03:00
|
|
|
walkTree(children, (child, key) =>
|
2021-08-08 00:46:45 +03:00
|
|
|
{
|
|
|
|
if (child && (child.type instanceof Object) && (child.type.prototype instanceof SSRComponent))
|
|
|
|
{
|
2021-08-08 12:22:48 +03:00
|
|
|
this.props.store.idx[key] = true;
|
2021-08-08 00:46:45 +03:00
|
|
|
let chstore = (this.props.store.children ||= {});
|
|
|
|
chstore = (chstore[key] ||= {});
|
2021-08-08 12:22:48 +03:00
|
|
|
child.props.store = chstore;
|
2021-08-08 00:46:45 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
render()
|
|
|
|
{
|
|
|
|
if (this.props.store)
|
|
|
|
{
|
|
|
|
this.props.store.instance = this;
|
|
|
|
this.props.store.idx = {};
|
|
|
|
}
|
2021-08-08 12:22:48 +03:00
|
|
|
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;
|
|
|
|
}
|
2021-08-08 00:46:45 +03:00
|
|
|
if (this.props.store)
|
|
|
|
{
|
2021-08-08 12:22:48 +03:00
|
|
|
this.passStore(children);
|
2021-08-08 00:46:45 +03:00
|
|
|
// Clear unused keys
|
2021-08-08 12:22:48 +03:00
|
|
|
for (let key in this.props.store.children)
|
2021-08-08 00:46:45 +03:00
|
|
|
{
|
2021-08-08 12:22:48 +03:00
|
|
|
if (!this.props.store.idx[key])
|
2021-08-08 00:46:45 +03:00
|
|
|
{
|
2021-08-08 12:22:48 +03:00
|
|
|
delete this.props.store.children[key];
|
2021-08-08 00:46:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
delete this.props.store.idx;
|
|
|
|
}
|
|
|
|
return children;
|
|
|
|
}
|
|
|
|
|
|
|
|
doRender()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
2021-08-08 12:22:48 +03:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|