miniray

module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 28, 2025 License: CC0-1.0

README

miniray

A high-performance WGSL (WebGPU Shading Language) minifier written in Go, inspired by esbuild's architecture.

Try the online demo

Are you an LLM or AI agent? See BUILDING_WITH_MINIRAY.md for a token-efficient reference covering all options, patterns, and gotchas.

Features

  • Whitespace minification - Remove unnecessary whitespace and newlines
  • Identifier renaming - Shorten local variable and function names
  • Syntax optimization - Optimize numeric literals and syntax patterns
  • Source maps - Generate v3 source maps for debugging minified shaders
  • Shader reflection - Extract bindings, struct layouts, and entry points with WGSL-spec memory layout computation
  • Semantic validation - Type checking, symbol resolution, and uniformity analysis
  • API-aware - Preserves entry point names, @location, @binding, and @builtin declarations
  • WebAssembly build - Run in browsers and Node.js via miniray package

Installation

CLI (Go)
# From source
go install github.com/HugoDaniel/miniray/cmd/miniray@latest

# Or build locally
git clone https://github.com/HugoDaniel/miniray.git
cd miniray
make build
Browser/Node.js (WASM)
npm install miniray
import { initialize, minify } from 'miniray';

await initialize({ wasmURL: '/path/to/miniray.wasm' });

const result = minify(source, {
  minifyWhitespace: true,
  minifyIdentifiers: true,
});

console.log(result.code);

See npm/miniray/README.md for full WASM documentation.

Usage

# Minify a file
miniray shader.wgsl -o shader.min.wgsl

# Minify from stdin
cat shader.wgsl | miniray > shader.min.wgsl

# Whitespace-only minification (safest)
miniray --no-mangle shader.wgsl

# Preserve specific names
miniray --keep-names myHelper,computeOffset shader.wgsl

# Mangle uniform/storage bindings directly (smaller output)
miniray --mangle-external-bindings shader.wgsl

# Use a specific config file
miniray --config myconfig.json shader.wgsl

# Reflect shader (extract bindings, structs, entry points as JSON)
miniray reflect shader.wgsl
miniray reflect shader.wgsl -o info.json
miniray reflect --compact shader.wgsl | jq '.bindings'

# Validate shader (semantic type checking)
miniray validate shader.wgsl
miniray validate --json shader.wgsl
miniray validate --strict shader.wgsl
Options
Flag Description
-o <file> Write output to file (default: stdout)
--config <file> Use specific config file
--no-config Ignore config files
--minify Enable all minification (default)
--minify-whitespace Remove unnecessary whitespace
--minify-identifiers Shorten identifier names
--minify-syntax Apply syntax optimizations
--no-mangle Don't rename identifiers
--mangle-external-bindings Rename uniform/storage vars directly
--no-tree-shaking Disable dead code elimination
--preserve-uniform-struct-types Preserve struct types used in uniforms
--keep-names <names> Comma-separated names to preserve
--source-map Generate source map file (.map)
--source-map-inline Embed source map as inline data URI
--source-map-sources Include original source in source map
--version Print version and exit
--help Print help and exit
Reflect Subcommand

Extract binding information, struct memory layouts, and entry points as JSON:

miniray reflect [options] <input.wgsl>
Flag Description
-o <file> Write JSON output to file (default: stdout)
--compact Output compact JSON (default: pretty-printed)

Output includes:

  • bindings: Array of @group/@binding variables with memory layouts
  • structs: Map of struct names to their layouts (size, alignment, fields)
  • entryPoints: Array of entry point functions with stage and workgroup size
  • errors: Parse errors if any
Validate Subcommand

Validate WGSL source for semantic errors:

miniray validate [options] <input.wgsl>
Flag Description
-o <file> Write output to file (default: stdout)
--json Output JSON format with detailed diagnostics
--strict Treat warnings as errors

Example output:

shader.wgsl:5:12: error: cannot return 'i32' from function returning 'f32' [E0200]
shader.wgsl:8:5: warning: derivative_uniformity violation [W0001]

2 error(s), 1 warning(s)

JSON output (--json):

{
  "valid": false,
  "diagnostics": [
    {
      "severity": "error",
      "code": "E0200",
      "message": "cannot return 'i32' from function returning 'f32'",
      "line": 5,
      "column": 12
    }
  ],
  "errorCount": 1,
  "warningCount": 0
}

Validation checks:

  • Type mismatches (assignments, return statements, function arguments)
  • Undefined symbols (variables, functions, types)
  • Invalid operations (operators, array indexing, member access)
  • Entry point requirements (return types, parameter attributes)
  • Uniformity analysis for texture sampling and derivatives
  • WGSL spec compliance (reserved identifiers, recursion prohibition)
Config File

The minifier searches for config files in the current directory and parent directories:

  • miniray.json
  • .minirayrc
  • .minirayrc.json

Example miniray.json:

{
    "minifyWhitespace": true,
    "minifyIdentifiers": true,
    "minifySyntax": true,
    "mangleExternalBindings": false,
    "treeShaking": true,
    "preserveUniformStructTypes": false,
    "keepNames": ["myUniform", "myBuffer"]
}

CLI flags override config file settings.

Platform Configurations

Pre-built configuration templates are available in the configs/ directory:

compute.toys

compute.toys is a WebGPU compute shader playground. Use the provided config to preserve all platform-specific bindings and helpers:

miniray --config configs/compute.toys.json my_shader.wgsl

Or copy configs/compute.toys.json to your project directory as miniray.json for automatic detection.

Preserved names:

  • Uniforms: time, mouse, custom, dispatch
  • Textures: screen, pass_in, pass_out, channel0, channel1
  • Samplers: nearest, bilinear, trilinear, *_repeat variants
  • Helpers: keyDown, passLoad, passStore, passSampleLevelBilinearRepeat
  • Type aliases: int, float, float2..float4, float2x2..float4x4, etc.
  • Entry point: main_image

Size reduction: ~55% on typical shaders

PNGine

PNGine is a WebGPU DSL that embeds shader code in PNG files. It detects builtin uniforms by struct type name, so these must be preserved:

miniray --config configs/pngine.json shader.wgsl

Key feature: Uses preserveUniformStructTypes: true to automatically preserve any struct type used in var<uniform> or var<storage> declarations.

Preserved names:

  • Struct types: PngineInputs, TimeInputs, CanvasInputs, SceneTimeInputs, GlobalTimeInputs
  • Uniform variable names (preserved by default)
  • Struct field names (preserved by default - accessed via . operator)
  • Entry point function names

What gets renamed:

  • Helper functions
  • Local variables and parameters
  • Struct types not used in uniform/storage declarations

Example:

// Input
struct PngineInputs { time: f32, canvasW: f32 }
@group(0) @binding(0) var<uniform> inputs: PngineInputs;
fn helper(t: f32) -> f32 { return t * 2.0; }
@vertex fn vs() -> @builtin(position) vec4f {
  let adjusted = helper(inputs.time);
  return vec4f(adjusted);
}

// Output
struct PngineInputs{time:f32,canvasW:f32}@group(0) @binding(0) var<uniform> inputs:PngineInputs;fn a(b:f32)->f32{return b*2.;}@vertex fn vs()->@builtin(position) vec4f{let c=a(inputs.time);return vec4f(c);}
Source Maps

Generate source maps to debug minified shaders by mapping back to original source positions.

# Generate external source map file (.map)
miniray -o shader.min.wgsl --source-map shader.wgsl
# Creates: shader.min.wgsl and shader.min.wgsl.map

# Embed source map as inline data URI
miniray --source-map-inline shader.wgsl > shader.min.wgsl

# Include original source in source map (self-contained)
miniray -o shader.min.wgsl --source-map --source-map-sources shader.wgsl

The generated source map follows the Source Map v3 specification:

{
  "version": 3,
  "file": "shader.min.wgsl",
  "sources": ["shader.wgsl"],
  "sourcesContent": ["...original source..."],
  "names": ["longVariableName", "helperFunction"],
  "mappings": "MAAAA,QAAAC,..."
}

What gets mapped:

  • Renamed identifiers (variables, functions, structs) → original names in names array
  • Generated positions → source positions via VLQ-encoded mappings
WebGPU Compatibility

WebGPU does not natively consume source maps—there's no sourceMap field in GPUShaderModuleDescriptor. However, miniray's source maps use UTF-16 column positions matching WebGPU's GPUCompilationMessage format, making them suitable for custom error translation tooling:

import { SourceMapConsumer } from 'source-map';

// Minify with source map
const result = minify(source, { sourceMap: true, sourceMapSources: true });
const module = device.createShaderModule({ code: result.code });

// Translate compilation errors back to original source
const info = await module.getCompilationInfo();
const consumer = await new SourceMapConsumer(JSON.parse(result.sourceMap));

for (const msg of info.messages) {
  if (msg.lineNum > 0) {
    const pos = consumer.originalPositionFor({
      line: msg.lineNum,
      column: msg.linePos - 1  // source-map lib uses 0-based columns
    });
    console.log(`${msg.type}: ${msg.message}`);
    console.log(`  Original: ${pos.source}:${pos.line}:${pos.column + 1}`);
    if (pos.name) console.log(`  Identifier was: ${pos.name}`);
  }
}
Shader Reflection

Extract binding information, struct memory layouts, and entry points from WGSL source:

import { initialize, reflect } from 'miniray';

await initialize();

const result = reflect(`
  struct Uniforms { time: f32, resolution: vec2<u32> }
  @group(0) @binding(0) var<uniform> u: Uniforms;
  @group(0) @binding(1) var tex: texture_2d<f32>;
  @compute @workgroup_size(8, 8) fn main() {}
`);

console.log(result.bindings);
// [
//   { group: 0, binding: 0, name: "u", nameMapped: "u", addressSpace: "uniform",
//     type: "Uniforms", typeMapped: "Uniforms",
//     layout: { size: 16, alignment: 8, fields: [...] } },
//   { group: 0, binding: 1, name: "tex", addressSpace: "handle",
//     type: "texture_2d<f32>", layout: null }
// ]

console.log(result.entryPoints);
// [{ name: "main", stage: "compute", workgroupSize: [8, 8, 1] }]

console.log(result.structs);
// { "Uniforms": { size: 16, alignment: 8, fields: [...] } }

Memory layouts follow the WGSL specification (vec3 has align=16, size=12; proper struct padding).

Array Bindings

For array type bindings (e.g., array<Particle> or array<vec4f, 100>), reflection provides detailed array metadata:

const result = reflect(`
  struct Particle { position: vec3f, velocity: vec3f, lifetime: f32 }
  @group(0) @binding(0) var<storage> particles: array<Particle>;
`);

const binding = result.bindings[0];
console.log(binding.array);
// {
//   depth: 1,                    // Nesting depth (1 = simple array)
//   elementCount: null,          // null for runtime-sized arrays
//   elementStride: 32,           // Bytes per element (size + padding)
//   totalSize: null,             // null for runtime-sized
//   elementType: "Particle",     // Original type name
//   elementTypeMapped: "Particle", // Minified name (see below)
//   elementLayout: { size: 28, alignment: 16, fields: [...] }
// }

For nested arrays (e.g., array<array<f32, 4>, 10>), the array field contains nested ArrayInfo:

// depth: 1 for outer, depth: 2 for inner
// outer.array points to inner array info
Combined Minify + Reflect

When you need both minified code and reflection data with actual minified names, use minifyAndReflect:

import { initialize, minifyAndReflect } from 'miniray';

await initialize();

const result = minifyAndReflect(`
  struct Particle { position: vec3f, velocity: vec3f }
  @group(0) @binding(0) var<storage> particles: array<Particle>;
  @compute @workgroup_size(64) fn main() { /* ... */ }
`);

// result.code - minified WGSL
// result.reflect - reflection with minified names

console.log(result.reflect.bindings[0]);
// {
//   name: "particles",           // Original name
//   nameMapped: "particles",     // Minified (unchanged - external binding)
//   type: "array<Particle>",     // Original type
//   typeMapped: "array<a>",      // Minified type (Particle → a)
//   array: {
//     elementType: "Particle",
//     elementTypeMapped: "a",    // Minified struct name
//     elementLayout: { fields: [
//       { name: "position", nameMapped: "position", ... },
//       { name: "velocity", nameMapped: "velocity", ... }
//     ]}
//   }
// }

This is useful when creating buffer bindings that need to reference minified struct field names.

Go API
import "github.com/HugoDaniel/miniray/pkg/api"

// Full minification
result := api.Minify(source)

// Custom options
result := api.MinifyWithOptions(source, api.MinifyOptions{
    MinifyWhitespace:           true,
    MinifyIdentifiers:          true,
    MinifySyntax:               true,
    MangleExternalBindings:     false, // keep uniform/storage names for reflection
    TreeShaking:                true,  // remove unused declarations
    PreserveUniformStructTypes: true,  // keep struct types used in uniforms
    KeepNames:                  []string{"myHelper"},
})

// Safe whitespace-only
result := api.MinifyWhitespaceOnly(source)

fmt.Printf("Minified: %d -> %d bytes\n",
    result.OriginalSize, result.MinifiedSize)
fmt.Println(result.Code)

// Shader reflection (without minification)
info := api.Reflect(source)
for _, binding := range info.Bindings {
    fmt.Printf("@group(%d) @binding(%d) %s: %s\n",
        binding.Group, binding.Binding, binding.Name, binding.Type)
    if binding.Layout != nil {
        fmt.Printf("  size=%d, alignment=%d\n",
            binding.Layout.Size, binding.Layout.Alignment)
    }
    if binding.Array != nil {
        fmt.Printf("  array: elementStride=%d, elementType=%s\n",
            binding.Array.ElementStride, binding.Array.ElementType)
    }
}

// Combined minify + reflect (with minified names)
combined := api.MinifyAndReflect(source)
fmt.Println(combined.Code)  // Minified WGSL
for _, b := range combined.Reflect.Bindings {
    fmt.Printf("%s -> %s (type: %s -> %s)\n",
        b.Name, b.NameMapped, b.Type, b.TypeMapped)
}

// Semantic validation
valResult := api.Validate(source)
if valResult.Valid {
    fmt.Println("Shader is valid!")
} else {
    for _, d := range valResult.Diagnostics {
        fmt.Printf("%d:%d: %s: %s\n",
            d.Line, d.Column, d.Severity, d.Message)
    }
}

// Validation with options
valResult := api.ValidateWithOptions(source, api.ValidateOptions{
    StrictMode: true,  // Treat warnings as errors
    DiagnosticFilters: map[string]string{
        "derivative_uniformity": "off",  // Disable specific warning
    },
})
Source Maps in Go
result := api.MinifyWithOptions(source, api.MinifyOptions{
    MinifyIdentifiers: true,
    SourceMap: true,
    SourceMapOptions: api.SourceMapOptions{
        File:          "shader.min.wgsl",
        SourceName:    "shader.wgsl",
        IncludeSource: true,
    },
})

// result.SourceMap contains JSON string
// result.SourceMapDataURI contains data:application/json;base64,... for inline embedding
fmt.Println(result.SourceMap)
C Library (FFI)

Build a static library for integration with C, Zig, Rust, or other languages:

make lib
# Creates: build/libminiray.a and build/libminiray.h
C Example
#include <stdio.h>
#include <stdlib.h>
#include "libminiray.h"

int main() {
    const char* source =
        "struct Data { value: f32 }\n"
        "@group(0) @binding(0) var<uniform> data: Data;\n"
        "@fragment fn main() -> @location(0) vec4f {\n"
        "    return vec4f(data.value);\n"
        "}\n";

    char* code = NULL;
    int code_len = 0;
    char* json = NULL;
    int json_len = 0;

    // Minify with default options (pass NULL for options)
    int err = miniray_minify(
        (char*)source, strlen(source),
        NULL, 0,           // options_json, options_len
        &code, &code_len,
        &json, &json_len   // NULL to skip JSON stats
    );

    if (err == 0) {
        printf("Minified (%d bytes):\n%.*s\n", code_len, code_len, code);
    }

    // Free allocated memory
    miniray_free(code);
    miniray_free(json);

    return err;
}

Compile with:

gcc -o example example.c -L./build -lminiray -lpthread -lm
Custom Options
// Options as JSON string
const char* options = "{\"minifyWhitespace\":true,\"minifyIdentifiers\":true}";

int err = miniray_minify(
    source, source_len,
    (char*)options, strlen(options),
    &code, &code_len,
    NULL, NULL  // Skip JSON result
);
Reflection
char* json = NULL;
int json_len = 0;

int err = miniray_reflect(source, strlen(source), &json, &json_len);
if (err == 0) {
    // json contains: {"bindings":[...],"structs":{...},"entryPoints":[...]}
    printf("Reflection: %.*s\n", json_len, json);
}
miniray_free(json);
Combined Minify + Reflect

Get minified code with reflection that includes mapped (minified) names:

char* code = NULL;
int code_len = 0;
char* json = NULL;
int json_len = 0;

int err = miniray_minify_and_reflect(
    source, strlen(source),
    NULL, 0,  // default options
    &code, &code_len,
    &json, &json_len
);

if (err == 0) {
    // code = minified WGSL
    // json = {"code":"...","reflect":{"bindings":[{"name":"data","nameMapped":"data",...}],...}}
}

miniray_free(code);
miniray_free(json);
Validation
char* json = NULL;
int json_len = 0;

// Options as JSON (optional)
const char* options = "{\"strictMode\":true}";

int err = miniray_validate(
    source, strlen(source),
    (char*)options, strlen(options),  // or NULL, 0 for defaults
    &json, &json_len
);

if (err == 0) {
    // json contains: {"valid":true/false,"diagnostics":[...],"errorCount":N,"warningCount":N}
    printf("Validation: %.*s\n", json_len, json);
}
miniray_free(json);

Validation options:

{
  "strictMode": true,
  "diagnosticFilters": {
    "derivative_uniformity": "off"
  }
}
Error Codes
Code Constant Description
0 MINIRAY_OK Success
1 MINIRAY_ERR_JSON_ENCODE Failed to encode JSON result
2 MINIRAY_ERR_NULL_INPUT NULL pointer passed for required parameter
3 MINIRAY_ERR_JSON_DECODE Failed to parse options JSON
Zig Example
const c = @cImport({
    @cInclude("libminiray.h");
});

pub fn minify(source: []const u8) ![]const u8 {
    var code: [*c]u8 = null;
    var code_len: c_int = 0;

    const err = c.miniray_minify(
        @ptrCast(source.ptr), @intCast(source.len),
        null, 0,
        &code, &code_len,
        null, null
    );

    if (err != 0) return error.MinifyFailed;
    defer c.miniray_free(code);

    return code[0..@intCast(code_len)];
}

What Gets Minified

Always Preserved
  • Entry point function names (@vertex, @fragment, @compute)
  • @builtin names (e.g., position, global_invocation_id)
  • @location struct member names
  • @group/@binding binding indices
  • override constant names (unless using @id)
External Bindings (uniform/storage)

By default, var<uniform> and var<storage> declarations keep their original names for WebGPU binding reflection compatibility:

// Input
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
fn main() { return uniforms.value * 2.0; }

// Output (default) - declaration preserved, alias used internally
var<uniform> uniforms:Uniforms;let a=uniforms;fn b(){return a.value*2.0;}

// Output (--mangle-external-bindings) - smaller, but breaks reflection
var<uniform> a:Uniforms;fn b(){return a.value*2.0;}

Use --mangle-external-bindings only if you don't use WebGPU's binding reflection APIs.

Minified (with --minify-identifiers)
  • Local variables (let, var)
  • Function parameters
  • Non-entry-point function names
  • Private struct names (no API interface)
  • Type alias names
Syntax Optimizations (with --minify-syntax)
  • Numeric literal shortening: 0.5.5, 1.01.
  • Scientific notation: 1000000.01e6
  • Vector constructor shorthand: vec3<f32>vec3f

Architecture

The minifier follows esbuild's architecture:

Source → Lexer → Parser → AST → Minifier → Printer → Output
                           ↑
                        Renamer
Packages
Package Description
internal/lexer Tokenizes WGSL source (Unicode XID, nested comments)
internal/parser Recursive descent parser with symbol table
internal/ast Complete WGSL AST types
internal/renamer Frequency-based identifier renaming
internal/printer Code generation with minification
internal/reflect Shader reflection and memory layout computation
internal/minifier Orchestrates the minification pipeline
pkg/api Public API for programmatic use
cmd/miniray CLI entry point

Development

# Build
make build

# Test
make test

# Format and lint
make check

# Build for all platforms
make build-all

WGSL Spec Compliance

This minifier targets WGSL as specified in the WebGPU Shading Language specification. Key considerations:

  • Identifiers use Unicode XID_Start/XID_Continue
  • Block comments can nest (/* /* nested */ */)
  • Reserved words (~120) cannot be used as identifiers
  • _ alone and __* prefixes are invalid identifiers
  • Boolean literals true/false are keywords (not expressions)

License

See LICENSE file.

Contributing

Contributions welcome! Please open an issue or pull request.

Directories

Path Synopsis
cmd
miniray command
Command miniray minifies WGSL shader source code.
Command miniray minifies WGSL shader source code.
miniray-lib command
Package main provides a C-callable static library for WGSL minification, reflection, and validation.
Package main provides a C-callable static library for WGSL minification, reflection, and validation.
miniray-wasm command
Command miniray-wasm is the WebAssembly build of the WGSL minifier.
Command miniray-wasm is the WebAssembly build of the WGSL minifier.
internal
ast
Package ast defines the Abstract Syntax Tree types for WGSL.
Package ast defines the Abstract Syntax Tree types for WGSL.
builtins
Package builtins defines WGSL built-in functions and their type signatures.
Package builtins defines WGSL built-in functions and their type signatures.
config
Package config handles loading minifier configuration from files.
Package config handles loading minifier configuration from files.
dce
Package dce implements dead code elimination for WGSL modules.
Package dce implements dead code elimination for WGSL modules.
diagnostic
Package diagnostic provides error reporting and diagnostic messages for WGSL validation.
Package diagnostic provides error reporting and diagnostic messages for WGSL validation.
lexer
Package lexer provides tokenization for WGSL source code.
Package lexer provides tokenization for WGSL source code.
minifier
Package minifier provides the main minification API.
Package minifier provides the main minification API.
parser
Package parser provides WGSL parsing into an AST.
Package parser provides WGSL parsing into an AST.
printer
Package printer outputs WGSL code from an AST.
Package printer outputs WGSL code from an AST.
reflect
Package reflect provides WGSL shader reflection capabilities.
Package reflect provides WGSL shader reflection capabilities.
renamer
Package renamer provides symbol renaming for minification.
Package renamer provides symbol renaming for minification.
sourcemap
Package sourcemap provides source map generation for the WGSL minifier.
Package sourcemap provides source map generation for the WGSL minifier.
test
Package test provides testing utilities for the WGSL minifier.
Package test provides testing utilities for the WGSL minifier.
types
Package types provides the WGSL type system for semantic validation.
Package types provides the WGSL type system for semantic validation.
validator
Package validator provides uniformity analysis for WGSL shaders.
Package validator provides uniformity analysis for WGSL shaders.
validator_tests
Package validator_tests provides test infrastructure for WGSL validation tests.
Package validator_tests provides test infrastructure for WGSL validation tests.
pkg
api
Package api provides the public API for the WGSL minifier.
Package api provides the public API for the WGSL minifier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL