Compare commits
No commits in common. 'main' and 'post/wasm-gol-2' have entirely different histories.
main
...
post/wasm-
@ -1,68 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import type { ContentModule, ScriptModule, WasmModuleParam, ResourceFileParam, StringParam } from "./types"
|
|
||||||
import { useEffect, useRef } from "react"
|
|
||||||
import { themeSignal } from "../Appearance"
|
|
||||||
|
|
||||||
interface ScriptLoaderClientParams {
|
|
||||||
src: ScriptModule,
|
|
||||||
wasm: WasmModuleParam | undefined,
|
|
||||||
rest: {
|
|
||||||
[name: string]: ResourceFileParam | StringParam
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ScriptLoaderClient({ src, wasm, rest }: ScriptLoaderClientParams) {
|
|
||||||
const moduleRef = useRef<ContentModule | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
if (!src) return undefined
|
|
||||||
|
|
||||||
const scriptModule : ContentModule = await import(/*webpackIgnore: true */ src.path)
|
|
||||||
|
|
||||||
let wasmModule: WebAssembly.Instance | undefined = undefined
|
|
||||||
|
|
||||||
if (wasm) {
|
|
||||||
const instantiatedWasm = await WebAssembly.instantiateStreaming(fetch(wasm.binaryPath))
|
|
||||||
wasmModule = instantiatedWasm.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
moduleRef.current = scriptModule
|
|
||||||
|
|
||||||
const restProps = Object.keys(rest).reduce((acc: {[name: string]: string}, propName: string) => {
|
|
||||||
const prop = rest[propName]
|
|
||||||
return {
|
|
||||||
...acc,
|
|
||||||
[propName]: prop.type === 'string' ? prop.value : prop
|
|
||||||
}
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
if (scriptModule.setup && typeof scriptModule.setup === 'function')
|
|
||||||
scriptModule.setup(restProps, wasmModule)
|
|
||||||
})()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const mod = moduleRef.current
|
|
||||||
|
|
||||||
if (mod?.cleanup && typeof mod.cleanup === 'function') {
|
|
||||||
mod.cleanup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function onThemeChange() {
|
|
||||||
const mod = moduleRef.current
|
|
||||||
|
|
||||||
if (mod?.onThemeChange && typeof mod?.onThemeChange === 'function') {
|
|
||||||
mod.onThemeChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
themeSignal.addListener('change', onThemeChange)
|
|
||||||
return () => { themeSignal.removeListener('change', onThemeChange) }
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
import ScriptLoaderClient from './client'
|
|
||||||
|
|
||||||
import defaultTransformer from './transformers/_default'
|
|
||||||
import glslTransformer from './transformers/glsl'
|
|
||||||
import javascriptTransformer from './transformers/javascript'
|
|
||||||
import webassemblyTransformer from './transformers/webassembly'
|
|
||||||
|
|
||||||
import type { ResourceFileParam, StringParam, TransformResult } from './types'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
[propName: string]: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformers = [
|
|
||||||
javascriptTransformer,
|
|
||||||
webassemblyTransformer,
|
|
||||||
glslTransformer,
|
|
||||||
defaultTransformer
|
|
||||||
]
|
|
||||||
|
|
||||||
export default async function ScriptServer(props: Props) {
|
|
||||||
let transformedProps: {[name: string] : TransformResult} = {}
|
|
||||||
propLoop: for (const propName in props) {
|
|
||||||
const propValue = props[propName]
|
|
||||||
if (typeof propValue !== 'string')
|
|
||||||
continue
|
|
||||||
|
|
||||||
const extension = propValue.split('.').at(-1)
|
|
||||||
|
|
||||||
for (const transformer of transformers) {
|
|
||||||
if (transformer.extension === '*' || transformer.extension === extension) {
|
|
||||||
transformedProps[propName] = await transformer.transform(propValue)
|
|
||||||
continue propLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Cannot transform prop ${propName} with value ${propValue}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!transformedProps.src) {
|
|
||||||
throw new Error("No script source specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transformedProps.src.type !== 'script') {
|
|
||||||
throw new Error("Script source is not of type script")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transformedProps.wasm !== undefined && transformedProps.wasm.type !== 'wasm') {
|
|
||||||
throw new Error(`Wasm prop cannot be of type ${transformedProps.wasm.type}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const {src, wasm, ...rest} = transformedProps
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScriptLoaderClient
|
|
||||||
src={src}
|
|
||||||
wasm={wasm}
|
|
||||||
rest={rest as {[name: string]: StringParam | ResourceFileParam}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import type { PostResourceTransformer } from '../types'
|
|
||||||
|
|
||||||
import path from 'path'
|
|
||||||
import { promises as fs } from 'fs'
|
|
||||||
|
|
||||||
const transformer : PostResourceTransformer = {
|
|
||||||
extension: "*",
|
|
||||||
transform: async (value) => {
|
|
||||||
try {
|
|
||||||
const argPath = path.join(process.cwd(), 'scripts', value)
|
|
||||||
const argContents = await fs.readFile(argPath)
|
|
||||||
const argFileName = path.basename(value)
|
|
||||||
const argDest = path.join(process.cwd(), '.next/static/scripts', value)
|
|
||||||
const argDir = path.dirname(argDest)
|
|
||||||
await fs.mkdir(argDir, { recursive: true })
|
|
||||||
await fs.writeFile(argDest, argContents)
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "resource",
|
|
||||||
name: argFileName,
|
|
||||||
path: path.join('/_next/static/scripts', value)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
type: 'string',
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default transformer
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
|||||||
import { PostResourceTransformer } from "../types"
|
|
||||||
|
|
||||||
import path from 'path'
|
|
||||||
import { promises as fs } from 'fs'
|
|
||||||
import glslx from 'glslx'
|
|
||||||
|
|
||||||
const transformer : PostResourceTransformer = {
|
|
||||||
extension: "glsl",
|
|
||||||
transform: async (value) => {
|
|
||||||
const argPath = path.join(process.cwd(), 'scripts', value)
|
|
||||||
const argContents = await fs.readFile(argPath)
|
|
||||||
const argFileName = path.basename(value)
|
|
||||||
|
|
||||||
const {output: glslResult, log} = glslx.compile({name: argFileName, contents: argContents.toString('utf8')})
|
|
||||||
if (!glslResult) {
|
|
||||||
console.error(log)
|
|
||||||
throw new Error("Could not parse GLSL file")
|
|
||||||
}
|
|
||||||
|
|
||||||
const argDest = path.join(process.cwd(), '.next/static/scripts', value)
|
|
||||||
const argDir = path.dirname(argDest)
|
|
||||||
await fs.mkdir(argDir, { recursive: true })
|
|
||||||
await fs.writeFile(argDest, argContents)
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: "resource",
|
|
||||||
name: argFileName,
|
|
||||||
path: path.join('/_next/static/scripts', value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default transformer
|
|
@ -1,25 +0,0 @@
|
|||||||
import type { PostResourceTransformer } from '../types'
|
|
||||||
|
|
||||||
import path from 'path'
|
|
||||||
import { promises as fs } from 'fs'
|
|
||||||
|
|
||||||
const transformer : PostResourceTransformer = {
|
|
||||||
extension: "js",
|
|
||||||
transform: async (value) => {
|
|
||||||
const scriptPath = path.join(process.cwd(), 'scripts', value)
|
|
||||||
const scriptContents = await fs.readFile(scriptPath)
|
|
||||||
const scriptFileName = path.basename(value)
|
|
||||||
const scriptDest = path.join(process.cwd(), '.next/static/scripts', value)
|
|
||||||
const destDir = path.dirname(scriptDest)
|
|
||||||
await fs.mkdir(destDir, { recursive: true })
|
|
||||||
await fs.writeFile(scriptDest, scriptContents)
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'script',
|
|
||||||
name: scriptFileName,
|
|
||||||
path: path.join('/_next/static/scripts', value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default transformer
|
|
@ -1,37 +0,0 @@
|
|||||||
import type { PostResourceTransformer } from '../types'
|
|
||||||
|
|
||||||
import path from 'path'
|
|
||||||
import { promises as fs } from 'fs'
|
|
||||||
import wabtModule from 'wabt'
|
|
||||||
|
|
||||||
const wabtModulePromise = wabtModule()
|
|
||||||
|
|
||||||
const transformer : PostResourceTransformer = {
|
|
||||||
extension: "wat",
|
|
||||||
transform: async (src) => {
|
|
||||||
const wasmPath = path.join(process.cwd(), 'scripts', src)
|
|
||||||
const wasmContents = await fs.readFile(wasmPath)
|
|
||||||
|
|
||||||
const wasmTextFileName = path.basename(src)
|
|
||||||
const wasmBinFileName = wasmTextFileName.replace(/\.wat$/, '.wasm')
|
|
||||||
const wabt = await wabtModulePromise
|
|
||||||
|
|
||||||
const wasmParsed = wabt.parseWat(wasmTextFileName, wasmContents)
|
|
||||||
const { buffer: wasmBinary } = wasmParsed.toBinary({ write_debug_names: true })
|
|
||||||
const wasmDestText = path.join(process.cwd(), '.next/static/scripts', src)
|
|
||||||
const wasmDestBinary = wasmDestText.replace(/\.wat$/, '.wasm')
|
|
||||||
const destDir = path.dirname(wasmDestText)
|
|
||||||
await fs.mkdir(destDir, { recursive: true })
|
|
||||||
await fs.writeFile(wasmDestText, wasmContents)
|
|
||||||
await fs.writeFile(wasmDestBinary, wasmBinary)
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'wasm',
|
|
||||||
name: wasmBinFileName,
|
|
||||||
binaryPath: path.join('/_next/static/scripts/', src.replace(/\.wat$/, '.wasm')),
|
|
||||||
textPath: path.join('/_next/static/scripts/', src),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default transformer
|
|
@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Content Script Interfaces
|
|
||||||
*/
|
|
||||||
export interface ContentModule {
|
|
||||||
setup: (args: ContentModuleSetupParams, wasm?: WebAssembly.Instance) => Promise<undefined>,
|
|
||||||
cleanup?: () => Promise<undefined>,
|
|
||||||
onThemeChange?: () => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentModuleSetupParams {
|
|
||||||
[argName: string]: ResourceFileParam | string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScriptModule {
|
|
||||||
type: 'script',
|
|
||||||
name: string,
|
|
||||||
path: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WasmModuleParam {
|
|
||||||
type: 'wasm',
|
|
||||||
name: string,
|
|
||||||
binaryPath: string,
|
|
||||||
textPath: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceFileParam {
|
|
||||||
type: 'resource',
|
|
||||||
name: string,
|
|
||||||
path: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StringParam {
|
|
||||||
type: 'string',
|
|
||||||
value: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TransformResult = ScriptModule | WasmModuleParam | ResourceFileParam | StringParam
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource transformer interfaces
|
|
||||||
*/
|
|
||||||
export interface PostResourceTransformer {
|
|
||||||
extension: string,
|
|
||||||
transform: (value: string) => Promise<TransformResult>
|
|
||||||
}
|
|
@ -0,0 +1,58 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useRef } from "react"
|
||||||
|
import { themeSignal } from "./Appearance"
|
||||||
|
|
||||||
|
interface Module {
|
||||||
|
setup?: (params: any, wasmModule: WebAssembly.Instance | undefined) => Promise<undefined>
|
||||||
|
cleanup?: () => Promise<undefined>
|
||||||
|
onThemeChange?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ScriptLoaderClient({ src, wasmSrc, ...rest }: { src: string, wasmSrc?: string }) {
|
||||||
|
const moduleRef = useRef<Module | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!src) return undefined;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const scriptModule = await import(/*webpackIgnore: true */ src)
|
||||||
|
|
||||||
|
let wasmModule: WebAssembly.Instance | undefined = undefined;
|
||||||
|
|
||||||
|
if (wasmSrc) {
|
||||||
|
const wasm = await WebAssembly.instantiateStreaming(fetch(wasmSrc))
|
||||||
|
wasmModule = wasm.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleRef.current = scriptModule
|
||||||
|
|
||||||
|
|
||||||
|
if (scriptModule.setup && typeof scriptModule.setup === 'function')
|
||||||
|
scriptModule.setup(rest, wasmModule)
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const mod = moduleRef.current
|
||||||
|
|
||||||
|
if (mod?.cleanup && typeof mod.cleanup === 'function') {
|
||||||
|
mod.cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onThemeChange() {
|
||||||
|
const mod = moduleRef.current
|
||||||
|
|
||||||
|
if (mod?.onThemeChange && typeof mod?.onThemeChange === 'function') {
|
||||||
|
mod.onThemeChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
themeSignal.addListener('change', onThemeChange)
|
||||||
|
return () => { themeSignal.removeListener('change', onThemeChange) }
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import { promises as fs } from 'fs'
|
||||||
|
import wabtModule from 'wabt'
|
||||||
|
import ScriptLoaderClient from './ScriptLoaderClient'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
src: string,
|
||||||
|
wasm?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptFile {
|
||||||
|
name: string,
|
||||||
|
path: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wabtModulePromise = wabtModule()
|
||||||
|
|
||||||
|
export default async function ScriptServer({ src, wasm, ...rest }: Props) {
|
||||||
|
if (!src)
|
||||||
|
throw new Error("ScriptLoader: No src parameter")
|
||||||
|
|
||||||
|
const scriptPath = path.join(process.cwd(), 'scripts', src)
|
||||||
|
const wasmPath = wasm && path.join(process.cwd(), 'scripts', wasm)
|
||||||
|
|
||||||
|
const scriptContents = await fs.readFile(scriptPath)
|
||||||
|
const wasmContents = wasmPath && await fs.readFile(wasmPath)
|
||||||
|
|
||||||
|
let script: ScriptFile | undefined = undefined;
|
||||||
|
|
||||||
|
if (scriptContents) {
|
||||||
|
const scriptFileName = path.basename(src)
|
||||||
|
const scriptDest = path.join(process.cwd(), '.next/static/scripts', src)
|
||||||
|
const destDir = path.dirname(scriptDest)
|
||||||
|
await fs.mkdir(destDir, { recursive: true })
|
||||||
|
await fs.writeFile(scriptDest, scriptContents)
|
||||||
|
script = {
|
||||||
|
name: scriptFileName,
|
||||||
|
path: path.join('/_next/static/scripts', src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wasmCompiled: ScriptFile | undefined = undefined;
|
||||||
|
if (wasmContents) {
|
||||||
|
const wasmTextFileName = path.basename(wasm)
|
||||||
|
const wasmBinFileName = wasmTextFileName.replace(/\.wat$/, '.wasm')
|
||||||
|
const wabt = await wabtModulePromise
|
||||||
|
|
||||||
|
const wasmParsed = wabt.parseWat(wasmTextFileName, wasmContents)
|
||||||
|
const { buffer: wasmBinary } = wasmParsed.toBinary({ write_debug_names: true })
|
||||||
|
const wasmDestText = path.join(process.cwd(), '.next/static/scripts', wasm)
|
||||||
|
const wasmDestBinary = wasmDestText.replace(/\.wat$/, '.wasm')
|
||||||
|
const destDir = path.dirname(wasmDestText)
|
||||||
|
await fs.mkdir(destDir, { recursive: true })
|
||||||
|
await fs.writeFile(wasmDestText, wasmContents)
|
||||||
|
await fs.writeFile(wasmDestBinary, wasmBinary)
|
||||||
|
|
||||||
|
wasmCompiled = {
|
||||||
|
name: wasmBinFileName,
|
||||||
|
path: path.join('/_next/static/scripts/', wasm.replace(/\.wat$/, '.wasm'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let processedArgs = {}
|
||||||
|
for (const argName in rest) {
|
||||||
|
const argValue = rest[argName]
|
||||||
|
|
||||||
|
try {
|
||||||
|
const argPath = path.join(process.cwd(), 'scripts', argValue)
|
||||||
|
const argContents = await fs.readFile(argPath)
|
||||||
|
const argFileName = path.basename(argValue)
|
||||||
|
const argDest = path.join(process.cwd(), '.next/static/scripts', argValue)
|
||||||
|
const argDir = path.dirname(argDest)
|
||||||
|
await fs.mkdir(argDir, { recursive: true })
|
||||||
|
await fs.writeFile(argDest, argContents)
|
||||||
|
processedArgs[argName] = {
|
||||||
|
name: argFileName,
|
||||||
|
path: path.join('/_next/static/scripts', argValue)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
processedArgs[argName] = argValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!script) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScriptLoaderClient
|
||||||
|
src={script.path}
|
||||||
|
{...(wasmCompiled ? {
|
||||||
|
wasmSrc: wasmCompiled.path
|
||||||
|
} : {})}
|
||||||
|
{...processedArgs}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue