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

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