diff --git a/src/components/Playground/Preview/iframe.html b/src/components/Playground/Preview/iframe.html
new file mode 100644
index 0000000..9257ece
--- /dev/null
+++ b/src/components/Playground/Preview/iframe.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Preview
+
+
+
+
+
+
+
+
diff --git a/src/components/Playground/Preview/index.tsx b/src/components/Playground/Preview/index.tsx
new file mode 100644
index 0000000..f3b1325
--- /dev/null
+++ b/src/components/Playground/Preview/index.tsx
@@ -0,0 +1,95 @@
+import React, { useRef, useState } from 'react'
+import { IFiles } from '@/components/Playground/shared'
+import iframeRaw from '@/components/Playground/Preview/iframe.html?raw'
+import { useUpdatedEffect } from '@/util/hooks'
+import { IMPORT_MAP_FILE_NAME } from '@/components/Playground/files'
+import Compiler from '@/components/Playground/compiler'
+import '@/components/Playground/Preview/preview.scss'
+
+interface PreviewProps {
+ iframeKey: string
+ files: IFiles
+}
+
+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(
+ '',
+ ``
+ )
+ : iframeRaw
+ return URL.createObjectURL(new Blob([newIframeRaw], { type: 'text/html' }))
+}
+
+const iframeUrl = getIframeUrl(iframeRaw)
+
+const Preview: React.FC = ({ iframeKey, files }) => {
+ const iframeRef = useRef(null)
+ const [errorMsg, setErrorMsg] = useState('')
+ const [loaded, setLoaded] = useState(false)
+
+ const handleMessage = ({ data }: { data: IMessage }) => {
+ const { type, msg } = data
+ switch (type) {
+ case 'LOADED':
+ setLoaded(true)
+ break
+ case 'ERROR':
+ setErrorMsg(msg)
+ break
+ default:
+ setErrorMsg('')
+ }
+ }
+
+ useEffect(() => {
+ console.error(errorMsg)
+ }, [errorMsg])
+
+ useUpdatedEffect(() => {
+ window.addEventListener('message', handleMessage)
+
+ return () => {
+ window.removeEventListener('message', handleMessage)
+ }
+ }, [])
+
+ useUpdatedEffect(() => {
+ Compiler.compile(files, JSON.parse(files[IMPORT_MAP_FILE_NAME].value))
+ .then((result) => {
+ if (loaded) {
+ iframeRef.current?.contentWindow?.postMessage({
+ type: 'UPDATE',
+ data: { compiledCode: result.outputFiles[0].text }
+ } as IMessage)
+ }
+ })
+ .catch((e) => {
+ setErrorMsg(e)
+ })
+ }, [files, Compiler, loaded])
+
+ return (
+
+
+
+ )
+}
+
+export default Preview
diff --git a/src/components/Playground/Preview/preview.scss b/src/components/Playground/Preview/preview.scss
new file mode 100644
index 0000000..5836dce
--- /dev/null
+++ b/src/components/Playground/Preview/preview.scss
@@ -0,0 +1,7 @@
+[data-component=playground-preview] {
+ display: flex;
+ iframe {
+ border: none;
+ flex: 1;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Playground/Transform/index.tsx b/src/components/Playground/Transform/index.tsx
index 9162f14..23dd8be 100644
--- a/src/components/Playground/Transform/index.tsx
+++ b/src/components/Playground/Transform/index.tsx
@@ -2,27 +2,19 @@ import React from 'react'
import MonacoEditor from '@monaco-editor/react'
import { Loader } from 'esbuild-wasm'
import { useUpdatedEffect } from '@/util/hooks'
-import { IFiles, IImportMap, ITheme } from '@/components/Playground/shared'
+import { IFile, ITheme } from '@/components/Playground/shared'
import Compiler from '@/components/Playground/compiler'
import { cssToJs, jsonToJs } from '@/components/Playground/files'
import { MonacoEditorConfig } from '@/components/Playground/CodeEditor/Editor/monacoConfig'
-import { addReactImport } from '@/components/Playground/utils.ts'
+import { addReactImport } from '@/components/Playground/utils'
interface OutputProps {
- files: IFiles
- selectedFileName: string
+ file: IFile
theme?: ITheme
}
-const Preview: React.FC = ({ files, selectedFileName, theme }) => {
- const compiler = useRef()
- const [compileCode, setCompileCode] = useState('')
-
- useUpdatedEffect(() => {
- if (!compiler.current) {
- compiler.current = new Compiler()
- }
- }, [])
+const Transform: React.FC = ({ file, theme }) => {
+ const [compiledCode, setCompiledCode] = useState('')
const compile = (code: string, loader: Loader) => {
let _code = code
@@ -30,33 +22,21 @@ const Preview: React.FC = ({ files, selectedFileName, theme }) => {
_code = addReactImport(code)
}
- compiler.current
- ?.transform(_code, loader)
+ Compiler?.transform(_code, loader)
.then((value) => {
- setCompileCode(value.code)
+ setCompiledCode(value.code)
})
.catch((e) => {
console.error('编译失败', e)
})
-
- compiler.current
- ?.compile(files, {
- imports: {
- react: 'https://esm.sh/react@18.2.0',
- 'react-dom/client': 'https://esm.sh/react-dom@18.2.0'
- }
- })
- .then((r) => {
- console.log(r)
- })
}
useUpdatedEffect(() => {
- if (files[selectedFileName]) {
+ if (file) {
try {
- const code = files[selectedFileName].value
+ const code = file.value
- switch (files[selectedFileName].language) {
+ switch (file.language) {
case 'typescript':
compile(code, 'tsx')
break
@@ -64,32 +44,33 @@ const Preview: React.FC = ({ files, selectedFileName, theme }) => {
compile(code, 'jsx')
break
case 'css':
- setCompileCode(cssToJs(files[selectedFileName]))
+ setCompiledCode(cssToJs(file))
break
case 'json':
- setCompileCode(jsonToJs(files[selectedFileName]))
+ setCompiledCode(jsonToJs(file))
break
case 'xml':
- setCompileCode(code)
+ setCompiledCode(code)
}
} catch (e) {
- setCompileCode('')
+ console.log(e)
+ setCompiledCode('')
}
} else {
- setCompileCode('')
+ setCompiledCode('')
}
- }, [files[selectedFileName]])
+ }, [file, Compiler])
return (
<>
>
)
}
-export default Preview
+export default Transform
diff --git a/src/components/Playground/compiler.ts b/src/components/Playground/compiler.ts
index 854c0f5..c86aa76 100644
--- a/src/components/Playground/compiler.ts
+++ b/src/components/Playground/compiler.ts
@@ -1,9 +1,9 @@
import esbuild, { Loader, OnLoadArgs, Plugin, PluginBuild } from 'esbuild-wasm'
-import wasm from 'esbuild-wasm/esbuild.wasm?url'
import { IFiles, IImportMap } from '@/components/Playground/shared.ts'
import { cssToJs, ENTRY_FILE_NAME, jsonToJs } from '@/components/Playground/files.ts'
import localforage from 'localforage'
import axios from 'axios'
+import { addReactImport } from '@/components/Playground/utils.ts'
class Compiler {
private init = false
@@ -14,11 +14,13 @@ class Compiler {
constructor() {
try {
- void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => {
- this.init = true
- })
+ void esbuild
+ .initialize({ worker: true, wasmURL: 'https://esm.sh/esbuild-wasm/esbuild.wasm' })
+ .finally(() => {
+ this.init = true
+ })
} catch (e) {
- /* empty */
+ this.init = true
}
}
@@ -107,6 +109,24 @@ class Compiler {
}
}
+ if (/^https?:\/\/.*/.test(args.path)) {
+ return {
+ namespace: 'default',
+ path: args.path
+ }
+ }
+
+ if (
+ args.path.includes('./') ||
+ args.path.includes('../') ||
+ args.path.startsWith('/')
+ ) {
+ return {
+ namespace: 'default',
+ path: new URL(args.path, args.resolveDir.substring(1)).href
+ }
+ }
+
const path = importMap.imports[args.path]
if (!path) {
@@ -147,12 +167,12 @@ class Compiler {
if (args.path === ENTRY_FILE_NAME) {
return {
loader: 'tsx',
- contents: files[ENTRY_FILE_NAME].value
+ contents: addReactImport(files[ENTRY_FILE_NAME].value)
}
}
if (files[args.path]) {
- const contents = files[args.path].value
+ const contents = addReactImport(files[args.path].value)
if (args.path.endsWith('.jsx')) {
return {
loader: 'jsx',
@@ -187,10 +207,10 @@ class Compiler {
const result: esbuild.OnLoadResult = {
loader: 'jsx',
contents: data,
- resolveDir: new URL('./', request.responseURL).pathname
+ resolveDir: request.responseURL
}
- await this.fileCache.setItem(args.path, request)
+ await this.fileCache.setItem(args.path, result)
return result
})
@@ -199,4 +219,4 @@ class Compiler {
}
}
-export default Compiler
+export default new Compiler()
diff --git a/src/pages/OnlineEditor.tsx b/src/pages/OnlineEditor.tsx
index 07c8e0b..248c26b 100644
--- a/src/pages/OnlineEditor.tsx
+++ b/src/pages/OnlineEditor.tsx
@@ -1,10 +1,10 @@
import React, { useState } from 'react'
import CodeEditor from '@/components/Playground/CodeEditor'
-import { initFiles, MAIN_FILE_NAME } from '@/components/Playground/files'
+import { IMPORT_MAP_FILE_NAME, initFiles, MAIN_FILE_NAME } from '@/components/Playground/files'
import { IFiles } from '@/components/Playground/shared'
import FitFullscreen from '@/components/common/FitFullscreen'
import FlexBox from '@/components/common/FlexBox'
-import Transform from '@/components/Playground/Transform'
+import Preview from '@/components/Playground/Preview'
const OnlineEditor: React.FC = () => {
const [files, setFiles] = useState(initFiles)
@@ -23,7 +23,7 @@ const OnlineEditor: React.FC = () => {
onRenameFile={(_, __, files) => setFiles(files)}
onChangeFileContent={(_, __, files) => setFiles(files)}
/>
-
+
>