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