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 +188 -0
- package/bin/miniray +134 -0
- package/esm/browser.js +166 -0
- package/esm/node.mjs +152 -0
- package/lib/browser.js +177 -0
- package/lib/main.d.ts +103 -0
- package/lib/main.js +152 -0
- package/miniray.wasm +0 -0
- package/package.json +54 -0
- package/wasm_exec.js +575 -0
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
|
+
};
|