forked from topcoder-platform/topcoder-react-utils
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.jsx
121 lines (103 loc) · 3.2 KB
/
index.jsx
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
/* eslint-disable react/jsx-props-no-spreading */
/* global document */
import PT from 'prop-types';
import {
lazy,
Suspense,
useEffect,
useRef,
} from 'react';
import { getGlobalState } from '@dr.pogodin/react-global-state';
import { newBarrier } from 'utils/Barrier';
import {
IS_CLIENT_SIDE,
IS_SERVER_SIDE,
getBuildInfo,
} from 'utils/isomorphy';
let chunkGroups;
let styleSheetUsageCounters;
if (IS_CLIENT_SIDE) {
// eslint-disable-next-line global-require
chunkGroups = require('client/getInj').default().CHUNK_GROUPS || {};
styleSheetUsageCounters = {};
}
export default function CodeSplit({
children,
chunkName,
getComponent,
placeholder,
...props
}) {
const { current: heap } = useRef({
mounted: false,
pendingStyles: [],
});
const { publicPath } = getBuildInfo();
// TODO: Not sure whether it is fine for the inner React.lazy() mechanics
// if we dynamically create the lazy component inside a render of another
// component, or does it expect we only create it once on outside of any
// component.
const LazyComponent = lazy(async () => {
const res = await getComponent();
if (heap.pendingStyles.length) await Promise.all(heap.pendingStyles);
return res.default ? res : { default: res };
});
if (IS_SERVER_SIDE) {
const { chunks } = getGlobalState().ssrContext;
if (chunks.includes(chunkName)) {
throw Error(`Chunk name clash for "${chunkName}"`);
} else chunks.push(chunkName);
} else if (!heap.mounted) {
heap.mounted = true;
chunkGroups[chunkName].forEach((asset) => {
if (!asset.endsWith('.css')) return;
const path = `${publicPath}/${asset}`;
let link = document.querySelector(`link[href="${path}"]`);
if (!link) {
link = document.createElement('link');
link.setAttribute('href', path);
link.setAttribute('rel', 'stylesheet');
const barrier = newBarrier();
link.onload = barrier.resolve;
// Even if the style load failed, still allow to mount the component,
// abeit with broken styling.
link.onerror = barrier.resolve;
heap.pendingStyles.push(barrier);
const head = document.querySelector('head');
head.appendChild(link);
}
const count = styleSheetUsageCounters[path] || 0;
styleSheetUsageCounters[path] = count + 1;
});
}
// This effectively fires only once, just before the component unmounts.
useEffect(() => () => {
heap.mounted = false;
chunkGroups[chunkName].forEach((item) => {
if (!item.endsWith('.css')) return;
const path = `${publicPath}/${item}`;
if (--styleSheetUsageCounters[path] <= 0) {
const link = document.querySelector(`link[href="${path}"]`);
const head = document.querySelector('head');
head.removeChild(link);
}
});
}, [chunkName, heap, publicPath]);
return (
<Suspense fallback={placeholder}>
<LazyComponent {...props}>
{children}
</LazyComponent>
</Suspense>
);
}
CodeSplit.propTypes = {
children: PT.node,
chunkName: PT.string.isRequired,
getComponent: PT.func.isRequired,
placeholder: PT.node,
};
CodeSplit.defaultProps = {
children: undefined,
placeholder: undefined,
};