-
-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathrenderer.ts
More file actions
102 lines (78 loc) · 2.7 KB
/
renderer.ts
File metadata and controls
102 lines (78 loc) · 2.7 KB
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
import * as React from 'react'
import * as ReactDOM from 'react-dom'
// React 18+ root type
interface Root {
render(children: React.ReactNode): void
unmount(): void
}
export type HasLegacyRender = (typeof ReactDOM) extends { render(...args: any[]): any } ? true : false
export type CreateRoot = (container: Element | DocumentFragment) => Root
type ReactDOMRenderer = (
element: React.ReactElement,
container: HTMLElement
) => React.Component | Element
export type Renderer = { mount: ReactDOMRenderer, unmount: (container: HTMLElement) => void }
export function getRenderer(props?: { createRoot?: CreateRoot }): Renderer {
const createRoot = props?.createRoot
const wrappers = new WeakMap<HTMLElement, HTMLElement>()
function getWrapper(container: HTMLElement) {
const wrapper = wrappers.get(container)
if (wrapper) return wrapper
const span = document.createElement('span')
container.appendChild(span)
wrappers.set(container, span)
return span
}
function removeWrapper(container: HTMLElement): void {
const wrapper = wrappers.get(container)
if (wrapper) {
wrapper.remove()
wrappers.delete(container)
}
}
// React 18+ path with createRoot
if (createRoot) {
const roots = new WeakMap<HTMLElement, Root>()
return {
mount: (element: React.ReactElement, container: HTMLElement) => {
const wrapper = getWrapper(container)
let root = roots.get(wrapper)
if (!root) {
root = createRoot(wrapper)
roots.set(wrapper, root)
}
root.render(element)
return wrapper.firstElementChild ?? wrapper
},
unmount: (container: HTMLElement) => {
const wrapper = getWrapper(container)
const root = roots.get(wrapper)
if (root) {
root.unmount()
roots.delete(wrapper)
}
removeWrapper(container)
}
}
}
// React 16-17 legacy path with ReactDOM.render
return {
mount: (element: React.ReactElement, container: HTMLElement) => {
const wrapper = getWrapper(container)
if ('render' in ReactDOM && typeof ReactDOM.render === 'function') {
const result = ReactDOM.render(element, wrapper) as React.Component | Element
return result || wrapper
}
throw new Error('ReactDOM.render is not available')
},
unmount: (container: HTMLElement) => {
const wrapper = getWrapper(container)
if ('unmountComponentAtNode' in ReactDOM && typeof ReactDOM.unmountComponentAtNode === 'function') {
ReactDOM.unmountComponentAtNode(wrapper)
} else {
throw new Error('ReactDOM.unmountComponentAtNode is not available')
}
removeWrapper(container)
}
}
}