-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathloader.tsx
102 lines (94 loc) · 2.44 KB
/
loader.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
/**
* @module react/loader
* Description: Convenience React root component mounter
*/
import {
ReactNode,
} from 'react';
import {
createRoot,
} from 'react-dom/client';
import type {
Callable,
} from '../types';
/**
* Name: RenderFunc
* Description: Logger API type definition
* @private
* @typedef {typeof Function} RenderFunc
*/
type RenderFunc = Callable<RenderFuncParams, void>;
/**
* Description: Logger API type definition
* @public
* @template F - F: log line format
* @typedef {Object}
* @property {typeof Function} log
* @property {typeof Function} updateOptions
*/
type RenderFuncParams = {
Component: ReactNode;
rootElement: RootElement;
};
type RootElement = Element | DocumentFragment | string;
const defaultRender = (params: RenderFuncParams) => {
const {
Component,
rootElement,
} = params;
let el;
if (typeof rootElement === 'string') {
el = document.body.querySelector(rootElement);
if (!el) {
throw new Error(`Root Element query selector is invalid: ${rootElement}`);
}
} else {
el = rootElement;
}
const root = createRoot(el);
root.render(Component);
};
type MountRootComponentParams = {
logger?: Callable<any[], void>;
renderFunc?: RenderFunc;
} & RenderFuncParams;
/**
* Universal loader for Component root
* @param Comp
* @param rootElement
*/
export const mountRootComponent = (params: MountRootComponentParams) : boolean => {
const {
Component,
logger = globalThis.console.warn,
rootElement,
renderFunc = defaultRender,
} = params;
try {
/**
* https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
* There are three states: loading, interactive, and complete.
* The DOM is accessible in the 'complete', and 'interactive' states.
*/
if (['complete', 'interactive'].includes(document.readyState)) {
// Add render to event queue
setTimeout(() => renderFunc({ Component, rootElement }), 0);
} else {
/**
* DOMContentLoaded event has not yet fired. Still in 'loading' readyState.
* Add an event listener that removes itself when complete.
*/
const loader = () => {
renderFunc({ Component, rootElement });
globalThis.removeEventListener('DOMContentLoaded', loader);
};
globalThis.addEventListener('DOMContentLoaded', loader);
}
// Success-ish
return true;
} catch (err: any) {
logger(err);
// Failure
return false;
}
};