Add compiler

This commit is contained in:
2024-01-10 18:15:17 +08:00
parent e846c28082
commit 71052009ae
6 changed files with 172 additions and 9 deletions

14
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View 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

View 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

View File

@@ -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,

View File

@@ -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>
</>
)
}