Add compile to compiler (still not working)

This commit is contained in:
2024-01-11 18:34:12 +08:00
parent 528dff1487
commit 949e29e7cc
7 changed files with 211 additions and 20 deletions

View File

@@ -1,7 +1,11 @@
import React from 'react' import React from 'react'
import '@/components/Playground/CodeEditor/FileSelector/file-selector.scss' import '@/components/Playground/CodeEditor/FileSelector/file-selector.scss'
import { IFiles } from '@/components/Playground/shared' import { IFiles } from '@/components/Playground/shared'
import { ENTRY_FILE_NAME, IMPORT_MAP_FILE_NAME } from '@/components/Playground/files' import {
ENTRY_FILE_NAME,
getFileNameList,
IMPORT_MAP_FILE_NAME
} from '@/components/Playground/files'
import Item from '@/components/Playground/CodeEditor/FileSelector/Item' import Item from '@/components/Playground/CodeEditor/FileSelector/Item'
import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar' import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar'
import FlexBox from '@/components/common/FlexBox' import FlexBox from '@/components/common/FlexBox'
@@ -120,7 +124,7 @@ const FileSelector: React.FC<FileSelectorProps> = ({
const handleOnRemove = (fileName: string) => { const handleOnRemove = (fileName: string) => {
onRemoveFile?.(fileName) onRemoveFile?.(fileName)
if (fileName === selectedFileName) { if (fileName === selectedFileName) {
const keys = Object.keys(files).filter( const keys = getFileNameList(files).filter(
(item) => (item) =>
![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden ![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden
) )
@@ -134,9 +138,9 @@ const FileSelector: React.FC<FileSelectorProps> = ({
} }
useEffect(() => { useEffect(() => {
Object.keys(files).length getFileNameList(files).length
? setTabs( ? setTabs(
Object.keys(files).filter( getFileNameList(files).filter(
(item) => (item) =>
![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && ![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) &&
!files[item].hidden !files[item].hidden

View File

@@ -5,6 +5,7 @@ import { IEditorOptions, IFiles, ITheme } from '@/components/Playground/shared'
import { import {
ENTRY_FILE_NAME, ENTRY_FILE_NAME,
fileNameToLanguage, fileNameToLanguage,
getFileNameList,
IMPORT_MAP_FILE_NAME IMPORT_MAP_FILE_NAME
} from '@/components/Playground/files' } from '@/components/Playground/files'
import FileSelector from '@/components/Playground/CodeEditor/FileSelector' import FileSelector from '@/components/Playground/CodeEditor/FileSelector'
@@ -42,7 +43,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
onError, onError,
...props ...props
}) => { }) => {
const filteredFilesName = Object.keys(files).filter( const filteredFilesName = getFileNameList(files).filter(
(item) => ![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden (item) => ![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden
) )
const propsSelectedFileName = const propsSelectedFileName =

View File

@@ -2,18 +2,19 @@ import React from 'react'
import MonacoEditor from '@monaco-editor/react' import MonacoEditor from '@monaco-editor/react'
import { Loader } from 'esbuild-wasm' import { Loader } from 'esbuild-wasm'
import { useUpdatedEffect } from '@/util/hooks' import { useUpdatedEffect } from '@/util/hooks'
import { IFile, ITheme } from '@/components/Playground/shared' import { IFiles, IImportMap, ITheme } from '@/components/Playground/shared'
import Compiler from '@/components/Playground/compiler' import Compiler from '@/components/Playground/compiler'
import { cssToJs, jsonToJs } from '@/components/Playground/files' import { cssToJs, jsonToJs } from '@/components/Playground/files'
import { MonacoEditorConfig } from '@/components/Playground/CodeEditor/Editor/monacoConfig' import { MonacoEditorConfig } from '@/components/Playground/CodeEditor/Editor/monacoConfig'
import { addReactImport } from '@/components/Playground/utils.ts' import { addReactImport } from '@/components/Playground/utils.ts'
interface OutputProps { interface OutputProps {
file: IFile files: IFiles
selectedFileName: string
theme?: ITheme theme?: ITheme
} }
const Preview: React.FC<OutputProps> = ({ file, theme }) => { const Preview: React.FC<OutputProps> = ({ files, selectedFileName, theme }) => {
const compiler = useRef<Compiler>() const compiler = useRef<Compiler>()
const [compileCode, setCompileCode] = useState('') const [compileCode, setCompileCode] = useState('')
@@ -37,14 +38,25 @@ const Preview: React.FC<OutputProps> = ({ file, theme }) => {
.catch((e) => { .catch((e) => {
console.error('编译失败', 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(() => { useUpdatedEffect(() => {
if (file) { if (files[selectedFileName]) {
try { try {
const code = file.value const code = files[selectedFileName].value
switch (file.language) { switch (files[selectedFileName].language) {
case 'typescript': case 'typescript':
compile(code, 'tsx') compile(code, 'tsx')
break break
@@ -52,10 +64,10 @@ const Preview: React.FC<OutputProps> = ({ file, theme }) => {
compile(code, 'jsx') compile(code, 'jsx')
break break
case 'css': case 'css':
setCompileCode(cssToJs(file)) setCompileCode(cssToJs(files[selectedFileName]))
break break
case 'json': case 'json':
setCompileCode(jsonToJs(file)) setCompileCode(jsonToJs(files[selectedFileName]))
break break
case 'xml': case 'xml':
setCompileCode(code) setCompileCode(code)
@@ -66,7 +78,7 @@ const Preview: React.FC<OutputProps> = ({ file, theme }) => {
} else { } else {
setCompileCode('') setCompileCode('')
} }
}, [file]) }, [files[selectedFileName]])
return ( return (
<> <>

View File

@@ -1,9 +1,17 @@
import esbuild, { Loader } from 'esbuild-wasm' import esbuild, { Loader, OnLoadArgs, Plugin, PluginBuild } from 'esbuild-wasm'
import wasm from 'esbuild-wasm/esbuild.wasm?url' 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'
class Compiler { class Compiler {
private init = false private init = false
fileCache = localforage.createInstance({
name: 'fileCache'
})
constructor() { constructor() {
try { try {
void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => { void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => {
@@ -15,20 +23,180 @@ class Compiler {
} }
transform = (code: string, loader: Loader) => transform = (code: string, loader: Loader) =>
new Promise<boolean>((resolve) => { new Promise<void>((resolve) => {
if (this.init) { if (this.init) {
resolve(true) resolve()
return return
} }
const timer = setInterval(() => { const timer = setInterval(() => {
if (this.init) { if (this.init) {
clearInterval(timer) clearInterval(timer)
resolve(true) resolve()
} }
}, 100) }, 100)
}).then(() => { }).then(() => {
return esbuild.transform(code, { loader }) return esbuild.transform(code, { loader })
}) })
compile = (files: IFiles, importMap: IImportMap) =>
new Promise<void>((resolve) => {
if (this.init) {
resolve()
return
}
const timer = setInterval(() => {
if (this.init) {
clearInterval(timer)
resolve()
}
}, 100)
}).then(() => {
return esbuild.build({
bundle: true,
entryPoints: [ENTRY_FILE_NAME],
format: 'esm',
metafile: true,
write: false,
plugins: [this.fileResolverPlugin(files, importMap)]
})
})
stop = () => {
esbuild.stop()
}
private fileResolverPlugin = (files: IFiles, importMap: IImportMap): Plugin => {
return {
name: 'file-resolver-plugin',
setup: (build: PluginBuild) => {
build.onResolve({ filter: /.*/ }, async (args: esbuild.OnResolveArgs) => {
if (args.path === ENTRY_FILE_NAME) {
return {
namespace: 'OxygenToolbox',
path: args.path
}
}
if (args.path.startsWith('./') && files[args.path.substring(2)]) {
return {
namespace: 'OxygenToolbox',
path: args.path.substring(2)
}
}
if (args.path.startsWith('./') && files[`${args.path.substring(2)}.tsx`]) {
return {
namespace: 'OxygenToolbox',
path: `${args.path.substring(2)}.tsx`
}
}
if (args.path.startsWith('./') && files[`${args.path.substring(2)}.jsx`]) {
return {
namespace: 'OxygenToolbox',
path: `${args.path.substring(2)}.jsx`
}
}
if (args.path.startsWith('./') && files[`${args.path.substring(2)}.ts`]) {
return {
namespace: 'OxygenToolbox',
path: `${args.path.substring(2)}.ts`
}
}
if (args.path.startsWith('./') && files[`${args.path.substring(2)}.js`]) {
return {
namespace: 'OxygenToolbox',
path: `${args.path.substring(2)}.js`
}
}
const path = importMap.imports[args.path]
if (!path) {
throw Error(`Import '${args.path}' not found in Import Map`)
}
return {
namespace: 'default',
path
}
})
build.onLoad({ filter: /.*\.css$/ }, async (args: OnLoadArgs) => {
const contents = cssToJs(files[args.path])
return {
loader: 'js',
contents
}
})
build.onLoad({ filter: /.*\.json$/ }, async (args: OnLoadArgs) => {
const contents = jsonToJs(files[args.path])
return {
loader: 'js',
contents
}
})
build.onLoad({ filter: /.*\.svg$/ }, async (args: OnLoadArgs) => {
const contents = files[args.path].value
return {
loader: 'text',
contents
}
})
build.onLoad({ filter: /.*/ }, async (args: OnLoadArgs) => {
if (args.path === ENTRY_FILE_NAME) {
return {
loader: 'tsx',
contents: files[ENTRY_FILE_NAME].value
}
}
if (files[args.path]) {
const contents = files[args.path].value
if (args.path.endsWith('.jsx')) {
return {
loader: 'jsx',
contents
}
}
if (args.path.endsWith('.ts')) {
return {
loader: 'ts',
contents
}
}
if (args.path.endsWith('.js')) {
return {
loader: 'js',
contents
}
}
return {
loader: 'tsx',
contents
}
}
const cached = await this.fileCache.getItem<esbuild.OnLoadResult>(args.path)
if (cached) {
return cached
}
const { data, request } = await axios.get(args.path)
const result: esbuild.OnLoadResult = {
loader: 'jsx',
contents: data,
resolveDir: new URL('./', request.responseURL).pathname
}
await this.fileCache.setItem(args.path, request)
return result
})
}
}
}
} }
export default Compiler export default Compiler

View File

@@ -9,6 +9,8 @@ export const MAIN_FILE_NAME = 'App.tsx'
export const IMPORT_MAP_FILE_NAME = 'import-map.json' export const IMPORT_MAP_FILE_NAME = 'import-map.json'
export const ENTRY_FILE_NAME = 'main.tsx' export const ENTRY_FILE_NAME = 'main.tsx'
export const getFileNameList = (files: IFiles) => Object.keys(files)
export const fileNameToLanguage = (name: string): ILanguage => { export const fileNameToLanguage = (name: string): ILanguage => {
const suffix = name.split('.').pop() || '' const suffix = name.split('.').pop() || ''
if (['js', 'jsx'].includes(suffix)) return 'javascript' if (['js', 'jsx'].includes(suffix)) return 'javascript'
@@ -55,7 +57,7 @@ export const getFilesFromUrl = () => {
export const getModuleFile = (files: IFiles, moduleName: string) => { export const getModuleFile = (files: IFiles, moduleName: string) => {
let _moduleName = moduleName.split('./').pop() || '' let _moduleName = moduleName.split('./').pop() || ''
if (!_moduleName.includes('.')) { if (!_moduleName.includes('.')) {
const realModuleName = Object.keys(files).find((key) => const realModuleName = getFileNameList(files).find((key) =>
key.split('.').includes(_moduleName) key.split('.').includes(_moduleName)
) )
if (realModuleName) _moduleName = realModuleName if (realModuleName) _moduleName = realModuleName

View File

@@ -13,6 +13,10 @@ export interface IFiles {
[key: string]: IFile [key: string]: IFile
} }
export interface IImportMap {
imports: Record<string, string>
}
export type ITheme = 'light' | 'vs-dark' export type ITheme = 'light' | 'vs-dark'
export type IEditorOptions = editor.IStandaloneEditorConstructionOptions export type IEditorOptions = editor.IStandaloneEditorConstructionOptions

View File

@@ -23,7 +23,7 @@ const OnlineEditor: React.FC = () => {
onRenameFile={(_, __, files) => setFiles(files)} onRenameFile={(_, __, files) => setFiles(files)}
onChangeFileContent={(_, __, files) => setFiles(files)} onChangeFileContent={(_, __, files) => setFiles(files)}
/> />
<Transform file={files[selectedFileName]} /> <Transform files={files} selectedFileName={selectedFileName} />
</FlexBox> </FlexBox>
</FitFullscreen> </FitFullscreen>
</> </>