-
Notifications
You must be signed in to change notification settings - Fork 1
/
useCodePreview.tsx
98 lines (82 loc) · 2.32 KB
/
useCodePreview.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
import * as Comlink from "comlink"
import * as React from "react"
import { WorkerApi } from "../workers/transpile.worker"
import { render } from "react-dom"
interface Props {
code: string
inline?: boolean
scope?: Record<string, any>
transform?: (code: string) => string
onChange?: () => void
onError?: (error: string) => void
deps?: any[]
}
export default function useCodePreview({
code,
scope = {},
inline = false,
onChange,
transform,
onError,
deps = [],
}: Props) {
const previewRef = React.useRef<HTMLDivElement>()
const rWorker = React.useRef<Worker>()
const rWorkerAPI = React.useRef<Comlink.Remote<WorkerApi>>()
const [error, setError] = React.useState<string | null>(null)
React.useEffect(() => {
rWorker.current = new Worker("../workers/transpile.worker", {
type: "module",
})
rWorkerAPI.current = Comlink.wrap<WorkerApi>(rWorker.current)
rWorkerAPI.current?.start()
return () => {
rWorkerAPI.current?.stop()
rWorker.current?.terminate()
}
}, [])
React.useEffect(() => {
const elm = previewRef.current
setError(null)
if (!elm) {
setError("No container element found.")
return
}
let transformed = code
if (transform) {
transformed = transform(code)
}
if (inline) {
transformed = `function App() { return ${transformed} }`
} else {
if (!/App/.test(transformed)) {
setError("Your code must include a component named App.")
return
}
}
transformed += `; try { render(<App/>, elm) } catch (e) { console.error(e) };`
rWorkerAPI.current
?.transpile(transformed, ["jsx"])
.then((result) => {
if (result.code) {
try {
const args = ["React", "render", "elm", ...Object.keys(scope)]
const vArgs = [React, render, elm, ...Object.values(scope)]
const fn = new Function(...args, result.code)
fn.call(null, ...vArgs)
onChange && onChange()
} catch (e) {
setError(e.message)
onError && onError(e.message)
}
} else {
setError("Encountered an error")
}
})
.catch((e: Error) => {
setError(e.message)
onError && onError(e.message)
})
}, [code, inline, ...deps])
return { previewRef, error }
}