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

View File

@@ -5,6 +5,7 @@ import { IEditorOptions, IFiles, ITheme } from '@/components/Playground/shared'
import {
ENTRY_FILE_NAME,
fileNameToLanguage,
getFileNameList,
IMPORT_MAP_FILE_NAME
} from '@/components/Playground/files'
import FileSelector from '@/components/Playground/CodeEditor/FileSelector'
@@ -42,7 +43,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
onError,
...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
)
const propsSelectedFileName =

View File

@@ -2,18 +2,19 @@ import React from 'react'
import MonacoEditor from '@monaco-editor/react'
import { Loader } from 'esbuild-wasm'
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 { cssToJs, jsonToJs } from '@/components/Playground/files'
import { MonacoEditorConfig } from '@/components/Playground/CodeEditor/Editor/monacoConfig'
import { addReactImport } from '@/components/Playground/utils.ts'
interface OutputProps {
file: IFile
files: IFiles
selectedFileName: string
theme?: ITheme
}
const Preview: React.FC<OutputProps> = ({ file, theme }) => {
const Preview: React.FC<OutputProps> = ({ files, selectedFileName, theme }) => {
const compiler = useRef<Compiler>()
const [compileCode, setCompileCode] = useState('')
@@ -37,14 +38,25 @@ const Preview: React.FC<OutputProps> = ({ file, theme }) => {
.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 (file) {
if (files[selectedFileName]) {
try {
const code = file.value
const code = files[selectedFileName].value
switch (file.language) {
switch (files[selectedFileName].language) {
case 'typescript':
compile(code, 'tsx')
break
@@ -52,10 +64,10 @@ const Preview: React.FC<OutputProps> = ({ file, theme }) => {
compile(code, 'jsx')
break
case 'css':
setCompileCode(cssToJs(file))
setCompileCode(cssToJs(files[selectedFileName]))
break
case 'json':
setCompileCode(jsonToJs(file))
setCompileCode(jsonToJs(files[selectedFileName]))
break
case 'xml':
setCompileCode(code)
@@ -66,7 +78,7 @@ const Preview: React.FC<OutputProps> = ({ file, theme }) => {
} else {
setCompileCode('')
}
}, [file])
}, [files[selectedFileName]])
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 { 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 {
private init = false
fileCache = localforage.createInstance({
name: 'fileCache'
})
constructor() {
try {
void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => {
@@ -15,20 +23,180 @@ class Compiler {
}
transform = (code: string, loader: Loader) =>
new Promise<boolean>((resolve) => {
new Promise<void>((resolve) => {
if (this.init) {
resolve(true)
resolve()
return
}
const timer = setInterval(() => {
if (this.init) {
clearInterval(timer)
resolve(true)
resolve()
}
}, 100)
}).then(() => {
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

View File

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

View File

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