miniray 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # miniray
2
+
3
+ WGSL minifier for WebGPU shaders - WebAssembly build.
4
+
5
+ This package provides a WASM build of the [miniray](https://github.com/HugoDaniel/miniray) that runs in browsers and Node.js.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install miniray
11
+ ```
12
+
13
+ ## Browser Usage
14
+
15
+ ```html
16
+ <script src="node_modules/miniray/wasm_exec.js"></script>
17
+ <script src="node_modules/miniray/lib/browser.js"></script>
18
+ <script>
19
+ (async () => {
20
+ await miniray.initialize({ wasmURL: 'node_modules/miniray/miniray.wasm' });
21
+
22
+ const result = miniray.minify(`
23
+ @vertex fn main() -> @builtin(position) vec4f {
24
+ return vec4f(0.0);
25
+ }
26
+ `);
27
+
28
+ console.log(result.code);
29
+ // "@vertex fn main()->@builtin(position) vec4f{return vec4f(0.0);}"
30
+ })();
31
+ </script>
32
+ ```
33
+
34
+ ### ESM Usage
35
+
36
+ ```javascript
37
+ import { initialize, minify } from 'miniray';
38
+
39
+ await initialize({ wasmURL: '/path/to/miniray.wasm' });
40
+
41
+ const result = minify(source, {
42
+ minifyWhitespace: true,
43
+ minifyIdentifiers: true,
44
+ minifySyntax: true,
45
+ });
46
+ ```
47
+
48
+ ## Node.js Usage
49
+
50
+ ```javascript
51
+ const { initialize, minify } = require('miniray');
52
+
53
+ await initialize(); // Automatically finds miniray.wasm
54
+
55
+ const result = minify(source);
56
+ console.log(result.code);
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### `initialize(options)`
62
+
63
+ Initialize the WASM module. Must be called before `minify()`.
64
+
65
+ ```typescript
66
+ interface InitializeOptions {
67
+ wasmURL?: string | URL; // Path or URL to miniray.wasm
68
+ wasmModule?: WebAssembly.Module; // Pre-compiled module
69
+ }
70
+ ```
71
+
72
+ ### `minify(source, options?)`
73
+
74
+ Minify WGSL source code.
75
+
76
+ ```typescript
77
+ interface MinifyOptions {
78
+ minifyWhitespace?: boolean; // Remove whitespace (default: true)
79
+ minifyIdentifiers?: boolean; // Rename identifiers (default: true)
80
+ minifySyntax?: boolean; // Optimize syntax (default: true)
81
+ mangleExternalBindings?: boolean; // Mangle uniform/storage names (default: false)
82
+ keepNames?: string[]; // Names to preserve from renaming
83
+ }
84
+
85
+ interface MinifyResult {
86
+ code: string; // Minified code
87
+ errors: MinifyError[]; // Parse/minification errors
88
+ originalSize: number; // Input size in bytes
89
+ minifiedSize: number; // Output size in bytes
90
+ }
91
+ ```
92
+
93
+ ### `isInitialized()`
94
+
95
+ Returns `true` if the WASM module is initialized.
96
+
97
+ ### `version`
98
+
99
+ The version of the minifier.
100
+
101
+ ## Options
102
+
103
+ ### `minifyWhitespace`
104
+
105
+ Remove unnecessary whitespace and newlines.
106
+
107
+ ### `minifyIdentifiers`
108
+
109
+ Rename local variables, function parameters, and helper functions to shorter names. Entry points and API-facing declarations are preserved.
110
+
111
+ ### `minifySyntax`
112
+
113
+ Apply syntax-level optimizations like numeric literal shortening.
114
+
115
+ ### `mangleExternalBindings`
116
+
117
+ Control how `var<uniform>` and `var<storage>` names are handled:
118
+
119
+ - `false` (default): Original names are preserved in declarations, short aliases are used internally. This maintains compatibility with WebGPU's binding reflection APIs.
120
+ - `true`: Names are mangled directly for smaller output, but breaks binding reflection.
121
+
122
+ ```javascript
123
+ // Input
124
+ const shader = `
125
+ @group(0) @binding(0) var<uniform> uniforms: f32;
126
+ fn getValue() -> f32 { return uniforms * 2.0; }
127
+ `;
128
+
129
+ // With mangleExternalBindings: false (default)
130
+ // Output: "@group(0) @binding(0) var<uniform> uniforms:f32;let a=uniforms;fn b()->f32{return a*2.0;}"
131
+
132
+ // With mangleExternalBindings: true
133
+ // Output: "@group(0) @binding(0) var<uniform> a:f32;fn b()->f32{return a*2.0;}"
134
+ ```
135
+
136
+ ### `keepNames`
137
+
138
+ Array of identifier names that should not be renamed:
139
+
140
+ ```javascript
141
+ minify(source, {
142
+ minifyIdentifiers: true,
143
+ keepNames: ['myHelper', 'computeValue'],
144
+ });
145
+ ```
146
+
147
+ ## Using with Bundlers
148
+
149
+ ### Vite
150
+
151
+ ```javascript
152
+ import { initialize, minify } from 'miniray';
153
+ import wasmURL from 'miniray/miniray.wasm?url';
154
+
155
+ await initialize({ wasmURL });
156
+ ```
157
+
158
+ ### Webpack
159
+
160
+ ```javascript
161
+ import { initialize, minify } from 'miniray';
162
+
163
+ // Configure webpack to handle .wasm files
164
+ await initialize({ wasmURL: new URL('miniray/miniray.wasm', import.meta.url) });
165
+ ```
166
+
167
+ ## Pre-compiling WASM
168
+
169
+ For better performance when creating multiple instances:
170
+
171
+ ```javascript
172
+ const wasmModule = await WebAssembly.compileStreaming(
173
+ fetch('/miniray.wasm')
174
+ );
175
+
176
+ // Share module across workers
177
+ await initialize({ wasmModule });
178
+ ```
179
+
180
+ ## Performance
181
+
182
+ - WASM binary: ~3.6MB
183
+ - Initialization: ~100ms (first load)
184
+ - Transform: <1ms for typical shaders
185
+
186
+ ## License
187
+
188
+ MIT
package/bin/miniray ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Parse command line arguments
7
+ const args = process.argv.slice(2);
8
+
9
+ function printUsage() {
10
+ console.log(`Usage: miniray [options] [file]
11
+
12
+ Options:
13
+ -h, --help Show this help message
14
+ -v, --version Show version
15
+ -o, --output <file> Output file (default: stdout)
16
+ --no-mangle Disable identifier minification
17
+ --no-whitespace Disable whitespace minification
18
+ --mangle-external-bindings Mangle uniform/storage variable names
19
+ --keep-names <names> Comma-separated list of names to preserve
20
+ --config <file> Load config from JSON file
21
+
22
+ If no file is specified, reads from stdin.
23
+
24
+ Examples:
25
+ miniray shader.wgsl
26
+ miniray shader.wgsl -o shader.min.wgsl
27
+ miniray --keep-names main,uniforms shader.wgsl
28
+ cat shader.wgsl | miniray > shader.min.wgsl
29
+ `);
30
+ }
31
+
32
+ function printVersion() {
33
+ const pkg = require('../package.json');
34
+ console.log(`miniray ${pkg.version}`);
35
+ }
36
+
37
+ // Parse options
38
+ let inputFile = null;
39
+ let outputFile = null;
40
+ let options = {
41
+ minifyWhitespace: true,
42
+ minifyIdentifiers: true,
43
+ minifySyntax: true,
44
+ mangleExternalBindings: false,
45
+ keepNames: []
46
+ };
47
+ let configFile = null;
48
+
49
+ for (let i = 0; i < args.length; i++) {
50
+ const arg = args[i];
51
+
52
+ if (arg === '-h' || arg === '--help') {
53
+ printUsage();
54
+ process.exit(0);
55
+ } else if (arg === '-v' || arg === '--version') {
56
+ printVersion();
57
+ process.exit(0);
58
+ } else if (arg === '-o' || arg === '--output') {
59
+ outputFile = args[++i];
60
+ } else if (arg === '--no-mangle') {
61
+ options.minifyIdentifiers = false;
62
+ } else if (arg === '--no-whitespace') {
63
+ options.minifyWhitespace = false;
64
+ } else if (arg === '--mangle-external-bindings') {
65
+ options.mangleExternalBindings = true;
66
+ } else if (arg === '--keep-names') {
67
+ options.keepNames = args[++i].split(',').map(s => s.trim());
68
+ } else if (arg === '--config') {
69
+ configFile = args[++i];
70
+ } else if (!arg.startsWith('-')) {
71
+ inputFile = arg;
72
+ } else {
73
+ console.error(`Unknown option: ${arg}`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ // Load config file if specified
79
+ if (configFile) {
80
+ try {
81
+ const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
82
+ if (config.minifyWhitespace !== undefined) options.minifyWhitespace = config.minifyWhitespace;
83
+ if (config.minifyIdentifiers !== undefined) options.minifyIdentifiers = config.minifyIdentifiers;
84
+ if (config.minifySyntax !== undefined) options.minifySyntax = config.minifySyntax;
85
+ if (config.mangleExternalBindings !== undefined) options.mangleExternalBindings = config.mangleExternalBindings;
86
+ if (config.keepNames) options.keepNames = [...options.keepNames, ...config.keepNames];
87
+ } catch (err) {
88
+ console.error(`Error loading config file: ${err.message}`);
89
+ process.exit(1);
90
+ }
91
+ }
92
+
93
+ async function main() {
94
+ // Read input
95
+ let source;
96
+ if (inputFile) {
97
+ try {
98
+ source = fs.readFileSync(inputFile, 'utf8');
99
+ } catch (err) {
100
+ console.error(`Error reading file: ${err.message}`);
101
+ process.exit(1);
102
+ }
103
+ } else {
104
+ // Read from stdin
105
+ source = fs.readFileSync(0, 'utf8');
106
+ }
107
+
108
+ // Initialize WASM and minify
109
+ const { initialize, minify } = require('../lib/main.js');
110
+
111
+ try {
112
+ await initialize();
113
+ const result = minify(source, options);
114
+
115
+ if (result.errors && result.errors.length > 0) {
116
+ for (const err of result.errors) {
117
+ console.error(`Error at line ${err.line}, column ${err.column}: ${err.message}`);
118
+ }
119
+ process.exit(1);
120
+ }
121
+
122
+ // Write output
123
+ if (outputFile) {
124
+ fs.writeFileSync(outputFile, result.code);
125
+ } else {
126
+ process.stdout.write(result.code);
127
+ }
128
+ } catch (err) {
129
+ console.error(`Minification error: ${err.message}`);
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ main();
package/esm/browser.js ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * miniray - WGSL Minifier for WebGPU Shaders (ESM Build)
3
+ *
4
+ * Usage:
5
+ * import { initialize, minify } from 'miniray'
6
+ * await initialize({ wasmURL: '/miniray.wasm' })
7
+ * const result = minify(source, { minifyWhitespace: true })
8
+ */
9
+
10
+ let _initialized = false;
11
+ let _initPromise = null;
12
+ let _go = null;
13
+
14
+ /**
15
+ * Initialize the WASM module.
16
+ * @param {Object} options
17
+ * @param {string|URL} [options.wasmURL] - URL to miniray.wasm
18
+ * @param {WebAssembly.Module} [options.wasmModule] - Pre-compiled module
19
+ * @returns {Promise<void>}
20
+ */
21
+ export async function initialize(options) {
22
+ if (_initialized) {
23
+ return;
24
+ }
25
+ if (_initPromise) {
26
+ return _initPromise;
27
+ }
28
+
29
+ options = options || {};
30
+ const wasmURL = options.wasmURL;
31
+ const wasmModule = options.wasmModule;
32
+
33
+ if (!wasmURL && !wasmModule) {
34
+ throw new Error('Must provide either wasmURL or wasmModule');
35
+ }
36
+
37
+ _initPromise = _doInitialize(wasmURL, wasmModule);
38
+
39
+ try {
40
+ await _initPromise;
41
+ _initialized = true;
42
+ } catch (err) {
43
+ _initPromise = null;
44
+ throw err;
45
+ }
46
+ }
47
+
48
+ async function _doInitialize(wasmURL, wasmModule) {
49
+ // Load wasm_exec.js if Go is not defined
50
+ if (typeof Go === 'undefined') {
51
+ throw new Error(
52
+ 'Go runtime not found. Make sure to include wasm_exec.js before using miniray:\n' +
53
+ '<script src="wasm_exec.js"></script>'
54
+ );
55
+ }
56
+
57
+ _go = new Go();
58
+
59
+ let instance;
60
+ if (wasmModule) {
61
+ // Use pre-compiled module
62
+ instance = await WebAssembly.instantiate(wasmModule, _go.importObject);
63
+ } else {
64
+ // Fetch and instantiate
65
+ const url = wasmURL instanceof URL ? wasmURL.href : wasmURL;
66
+
67
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
68
+ try {
69
+ const response = await fetch(url);
70
+ if (!response.ok) {
71
+ throw new Error(`Failed to fetch ${url}: ${response.status}`);
72
+ }
73
+ const result = await WebAssembly.instantiateStreaming(response, _go.importObject);
74
+ instance = result.instance;
75
+ } catch (err) {
76
+ // Fall back to arrayBuffer if streaming fails (e.g., wrong MIME type)
77
+ if (err.message && err.message.includes('MIME')) {
78
+ const response = await fetch(url);
79
+ const bytes = await response.arrayBuffer();
80
+ const result = await WebAssembly.instantiate(bytes, _go.importObject);
81
+ instance = result.instance;
82
+ } else {
83
+ throw err;
84
+ }
85
+ }
86
+ } else {
87
+ // Fallback for older browsers
88
+ const response = await fetch(url);
89
+ const bytes = await response.arrayBuffer();
90
+ const result = await WebAssembly.instantiate(bytes, _go.importObject);
91
+ instance = result.instance;
92
+ }
93
+ }
94
+
95
+ // Run the Go program (this sets up __miniray global)
96
+ _go.run(instance);
97
+
98
+ // Wait for __miniray to be available
99
+ await _waitForGlobal('__miniray', 1000);
100
+ }
101
+
102
+ function _waitForGlobal(name, timeout) {
103
+ return new Promise((resolve, reject) => {
104
+ const start = Date.now();
105
+ const check = () => {
106
+ if (typeof globalThis[name] !== 'undefined') {
107
+ resolve();
108
+ } else if (Date.now() - start > timeout) {
109
+ reject(new Error(`Timeout waiting for ${name} to be defined`));
110
+ } else {
111
+ setTimeout(check, 10);
112
+ }
113
+ };
114
+ check();
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Minify WGSL source code.
120
+ * @param {string} source - WGSL source code
121
+ * @param {Object} [options] - Minification options
122
+ * @param {boolean} [options.minifyWhitespace=true] - Remove whitespace
123
+ * @param {boolean} [options.minifyIdentifiers=true] - Rename identifiers
124
+ * @param {boolean} [options.minifySyntax=true] - Optimize syntax
125
+ * @param {boolean} [options.mangleExternalBindings=false] - Mangle uniform/storage names
126
+ * @param {string[]} [options.keepNames] - Names to preserve
127
+ * @returns {Object} Result with code, errors, originalSize, minifiedSize
128
+ */
129
+ export function minify(source, options) {
130
+ if (!_initialized) {
131
+ throw new Error('miniray not initialized. Call initialize() first.');
132
+ }
133
+
134
+ if (typeof source !== 'string') {
135
+ throw new Error('source must be a string');
136
+ }
137
+
138
+ return globalThis.__miniray.minify(source, options || {});
139
+ }
140
+
141
+ /**
142
+ * Check if initialized.
143
+ * @returns {boolean}
144
+ */
145
+ export function isInitialized() {
146
+ return _initialized;
147
+ }
148
+
149
+ /**
150
+ * Get version (available after initialization).
151
+ * @type {string}
152
+ */
153
+ export const version = (() => {
154
+ // Getter that returns version after init
155
+ return {
156
+ toString() {
157
+ return _initialized ? globalThis.__miniray.version : 'unknown';
158
+ },
159
+ valueOf() {
160
+ return _initialized ? globalThis.__miniray.version : 'unknown';
161
+ }
162
+ };
163
+ })();
164
+
165
+ // Default export for convenience
166
+ export default { initialize, minify, isInitialized, version };
package/esm/node.mjs ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * miniray - WGSL Minifier for WebGPU Shaders (Node.js ESM Build)
3
+ *
4
+ * Usage:
5
+ * import { initialize, minify } from 'miniray'
6
+ * await initialize()
7
+ * const result = minify(source, { minifyWhitespace: true })
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import { createRequire } from 'module';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+ const require = createRequire(import.meta.url);
18
+
19
+ let _initialized = false;
20
+ let _initPromise = null;
21
+ let _go = null;
22
+
23
+ /**
24
+ * Initialize the WASM module.
25
+ * @param {Object} options
26
+ * @param {string} [options.wasmURL] - Path to miniray.wasm
27
+ * @param {WebAssembly.Module} [options.wasmModule] - Pre-compiled module
28
+ * @returns {Promise<void>}
29
+ */
30
+ export async function initialize(options) {
31
+ if (_initialized) {
32
+ return;
33
+ }
34
+ if (_initPromise) {
35
+ return _initPromise;
36
+ }
37
+
38
+ options = options || {};
39
+ let wasmURL = options.wasmURL;
40
+ const wasmModule = options.wasmModule;
41
+
42
+ // Default to miniray.wasm in the package directory
43
+ if (!wasmURL && !wasmModule) {
44
+ wasmURL = path.join(__dirname, '..', 'miniray.wasm');
45
+ }
46
+
47
+ _initPromise = _doInitialize(wasmURL, wasmModule);
48
+
49
+ try {
50
+ await _initPromise;
51
+ _initialized = true;
52
+ } catch (err) {
53
+ _initPromise = null;
54
+ throw err;
55
+ }
56
+ }
57
+
58
+ async function _doInitialize(wasmURL, wasmModule) {
59
+ // Load wasm_exec.js - this defines Go globally
60
+ const wasmExecPath = path.join(__dirname, '..', 'wasm_exec.js');
61
+ if (!fs.existsSync(wasmExecPath)) {
62
+ throw new Error(`wasm_exec.js not found at ${wasmExecPath}`);
63
+ }
64
+
65
+ // Use require to load wasm_exec.js since it modifies globalThis
66
+ require(wasmExecPath);
67
+
68
+ if (typeof globalThis.Go === 'undefined') {
69
+ throw new Error('Go runtime not found after loading wasm_exec.js');
70
+ }
71
+
72
+ _go = new globalThis.Go();
73
+
74
+ let instance;
75
+ if (wasmModule) {
76
+ instance = await WebAssembly.instantiate(wasmModule, _go.importObject);
77
+ } else {
78
+ const wasmPath = wasmURL instanceof URL ? wasmURL.pathname : wasmURL;
79
+ const wasmBuffer = fs.readFileSync(wasmPath);
80
+ const result = await WebAssembly.instantiate(wasmBuffer, _go.importObject);
81
+ instance = result.instance;
82
+ }
83
+
84
+ // Run the Go program
85
+ _go.run(instance);
86
+
87
+ // Wait for __miniray to be available
88
+ await _waitForGlobal('__miniray', 1000);
89
+ }
90
+
91
+ function _waitForGlobal(name, timeout) {
92
+ return new Promise((resolve, reject) => {
93
+ const start = Date.now();
94
+ const check = () => {
95
+ if (typeof globalThis[name] !== 'undefined') {
96
+ resolve();
97
+ } else if (Date.now() - start > timeout) {
98
+ reject(new Error(`Timeout waiting for ${name} to be defined`));
99
+ } else {
100
+ setTimeout(check, 10);
101
+ }
102
+ };
103
+ check();
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Minify WGSL source code.
109
+ * @param {string} source - WGSL source code
110
+ * @param {Object} [options] - Minification options
111
+ * @returns {Object} Result
112
+ */
113
+ export function minify(source, options) {
114
+ if (!_initialized) {
115
+ throw new Error('miniray not initialized. Call initialize() first.');
116
+ }
117
+
118
+ if (typeof source !== 'string') {
119
+ throw new Error('source must be a string');
120
+ }
121
+
122
+ return globalThis.__miniray.minify(source, options || {});
123
+ }
124
+
125
+ /**
126
+ * Check if initialized.
127
+ * @returns {boolean}
128
+ */
129
+ export function isInitialized() {
130
+ return _initialized;
131
+ }
132
+
133
+ /**
134
+ * Get version.
135
+ * @returns {string}
136
+ */
137
+ export function getVersion() {
138
+ if (!_initialized) {
139
+ return 'unknown';
140
+ }
141
+ return globalThis.__miniray.version;
142
+ }
143
+
144
+ export const version = getVersion;
145
+
146
+ // Default export
147
+ export default {
148
+ initialize,
149
+ minify,
150
+ isInitialized,
151
+ version: getVersion
152
+ };