404 lines
No EOL
14 KiB
Text
404 lines
No EOL
14 KiB
Text
#!/usr/bin/env nu
|
|
# WebAssembly optimization and compilation module
|
|
# Supports Rust, C/C++, AssemblyScript, and Go compilation to WASM
|
|
|
|
# Main WASM compilation function
|
|
export def compile_wasm [config: record, dev: bool, verbose: bool] {
|
|
if not ("wasm" in ($config | columns)) or not $config.wasm.enabled {
|
|
if $verbose { print "📦 WASM not enabled, skipping..." }
|
|
return { size: 0, modules: [] }
|
|
}
|
|
|
|
if $verbose { print "🦀 Compiling WebAssembly modules..." }
|
|
|
|
let wasm_config = $config.wasm
|
|
mut compiled_modules = []
|
|
mut total_size = 0
|
|
|
|
# Validate WASM build environment
|
|
let toolchain_check = (validate_wasm_toolchain $wasm_config $verbose)
|
|
if not $toolchain_check.valid {
|
|
print $"❌ WASM toolchain validation failed: ($toolchain_check.error)"
|
|
return { size: 0, modules: [] }
|
|
}
|
|
|
|
# Create WASM output directory
|
|
mkdir $wasm_config.output_dir
|
|
|
|
# Compile each WASM module
|
|
for module in $wasm_config.modules {
|
|
let module_result = (compile_wasm_module $module $wasm_config $dev $verbose)
|
|
|
|
if $module_result.success {
|
|
$compiled_modules = ($compiled_modules | append $module_result)
|
|
$total_size = ($total_size + $module_result.size)
|
|
} else {
|
|
print $"❌ Failed to compile WASM module: ($module.name)"
|
|
}
|
|
}
|
|
|
|
# Generate JavaScript loader if modules were compiled
|
|
if ($compiled_modules | length) > 0 {
|
|
let loader_result = (generate_wasm_loader $compiled_modules $wasm_config $verbose)
|
|
if $loader_result.success {
|
|
$total_size = ($total_size + $loader_result.size)
|
|
}
|
|
}
|
|
|
|
if $verbose {
|
|
print $"✅ Compiled ($compiled_modules | length) WASM modules, total size: ($total_size) bytes"
|
|
}
|
|
|
|
{
|
|
size: $total_size,
|
|
modules: $compiled_modules
|
|
}
|
|
}
|
|
|
|
# Compile individual WASM module
|
|
def compile_wasm_module [module: record, wasm_config: record, dev: bool, verbose: bool] {
|
|
let source_path = ($wasm_config.source_dir | path join $module.source)
|
|
|
|
if not ($source_path | path exists) {
|
|
return {
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"Source file not found: ($source_path)"
|
|
}
|
|
}
|
|
|
|
let output_path = ($wasm_config.output_dir | path join $"($module.name).wasm")
|
|
|
|
match $module.lang {
|
|
"rust" => { compile_rust_wasm $module $source_path $output_path $wasm_config $dev $verbose }
|
|
"c" | "cpp" | "c++" => { compile_c_wasm $module $source_path $output_path $wasm_config $dev $verbose }
|
|
"assemblyscript" | "as" => { compile_assemblyscript_wasm $module $source_path $output_path $wasm_config $dev $verbose }
|
|
"go" => { compile_go_wasm $module $source_path $output_path $wasm_config $dev $verbose }
|
|
_ => {
|
|
return {
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"Unsupported language: ($module.lang)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Compile Rust to WASM
|
|
def compile_rust_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
|
|
let rust_config = if "rust" in ($wasm_config | columns) { $wasm_config.rust } else { {} }
|
|
let toolchain = if "toolchain" in ($rust_config | columns) { $rust_config.toolchain } else { "wasm-pack" }
|
|
let target = if "target" in ($rust_config | columns) { $rust_config.target } else { "web" }
|
|
|
|
if $verbose { print $" 🦀 Compiling Rust module: ($module.name)" }
|
|
|
|
match $toolchain {
|
|
"wasm-pack" => {
|
|
let build_args = if $dev { ["--dev"] } else { ["--release"] }
|
|
let scope = if "scope" in ($rust_config | columns) { ["--scope", $rust_config.scope] } else { [] }
|
|
|
|
try {
|
|
let args = ["build", "--target", $target, "--out-dir", ($wasm_config.output_dir | path join $module.name)] ++ $build_args ++ $scope ++ [($source_path | path dirname)]
|
|
run-external "wasm-pack" ...$args
|
|
|
|
# Copy main WASM file to expected location
|
|
let pkg_wasm = ($wasm_config.output_dir | path join $module.name | path join $"($module.name)_bg.wasm")
|
|
if ($pkg_wasm | path exists) {
|
|
cp $pkg_wasm $output_path
|
|
}
|
|
|
|
let wasm_size = if ($output_path | path exists) {
|
|
(ls $output_path | get size | first | into int)
|
|
} else { 0 }
|
|
|
|
{
|
|
name: $module.name,
|
|
success: true,
|
|
path: $output_path,
|
|
size: $wasm_size,
|
|
lang: "rust",
|
|
loader_path: ($wasm_config.output_dir | path join $module.name | path join $"($module.name).js")
|
|
}
|
|
} catch { |e|
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"wasm-pack build failed: ($e.msg)"
|
|
}
|
|
}
|
|
}
|
|
"cargo" => {
|
|
try {
|
|
# Use cargo for basic WASM compilation
|
|
let release_args = if $dev { [] } else { ["--release"] }
|
|
let args = ["build", "--target", "wasm32-unknown-unknown"] ++ $release_args
|
|
run-external "cargo" ...$args
|
|
|
|
# Find compiled WASM file
|
|
let target_dir = if $dev { "debug" } else { "release" }
|
|
let cargo_wasm = ($source_path | path dirname | path join "target" "wasm32-unknown-unknown" $target_dir $"($module.name).wasm")
|
|
|
|
if ($cargo_wasm | path exists) {
|
|
cp $cargo_wasm $output_path
|
|
|
|
let wasm_size = (ls $output_path | get size | first | into int)
|
|
|
|
{
|
|
name: $module.name,
|
|
success: true,
|
|
path: $output_path,
|
|
size: $wasm_size,
|
|
lang: "rust"
|
|
}
|
|
} else {
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: "Compiled WASM file not found"
|
|
}
|
|
}
|
|
} catch { |e|
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"cargo build failed: ($e.msg)"
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"Unsupported Rust toolchain: ($toolchain)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Compile C/C++ to WASM using Emscripten
|
|
def compile_c_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
|
|
if $verbose { print $" 🔧 Compiling C/C++ module: ($module.name)" }
|
|
|
|
let c_config = if "c" in ($wasm_config | columns) { $wasm_config.c } else { {} }
|
|
let optimization = if $dev { "-O0" } else { "-O3" }
|
|
let exports = if "exports" in ($c_config | columns) { $c_config.exports } else { ["main"] }
|
|
let export_args = ($exports | each { |fn| ["-s", $"EXPORTED_FUNCTIONS=['_($fn)']"] } | flatten)
|
|
|
|
try {
|
|
let args = [$source_path, "-o", $output_path, $optimization, "-s", "WASM=1", "-s", "MODULARIZE=1", "-s", "EXPORT_NAME=initWasm"] ++ $export_args
|
|
run-external "emcc" ...$args
|
|
|
|
let wasm_size = if ($output_path | path exists) {
|
|
(ls $output_path | get size | first | into int)
|
|
} else { 0 }
|
|
|
|
{
|
|
name: $module.name,
|
|
success: true,
|
|
path: $output_path,
|
|
size: $wasm_size,
|
|
lang: "c"
|
|
}
|
|
} catch { |e|
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"emcc compilation failed: ($e.msg)"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Compile AssemblyScript to WASM
|
|
def compile_assemblyscript_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
|
|
if $verbose { print $" 📝 Compiling AssemblyScript module: ($module.name)" }
|
|
|
|
let optimization = if $dev { "" } else { "--optimize" }
|
|
let args = if $dev { [] } else { ["--optimize"] }
|
|
|
|
try {
|
|
let asc_args = [$source_path, "--outFile", $output_path, "--bindings", "esm"] ++ $args
|
|
run-external "asc" ...$asc_args
|
|
|
|
let wasm_size = if ($output_path | path exists) {
|
|
(ls $output_path | get size | first | into int)
|
|
} else { 0 }
|
|
|
|
{
|
|
name: $module.name,
|
|
success: true,
|
|
path: $output_path,
|
|
size: $wasm_size,
|
|
lang: "assemblyscript"
|
|
}
|
|
} catch { |e|
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"AssemblyScript compilation failed: ($e.msg)"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Compile Go to WASM
|
|
def compile_go_wasm [module: record, source_path: string, output_path: string, wasm_config: record, dev: bool, verbose: bool] {
|
|
if $verbose { print $" 🐹 Compiling Go module: ($module.name)" }
|
|
|
|
try {
|
|
# Set environment variables for Go WASM compilation
|
|
with-env {
|
|
GOOS: "js",
|
|
GOARCH: "wasm"
|
|
} {
|
|
run-external "go" "build" "-o" $output_path $source_path
|
|
}
|
|
|
|
let wasm_size = if ($output_path | path exists) {
|
|
(ls $output_path | get size | first | into int)
|
|
} else { 0 }
|
|
|
|
{
|
|
name: $module.name,
|
|
success: true,
|
|
path: $output_path,
|
|
size: $wasm_size,
|
|
lang: "go"
|
|
}
|
|
} catch { |e|
|
|
{
|
|
name: $module.name,
|
|
success: false,
|
|
error: $"Go compilation failed: ($e.msg)"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate WASM toolchain
|
|
def validate_wasm_toolchain [wasm_config: record, verbose: bool] {
|
|
mut missing_tools = []
|
|
|
|
# Check for common WASM tools
|
|
let tools_to_check = [
|
|
{ name: "wasm-pack", command: "wasm-pack", required_for: "Rust" },
|
|
{ name: "cargo", command: "cargo", required_for: "Rust" },
|
|
{ name: "emcc", command: "emcc", required_for: "C/C++" },
|
|
{ name: "asc", command: "asc", required_for: "AssemblyScript" },
|
|
{ name: "go", command: "go", required_for: "Go" },
|
|
{ name: "wasm-opt", command: "wasm-opt", required_for: "Optimization" }
|
|
]
|
|
|
|
for tool in $tools_to_check {
|
|
let available = (which $tool.command | length) > 0
|
|
if not $available {
|
|
$missing_tools = ($missing_tools | append $tool)
|
|
} else if $verbose {
|
|
print $" ✅ ($tool.name) available"
|
|
}
|
|
}
|
|
|
|
# Check which languages are actually needed
|
|
let needed_languages = if "modules" in ($wasm_config | columns) {
|
|
($wasm_config.modules | get lang | uniq)
|
|
} else { [] }
|
|
|
|
let critical_missing = ($missing_tools | where { |tool|
|
|
$needed_languages | any { |lang| $lang in $tool.required_for }
|
|
})
|
|
|
|
if ($critical_missing | length) > 0 {
|
|
{
|
|
valid: false,
|
|
error: $"Missing required tools: ($critical_missing | get name | str join ', ')"
|
|
}
|
|
} else {
|
|
{ valid: true }
|
|
}
|
|
}
|
|
|
|
# Generate JavaScript loader for WASM modules
|
|
def generate_wasm_loader [modules: list, wasm_config: record, verbose: bool] {
|
|
if $verbose { print " 📜 Generating WASM loader..." }
|
|
|
|
# Find framework root by looking for framework directory
|
|
let current_dir = $env.PWD
|
|
let framework_templates = ($current_dir | path join "framework" "templates")
|
|
|
|
let template_path = ($framework_templates | path join "wasm-loader.js.template")
|
|
if not ($template_path | path exists) {
|
|
return {
|
|
success: false,
|
|
error: $"WASM loader template not found: ($template_path)"
|
|
}
|
|
}
|
|
|
|
# Create module loading calls
|
|
let module_calls = ($modules | each { |mod|
|
|
let module_name = $mod.name
|
|
$" this.loadModule\('($module_name)', './($module_name).wasm'\),"
|
|
} | str join "\n")
|
|
|
|
# Load template and replace placeholder
|
|
let template_content = (open $template_path)
|
|
let loader_content = ($template_content | str replace "{{MODULE_PROMISES}}" $module_calls)
|
|
|
|
let loader_path = ($wasm_config.output_dir | path join "wasm-loader.js")
|
|
$loader_content | save --force $loader_path
|
|
|
|
if $verbose { print $" ✅ Generated WASM loader: ($loader_path)" }
|
|
|
|
{
|
|
success: true,
|
|
path: $loader_path,
|
|
size: ($loader_content | str length)
|
|
}
|
|
}
|
|
|
|
# Optimize WASM with wasm-opt (if available)
|
|
export def optimize_wasm_binary [wasm_path: string, config: record] {
|
|
let wasm_opt_available = (which wasm-opt | length) > 0
|
|
|
|
if not $wasm_opt_available {
|
|
return { optimized: false, reason: "wasm-opt not available" }
|
|
}
|
|
|
|
let wasm_config = if "wasm" in ($config | columns) { $config.wasm } else { {} }
|
|
let optimization = if "optimization" in ($wasm_config | columns) { $wasm_config.optimization } else { {} }
|
|
|
|
let opt_level = if "opt_level" in ($optimization | columns) { $optimization.opt_level } else { 3 }
|
|
let shrink_level = if "shrink_level" in ($optimization | columns) { $optimization.shrink_level } else { 2 }
|
|
|
|
let temp_path = $"($wasm_path).opt"
|
|
|
|
try {
|
|
let wasm_opt_args = [$wasm_path, "-o", $temp_path, $"-O($opt_level)", $"-s($shrink_level)", "--enable-simd", "--enable-bulk-memory"]
|
|
run-external "wasm-opt" ...$wasm_opt_args
|
|
|
|
# Replace original with optimized version
|
|
mv $temp_path $wasm_path
|
|
|
|
{ optimized: true }
|
|
} catch { |e|
|
|
# Clean up temp file if it exists
|
|
if ($temp_path | path exists) {
|
|
rm $temp_path
|
|
}
|
|
|
|
{ optimized: false, reason: $"wasm-opt failed: ($e.msg)" }
|
|
}
|
|
}
|
|
|
|
# Get WASM module information
|
|
export def analyze_wasm [wasm_path: string] {
|
|
if not ($wasm_path | path exists) {
|
|
return null
|
|
}
|
|
|
|
let size = (ls $wasm_path | get size | first | into int)
|
|
let size_kb = ($size / 1024 | math round -p 1)
|
|
|
|
{
|
|
path: $wasm_path,
|
|
size: $size,
|
|
size_kb: $size_kb,
|
|
exists: true
|
|
}
|
|
} |