Refactor(compiler): Support load css from ImportMap and load binary file
This commit is contained in:
@@ -1,8 +1,22 @@
|
||||
import esbuild, { Loader, OnLoadArgs, Plugin, PluginBuild } from 'esbuild-wasm'
|
||||
import esbuild, {
|
||||
Loader,
|
||||
OnLoadArgs,
|
||||
OnLoadResult,
|
||||
OnResolveArgs,
|
||||
OnResolveResult,
|
||||
Plugin,
|
||||
PluginBuild
|
||||
} from 'esbuild-wasm'
|
||||
import localforage from 'localforage'
|
||||
import axios from 'axios'
|
||||
import { IFile, IFiles, IImportMap } from '@/components/Playground/shared'
|
||||
import { addReactImport, cssToJs, jsonToJs } from '@/components/Playground/files'
|
||||
import {
|
||||
addReactImport,
|
||||
cssToJs,
|
||||
cssToJsFromFile,
|
||||
jsonToJs,
|
||||
jsonToJsFromFile
|
||||
} from '@/components/Playground/files'
|
||||
|
||||
class Compiler {
|
||||
private init = false
|
||||
@@ -26,44 +40,47 @@ class Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
transform = (code: string, loader: Loader) =>
|
||||
new Promise<void>((resolve) => {
|
||||
if (this.init) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const timer = setInterval(() => {
|
||||
if (this.init) {
|
||||
clearInterval(timer)
|
||||
resolve()
|
||||
private waitInit = async () => {
|
||||
if (!this.init) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const checkInit = () => {
|
||||
if (this.init) {
|
||||
resolve()
|
||||
} else {
|
||||
setTimeout(checkInit, 100)
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
}).then(() => {
|
||||
return esbuild.transform(code, { loader })
|
||||
})
|
||||
|
||||
compile = (files: IFiles, importMap: IImportMap, entryPoint: string) =>
|
||||
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: [entryPoint],
|
||||
format: 'esm',
|
||||
metafile: true,
|
||||
write: false,
|
||||
plugins: [this.fileResolverPlugin(files, importMap)]
|
||||
checkInit()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
transform = async (code: string, loader: Loader) => {
|
||||
await this.waitInit()
|
||||
return esbuild.transform(code, { loader })
|
||||
}
|
||||
|
||||
compile = async (files: IFiles, importMap: IImportMap, entryPoint: string) => {
|
||||
await this.waitInit()
|
||||
return esbuild.build({
|
||||
bundle: true,
|
||||
entryPoints: [entryPoint],
|
||||
format: 'esm',
|
||||
metafile: true,
|
||||
write: false,
|
||||
plugins: [this.fileResolverPlugin(files, importMap)]
|
||||
})
|
||||
}
|
||||
|
||||
compileCss = async (cssCode: string, basePath: string) => {
|
||||
await this.waitInit()
|
||||
return esbuild.build({
|
||||
bundle: true,
|
||||
entryPoints: [basePath],
|
||||
write: false,
|
||||
plugins: [this.cssCodeResolverPlugin(cssCode, basePath)]
|
||||
})
|
||||
}
|
||||
|
||||
stop = () => {
|
||||
void esbuild.stop()
|
||||
@@ -72,7 +89,7 @@ class Compiler {
|
||||
private fileResolverPlugin = (files: IFiles, importMap: IImportMap): Plugin => ({
|
||||
name: 'file-resolver-plugin',
|
||||
setup: (build: PluginBuild) => {
|
||||
build.onResolve({ filter: /.*/ }, (args: esbuild.OnResolveArgs) => {
|
||||
build.onResolve({ filter: /.*/ }, (args: OnResolveArgs): OnResolveResult => {
|
||||
if (args.kind === 'entry-point') {
|
||||
return {
|
||||
namespace: 'oxygen',
|
||||
@@ -134,54 +151,152 @@ class Compiler {
|
||||
}
|
||||
})
|
||||
|
||||
build.onLoad({ filter: /.*\.css$/ }, (args: OnLoadArgs) => {
|
||||
const contents = cssToJs(files[args.path])
|
||||
build.onLoad({ filter: /.*\.css$/ }, (args: OnLoadArgs): OnLoadResult => {
|
||||
const contents = cssToJsFromFile(files[args.path])
|
||||
return {
|
||||
loader: 'js',
|
||||
contents
|
||||
}
|
||||
})
|
||||
|
||||
build.onLoad({ filter: /.*\.json$/ }, (args: OnLoadArgs) => {
|
||||
const contents = jsonToJs(files[args.path])
|
||||
build.onLoad({ filter: /.*\.json$/ }, (args: OnLoadArgs): OnLoadResult => {
|
||||
const contents = jsonToJsFromFile(files[args.path])
|
||||
return {
|
||||
loader: 'js',
|
||||
contents
|
||||
}
|
||||
})
|
||||
|
||||
build.onLoad({ namespace: 'oxygen', filter: /.*/ }, (args: OnLoadArgs) => {
|
||||
let file: IFile | undefined
|
||||
build.onLoad(
|
||||
{ namespace: 'oxygen', filter: /.*/ },
|
||||
(args: OnLoadArgs): OnLoadResult | undefined => {
|
||||
let file: IFile | undefined
|
||||
|
||||
void ['', '.tsx', '.jsx', '.ts', '.js'].forEach((suffix) => {
|
||||
file = file || files[`${args.path}${suffix}`]
|
||||
})
|
||||
if (file) {
|
||||
return {
|
||||
loader: (() => {
|
||||
switch (file.language) {
|
||||
case 'javascript':
|
||||
return 'jsx'
|
||||
default:
|
||||
return 'tsx'
|
||||
}
|
||||
})(),
|
||||
contents: addReactImport(file.value)
|
||||
void ['', '.tsx', '.jsx', '.ts', '.js'].forEach((suffix) => {
|
||||
file = file || files[`${args.path}${suffix}`]
|
||||
})
|
||||
if (file) {
|
||||
return {
|
||||
loader: (() => {
|
||||
switch (file.language) {
|
||||
case 'javascript':
|
||||
return 'jsx'
|
||||
default:
|
||||
return 'tsx'
|
||||
}
|
||||
})(),
|
||||
contents: addReactImport(file.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
build.onLoad({ filter: /.*/ }, async (args: OnLoadArgs) => {
|
||||
const cached = await this.fileCache.getItem<esbuild.OnLoadResult>(args.path)
|
||||
build.onLoad({ filter: /.*/ }, async (args: OnLoadArgs): Promise<OnLoadResult> => {
|
||||
const cached = await this.fileCache.getItem<OnLoadResult>(args.path)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const axiosResponse = await axios.get<string>(args.path)
|
||||
const result: esbuild.OnLoadResult = {
|
||||
loader: 'js',
|
||||
contents: axiosResponse.data,
|
||||
const axiosResponse = await axios.get<ArrayBuffer>(args.path, {
|
||||
responseType: 'arraybuffer'
|
||||
})
|
||||
const contentType = axiosResponse.headers['content-type'] as string
|
||||
const utf8Decoder = new TextDecoder('utf-8')
|
||||
const result: OnLoadResult = {
|
||||
loader: (() => {
|
||||
if (
|
||||
contentType.includes('javascript') ||
|
||||
contentType.includes('css') ||
|
||||
contentType.includes('json')
|
||||
) {
|
||||
return 'js'
|
||||
}
|
||||
return 'base64'
|
||||
})(),
|
||||
contents: await (async () => {
|
||||
if (contentType.includes('css')) {
|
||||
return cssToJs(
|
||||
(
|
||||
await this.compileCss(
|
||||
utf8Decoder.decode(axiosResponse.data),
|
||||
args.path
|
||||
)
|
||||
).outputFiles[0].text
|
||||
)
|
||||
}
|
||||
if (contentType.includes('json')) {
|
||||
return jsonToJs(utf8Decoder.decode(axiosResponse.data))
|
||||
}
|
||||
return new Uint8Array(axiosResponse.data)
|
||||
})(),
|
||||
resolveDir: args.path
|
||||
}
|
||||
|
||||
await this.fileCache.setItem(args.path, result)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
private cssCodeResolverPlugin = (cssCode: string, basePath: string): Plugin => ({
|
||||
name: 'css-code-resolver-plugin',
|
||||
setup: (build: PluginBuild) => {
|
||||
build.onResolve({ filter: /.*/ }, (args: OnResolveArgs): OnResolveResult => {
|
||||
if (args.kind === 'entry-point') {
|
||||
return {
|
||||
namespace: 'default',
|
||||
path: basePath
|
||||
}
|
||||
}
|
||||
return {
|
||||
namespace: 'default',
|
||||
path: args.resolveDir.length
|
||||
? new URL(args.path, args.resolveDir.substring(1)).href
|
||||
: new URL(args.path, basePath).href
|
||||
}
|
||||
})
|
||||
|
||||
build.onLoad({ filter: /.*/ }, async (args: OnLoadArgs): Promise<OnLoadResult> => {
|
||||
if (args.path === basePath) {
|
||||
return {
|
||||
loader: 'css',
|
||||
contents: cssCode,
|
||||
resolveDir: basePath
|
||||
}
|
||||
}
|
||||
const cached = await this.fileCache.getItem<OnLoadResult>(args.path)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
const axiosResponse = await axios.get<ArrayBuffer>(args.path, {
|
||||
responseType: 'arraybuffer'
|
||||
})
|
||||
const contentType = axiosResponse.headers['content-type'] as string
|
||||
const utf8Decoder = new TextDecoder('utf-8')
|
||||
const result: OnLoadResult = {
|
||||
loader: (() => {
|
||||
if (contentType.includes('css')) {
|
||||
return 'js'
|
||||
}
|
||||
return 'dataurl'
|
||||
})(),
|
||||
contents: await (async () => {
|
||||
if (contentType.includes('css')) {
|
||||
return cssToJs(
|
||||
(
|
||||
await this.compileCss(
|
||||
utf8Decoder.decode(axiosResponse.data),
|
||||
args.path
|
||||
)
|
||||
).outputFiles[0].text
|
||||
)
|
||||
}
|
||||
return new Uint8Array(axiosResponse.data)
|
||||
})(),
|
||||
resolveDir: args.path
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user