-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathwithESI.tsx
130 lines (114 loc) · 3.33 KB
/
withESI.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import PropTypes from "prop-types";
import type { WeakValidationMap, ComponentType, ComponentClass } from "react";
import React from "react";
declare global {
interface Window {
__REACT_ESI__: { [s: string]: object };
}
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface Process {
browser?: boolean;
}
}
}
interface IWithESIProps {
esi?: {
attrs?: {
[key: string]: string | null;
};
};
}
// Prevent bundlers to bundle server.js
const safeRequireServer = () => {
try {
// Necessary for NextJS
return eval("require('react-esi/lib/server')");
} catch (error) {
// Necessary for Express and others
return eval("require('./server')");
}
};
const isClient = () => {
return (
(typeof process !== "undefined" && process?.browser) ||
typeof window !== "undefined"
);
};
const isServer = () => !isClient();
interface State {
childProps: object;
initialChildPropsLoaded: boolean;
}
/**
* Higher Order Component generating a <esi:include> tag server-side,
* and rendering the wrapped component client-side.
*/
export default function withESI<P>(
WrappedComponent: ComponentType<P>,
fragmentID: string
): ComponentClass<IWithESIProps & P> {
return class WithESI extends React.Component<P & IWithESIProps, State> {
public static WrappedComponent = WrappedComponent;
public static displayName = `WithESI(${
WrappedComponent.displayName || WrappedComponent.name || "Component"
})`;
public static propTypes = {
esi: PropTypes.shape({
attrs: PropTypes.objectOf(PropTypes.string), // extra attributes to add to the <esi:include> tag
}),
} as unknown as WeakValidationMap<IWithESIProps & P>;
public state: State = {
childProps: {},
initialChildPropsLoaded: true,
};
private esi = {};
constructor(props: P & IWithESIProps) {
super(props);
const { esi, ...childProps } = props;
this.esi = esi || {};
this.state.childProps = childProps;
if (isServer()) {
return;
}
if (window.__REACT_ESI__?.[fragmentID]) {
// Inject server-side computed initial props
this.state.childProps = {
...window.__REACT_ESI__[fragmentID],
...this.state.childProps,
};
return;
}
// TODO: add support for getServerSideProps
if ("getInitialProps" in WrappedComponent) {
// No server-side rendering for this component, getInitialProps will be called during componentDidMount
this.state.initialChildPropsLoaded = false;
}
}
public componentDidMount() {
if (this.state.initialChildPropsLoaded) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(WrappedComponent as any)
.getInitialProps({ props: this.state.childProps })
.then((initialProps: object) =>
this.setState({
childProps: initialProps,
initialChildPropsLoaded: true,
})
);
}
public render() {
if (isClient()) {
return (
<WrappedComponent
{...(this.state.childProps as JSX.IntrinsicAttributes & P)}
/>
);
}
const server = safeRequireServer();
return server.createIncludeElement(fragmentID, this.props, this.esi);
}
};
}