Compare commits
5 Commits
post/wasm-
...
main
Author | SHA1 | Date |
---|---|---|
Ashelyn Dawn | cd4fca9ea3 | 2 months ago |
Ashelyn Rose | ffcaf44af2 | 4 months ago |
Ashelyn Dawn | 513426bba5 | 1 year ago |
Ashelyn Dawn | 4ac2154cd9 | 1 year ago |
Ashelyn Dawn | e59294c192 | 1 year ago |
@ -1,3 +1,4 @@
|
|||||||
.next/
|
.next/
|
||||||
node_modules/
|
node_modules/
|
||||||
out/
|
out/
|
||||||
|
result
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
'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
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
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}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
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
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
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
|
@ -0,0 +1,25 @@
|
|||||||
|
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
|
@ -0,0 +1,37 @@
|
|||||||
|
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
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
'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
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
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