Optimize Playground

This commit is contained in:
2024-01-24 14:35:29 +08:00
parent 55065cd5af
commit cade87febe
3 changed files with 89 additions and 61 deletions

View File

@@ -0,0 +1,77 @@
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw'
interface RenderProps {
iframeKey: string
compiledCode: string
onError?: (errorMsg: string) => void
}
interface IMessage {
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE'
msg: string
data: {
compiledCode: string
}
}
const getIframeUrl = (iframeRaw: string) => {
const shimsUrl = '//unpkg.com/es-module-shims/dist/es-module-shims.js'
// 判断浏览器是否支持esm 不支持esm就引入es-module-shims
const newIframeRaw =
typeof import.meta === 'undefined'
? iframeRaw.replace(
'<!-- es-module-shims -->',
`<script async src="${shimsUrl}"></script>`
)
: iframeRaw
return URL.createObjectURL(new Blob([newIframeRaw], { type: 'text/html' }))
}
const iframeUrl = getIframeUrl(iframeRaw)
const Render = ({ iframeKey, compiledCode, onError }: RenderProps) => {
const iframeRef = useRef<HTMLIFrameElement>(null)
const [loaded, setLoaded] = useState(false)
const handleMessage = ({ data }: { data: IMessage }) => {
const { type, msg } = data
switch (type) {
case 'LOADED':
setLoaded(true)
break
case 'ERROR':
onError?.(msg)
break
case 'DONE':
onError?.('')
}
}
useEffect(() => {
window.addEventListener('message', handleMessage)
return () => {
window.removeEventListener('message', handleMessage)
}
}, [])
useEffect(() => {
if (loaded) {
iframeRef.current?.contentWindow?.postMessage({
type: 'UPDATE',
data: { compiledCode }
} as IMessage)
}
}, [compiledCode, loaded])
return (
<iframe
key={iframeKey}
ref={iframeRef}
src={iframeUrl}
sandbox="allow-popups-to-escape-sandbox allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation allow-modals allow-same-origin"
/>
)
}
export default Render

View File

@@ -1,8 +1,8 @@
import '@/components/Playground/Output/Preview/preview.scss' import '@/components/Playground/Output/Preview/preview.scss'
import { IFiles, IImportMap } from '@/components/Playground/shared' import { IFiles, IImportMap } from '@/components/Playground/shared'
import Compiler from '@/components/Playground/compiler' import Compiler from '@/components/Playground/compiler'
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw' import { ENTRY_FILE_NAME } from '@/components/Playground/files'
import { ENTRY_FILE_NAME } from '@/components/Playground/files.ts' import Render from '@/components/Playground/Output/Preview/Render'
interface PreviewProps { interface PreviewProps {
iframeKey: string iframeKey: string
@@ -10,79 +10,27 @@ interface PreviewProps {
importMap: IImportMap importMap: IImportMap
} }
interface IMessage {
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE'
msg: string
data: {
compiledCode: string
}
}
const getIframeUrl = (iframeRaw: string) => {
const shimsUrl = '//unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js'
// 判断浏览器是否支持esm 不支持esm就引入es-module-shims
const newIframeRaw =
typeof import.meta === 'undefined'
? iframeRaw.replace(
'<!-- es-module-shims -->',
`<script async src="${shimsUrl}"></script>`
)
: iframeRaw
return URL.createObjectURL(new Blob([newIframeRaw], { type: 'text/html' }))
}
const iframeUrl = getIframeUrl(iframeRaw)
const Preview = ({ iframeKey, files, importMap }: PreviewProps) => { const Preview = ({ iframeKey, files, importMap }: PreviewProps) => {
const iframeRef = useRef<HTMLIFrameElement>(null)
const [errorMsg, setErrorMsg] = useState('') const [errorMsg, setErrorMsg] = useState('')
const [loaded, setLoaded] = useState(false) const [compiledCode, setCompiledCode] = useState('')
const handleMessage = ({ data }: { data: IMessage }) => { const handleOnError = (errorMsg: string) => {
const { type, msg } = data setErrorMsg(errorMsg)
switch (type) {
case 'LOADED':
setLoaded(true)
break
case 'ERROR':
setErrorMsg(msg)
break
case 'DONE':
setErrorMsg('')
} }
}
useEffect(() => {
window.addEventListener('message', handleMessage)
return () => {
window.removeEventListener('message', handleMessage)
}
}, [])
useEffect(() => { useEffect(() => {
Compiler.compile(files, importMap, [ENTRY_FILE_NAME]) Compiler.compile(files, importMap, [ENTRY_FILE_NAME])
.then((result) => { .then((result) => {
if (loaded) { setCompiledCode(result.outputFiles[0].text)
iframeRef.current?.contentWindow?.postMessage({
type: 'UPDATE',
data: { compiledCode: result.outputFiles[0].text }
} as IMessage)
}
}) })
.catch((e: Error) => { .catch((e: Error) => {
setErrorMsg(`编译失败:${e.message}`) setErrorMsg(`编译失败:${e.message}`)
}) })
}, [files, Compiler, loaded]) }, [files, Compiler])
return ( return (
<div data-component={'playground-preview'}> <div data-component={'playground-preview'}>
<iframe <Render iframeKey={iframeKey} compiledCode={compiledCode} onError={handleOnError} />
key={iframeKey}
ref={iframeRef}
src={iframeUrl}
sandbox="allow-popups-to-escape-sandbox allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation allow-modals allow-same-origin"
/>
{errorMsg && <div className={'playground-error-message'}>{errorMsg}</div>} {errorMsg && <div className={'playground-error-message'}>{errorMsg}</div>}
</div> </div>
) )

View File

@@ -14,7 +14,10 @@ class Compiler {
constructor() { constructor() {
try { try {
void esbuild void esbuild
.initialize({ worker: true, wasmURL: 'https://esm.sh/esbuild-wasm/esbuild.wasm' }) .initialize({
worker: true,
wasmURL: 'https://esm.sh/esbuild-wasm@0.19.12/esbuild.wasm'
})
.finally(() => { .finally(() => {
this.init = true this.init = true
}) })