Add compiler
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"axios": "^1.6.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"esbuild-wasm": "^0.19.11",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fflate": "^0.8.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
@@ -2228,7 +2229,7 @@
|
||||
"resolved": "https://registry.npmmirror.com/@typescript/ata/-/ata-0.9.4.tgz",
|
||||
"integrity": "sha512-PaJ16WouPV/SaA+c0tnOKIqYq24+m93ipl/e0Dkxuianer+ibc5b0/6ZgfCFF8J7QEp57dySMSP9nWOFaCfJnw==",
|
||||
"peerDependencies": {
|
||||
"typescript": "^4.4.4"
|
||||
"typescript": ">=4.4.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
@@ -3302,6 +3303,17 @@
|
||||
"@esbuild/win32-x64": "0.19.8"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-wasm": {
|
||||
"version": "0.19.11",
|
||||
"resolved": "https://registry.npmmirror.com/esbuild-wasm/-/esbuild-wasm-0.19.11.tgz",
|
||||
"integrity": "sha512-MIhnpc1TxERUHomteO/ZZHp+kUawGEc03D/8vMHGzffLvbFLeDe6mwxqEZwlqBNY7SLWbyp6bBQAcCen8+wpjQ==",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"axios": "^1.6.2",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"esbuild-wasm": "^0.19.11",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fflate": "^0.8.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
|
||||
78
src/components/Playground/Preview/index.tsx
Normal file
78
src/components/Playground/Preview/index.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { IFiles } from '@/components/Playground/shared.ts'
|
||||
import { useUpdatedEffect } from '@/util/hooks.tsx'
|
||||
import Compiler from '@/components/Playground/compiler.ts'
|
||||
import { Loader } from 'esbuild-wasm'
|
||||
import { cssToJs, jsonToJs } from '@/components/Playground/files.ts'
|
||||
|
||||
interface OutputProps {
|
||||
files: IFiles
|
||||
}
|
||||
|
||||
const Preview: React.FC<OutputProps> = ({ files }) => {
|
||||
const compiler = useRef<Compiler>()
|
||||
const [compileCode, setCompileCode] = useState('')
|
||||
const [fileName, setFileName] = useState('main.tsx')
|
||||
|
||||
const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFileName(e.target.value)
|
||||
}
|
||||
|
||||
useUpdatedEffect(() => {
|
||||
if (!compiler.current) {
|
||||
compiler.current = new Compiler()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useUpdatedEffect(() => {
|
||||
if (files[fileName]) {
|
||||
try {
|
||||
const file = files[fileName]
|
||||
|
||||
let loader: Loader
|
||||
let code = file.value
|
||||
|
||||
switch (file.language) {
|
||||
case 'typescript':
|
||||
loader = 'tsx'
|
||||
break
|
||||
case 'javascript':
|
||||
loader = 'jsx'
|
||||
break
|
||||
case 'css':
|
||||
code = cssToJs(file)
|
||||
loader = 'js'
|
||||
break
|
||||
case 'json':
|
||||
code = jsonToJs(file)
|
||||
loader = 'js'
|
||||
break
|
||||
case 'xml':
|
||||
loader = 'default'
|
||||
}
|
||||
|
||||
compiler.current
|
||||
?.transform(code, loader)
|
||||
.then((value) => {
|
||||
setCompileCode(value.code)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('编译失败', e)
|
||||
})
|
||||
} catch (e) {
|
||||
setCompileCode('')
|
||||
}
|
||||
} else {
|
||||
setCompileCode('')
|
||||
}
|
||||
}, [fileName, files])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AntdInput.TextArea value={compileCode} />
|
||||
<AntdInput value={fileName} onChange={handleOnChange} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Preview
|
||||
30
src/components/Playground/compiler.ts
Normal file
30
src/components/Playground/compiler.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import esbuild, { Loader } from 'esbuild-wasm'
|
||||
import wasm from 'esbuild-wasm/esbuild.wasm?url'
|
||||
|
||||
class Compiler {
|
||||
private init = false
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => {
|
||||
this.init = true
|
||||
})
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
transform = (code: string, loader: Loader) =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
const timer = setInterval(() => {
|
||||
if (this.init) {
|
||||
clearInterval(timer)
|
||||
resolve(true)
|
||||
}
|
||||
}, 100)
|
||||
}).then(() => {
|
||||
return esbuild.transform(code, { loader })
|
||||
})
|
||||
}
|
||||
|
||||
export default Compiler
|
||||
@@ -3,7 +3,7 @@ import importMap from '@/components/Playground/template/import-map.json?raw'
|
||||
import AppCss from '@/components/Playground/template/src/App.css?raw'
|
||||
import App from '@/components/Playground/template/src/App.tsx?raw'
|
||||
import main from '@/components/Playground/template/src/main.tsx?raw'
|
||||
import { IFiles } from '@/components/Playground/shared'
|
||||
import { IFile, IFiles } from '@/components/Playground/shared'
|
||||
|
||||
export const MAIN_FILE_NAME = 'App.tsx'
|
||||
export const IMPORT_MAP_FILE_NAME = 'import-map.json'
|
||||
@@ -51,6 +51,40 @@ export const getFilesFromUrl = () => {
|
||||
return files
|
||||
}
|
||||
|
||||
export const getModuleFile = (files: IFiles, moduleName: string) => {
|
||||
let _moduleName = moduleName.split('./').pop() || ''
|
||||
if (!_moduleName.includes('.')) {
|
||||
const realModuleName = Object.keys(files).find((key) =>
|
||||
key.split('.').includes(_moduleName)
|
||||
)
|
||||
if (realModuleName) _moduleName = realModuleName
|
||||
}
|
||||
return files[_moduleName]
|
||||
}
|
||||
|
||||
export const jsonToJs = (file: IFile) => {
|
||||
const js = `export default ${file.value}`
|
||||
return URL.createObjectURL(new Blob([js], { type: 'application/javascript' }))
|
||||
}
|
||||
|
||||
export const cssToJs = (file: IFile) => {
|
||||
const randomId = new Date().getTime()
|
||||
const js = `
|
||||
(() => {
|
||||
let stylesheet = document.getElementById('style_${randomId}_${file.name}');
|
||||
if (!stylesheet) {
|
||||
stylesheet = document.createElement('style')
|
||||
stylesheet.setAttribute('id', 'style_${randomId}_${file.name}')
|
||||
document.head.appendChild(stylesheet)
|
||||
}
|
||||
const styles = document.createTextNode(\`${file.value}\`)
|
||||
stylesheet.innerHTML = ''
|
||||
stylesheet.appendChild(styles)
|
||||
})()
|
||||
`
|
||||
return URL.createObjectURL(new Blob([js], { type: 'application/javascript' }))
|
||||
}
|
||||
|
||||
export const initFiles: IFiles = getFilesFromUrl() || {
|
||||
[ENTRY_FILE_NAME]: {
|
||||
name: ENTRY_FILE_NAME,
|
||||
|
||||
@@ -2,19 +2,27 @@ import React from 'react'
|
||||
import CodeEditor from '@/components/Playground/CodeEditor'
|
||||
import { initFiles } from '@/components/Playground/files'
|
||||
import { IFiles } from '@/components/Playground/shared'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
||||
import FlexBox from '@/components/common/FlexBox.tsx'
|
||||
import Preview from '@/components/Playground/Preview'
|
||||
|
||||
const OnlineEditor: React.FC = () => {
|
||||
const [files, setFiles] = useState<IFiles>(initFiles)
|
||||
|
||||
return (
|
||||
<>
|
||||
<CodeEditor
|
||||
files={files}
|
||||
onAddFile={(_, files) => setFiles(files)}
|
||||
onRemoveFile={(_, files) => setFiles(files)}
|
||||
onRenameFile={(_, __, files) => setFiles(files)}
|
||||
onChangeFileContent={(_, __, files) => setFiles(files)}
|
||||
/>
|
||||
<FitFullscreen>
|
||||
<FlexBox style={{ width: '100%', height: '100%' }} direction={'horizontal'}>
|
||||
<CodeEditor
|
||||
files={files}
|
||||
onAddFile={(_, files) => setFiles(files)}
|
||||
onRemoveFile={(_, files) => setFiles(files)}
|
||||
onRenameFile={(_, __, files) => setFiles(files)}
|
||||
onChangeFileContent={(_, __, files) => setFiles(files)}
|
||||
/>
|
||||
<Preview files={files} />
|
||||
</FlexBox>
|
||||
</FitFullscreen>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user