ttmg-wasm-tool 1.0.2 → 1.0.4
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 +3 -52
- package/build/Release/wasm_tool_node.node +0 -0
- package/index.d.ts +297 -0
- package/index.js +346 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ttmg-wasm-tool
|
|
2
2
|
|
|
3
3
|
Native Node.js module for WASM preparation and splitting. Supports macOS and Windows.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install ttmg-wasm-tool
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
This will automatically compile the native module during installation.
|
|
@@ -22,10 +22,8 @@ This will automatically compile the native module during installation.
|
|
|
22
22
|
|
|
23
23
|
### prepareWasm(wasmBuffer: Buffer): PrepareResult
|
|
24
24
|
|
|
25
|
-
Prepare a WASM module by inserting `scwebgl.logCall` instrumentation at the beginning of each function.
|
|
26
|
-
|
|
27
25
|
```typescript
|
|
28
|
-
import { prepareWasm } from '
|
|
26
|
+
import { prepareWasm } from 'ttmg-wasm-tool';
|
|
29
27
|
import { readFileSync, writeFileSync } from 'fs';
|
|
30
28
|
|
|
31
29
|
const wasm = readFileSync('input.wasm');
|
|
@@ -98,50 +96,3 @@ interface SplitResult {
|
|
|
98
96
|
subWasmBuffer?: Buffer;
|
|
99
97
|
}
|
|
100
98
|
```
|
|
101
|
-
|
|
102
|
-
## Split Rules
|
|
103
|
-
|
|
104
|
-
The WASM splitting follows these rules:
|
|
105
|
-
|
|
106
|
-
1. All import functions and element segment functions go to the main package
|
|
107
|
-
2. Functions directly called from main package that don't exist create stub functions with `scwebgl.wait` calls
|
|
108
|
-
3. Sub package contains all functions not in the main package
|
|
109
|
-
4. Sub package functions get `scwebgl.logCall` instrumentation
|
|
110
|
-
5. Non-import global variables are converted to imports for sharing between packages
|
|
111
|
-
|
|
112
|
-
## Building from Source
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
# Install dependencies
|
|
116
|
-
npm install
|
|
117
|
-
|
|
118
|
-
# Build (Release)
|
|
119
|
-
npm run build
|
|
120
|
-
|
|
121
|
-
# Build (Debug)
|
|
122
|
-
npm run build:debug
|
|
123
|
-
|
|
124
|
-
# Clean build artifacts
|
|
125
|
-
npm run clean
|
|
126
|
-
|
|
127
|
-
# Rebuild
|
|
128
|
-
npm run rebuild
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Pre-built Binaries
|
|
132
|
-
|
|
133
|
-
To create pre-built binaries for distribution:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
# Current platform
|
|
137
|
-
npm run prebuild
|
|
138
|
-
|
|
139
|
-
# Specific platforms
|
|
140
|
-
npm run prebuild:win32
|
|
141
|
-
npm run prebuild:darwin
|
|
142
|
-
npm run prebuild:darwin-arm64
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## License
|
|
146
|
-
|
|
147
|
-
MIT
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -40,6 +40,44 @@ export interface SplitResult {
|
|
|
40
40
|
subWasmBuffer?: Buffer;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Result of splitWasmH5 operation for H5/web platform
|
|
45
|
+
*
|
|
46
|
+
* This is the complete H5 WASM splitting result with wasm2js conversion done natively.
|
|
47
|
+
* The returned artifacts can be directly used by the H5 runtime.
|
|
48
|
+
*
|
|
49
|
+
* Files:
|
|
50
|
+
* - mainWasmBuffer → split_main_h5.wasm
|
|
51
|
+
* - subElemJsonBuffer → sub_elem_range.json ({"funcName":[start,end],...})
|
|
52
|
+
* - mergedJsBuffer → subjs.data (all function JS concatenated)
|
|
53
|
+
*/
|
|
54
|
+
export interface SplitH5Result {
|
|
55
|
+
/** Whether the operation succeeded */
|
|
56
|
+
success: boolean;
|
|
57
|
+
/** Error message if operation failed */
|
|
58
|
+
error?: string;
|
|
59
|
+
/** Total number of functions in original WASM */
|
|
60
|
+
totalFuncCount?: number;
|
|
61
|
+
/** Number of functions in main package */
|
|
62
|
+
mainFuncCount?: number;
|
|
63
|
+
/** Number of functions in sub package */
|
|
64
|
+
subFuncCount?: number;
|
|
65
|
+
/** Table size after split */
|
|
66
|
+
tableSize?: number;
|
|
67
|
+
/** Global variable list info */
|
|
68
|
+
globalVarList?: string;
|
|
69
|
+
/** Main package WASM buffer (split_main_h5.wasm) */
|
|
70
|
+
mainWasmBuffer?: Buffer;
|
|
71
|
+
/** Merged JS buffer (subjs.data) - all function JS concatenated, use subElemJson offsets to extract */
|
|
72
|
+
mergedJsBuffer?: Buffer;
|
|
73
|
+
/** Sub element range JSON buffer - {"funcName":[start,end],...} */
|
|
74
|
+
subElemJsonBuffer?: Buffer;
|
|
75
|
+
/** Sub element range JSON string - {"funcName":[start,end],...} */
|
|
76
|
+
subElemJson?: string;
|
|
77
|
+
/** Array of function names in the sub package */
|
|
78
|
+
functionNames?: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
43
81
|
/**
|
|
44
82
|
* Prepare a WASM module by inserting logCall instrumentation
|
|
45
83
|
* This inserts a call to scwebgl.logCall at the beginning of each function
|
|
@@ -97,3 +135,262 @@ export function prepareWasm(wasmBuffer: Buffer): PrepareResult;
|
|
|
97
135
|
* ```
|
|
98
136
|
*/
|
|
99
137
|
export function splitWasm(wasmBuffer: Buffer, funcIdList?: string): SplitResult;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Split a WASM module for H5/web platform
|
|
141
|
+
*
|
|
142
|
+
* This is specifically designed for the H5 WASM splitting workflow where:
|
|
143
|
+
* 1. Main WASM (split_main_h5.wasm) is used directly
|
|
144
|
+
* 2. Sub WASM is converted to JavaScript via wasm2js (merged_js)
|
|
145
|
+
* 3. sub_elem.json maps table indices to function names for the JS runtime
|
|
146
|
+
*
|
|
147
|
+
* The sub package WASM needs to be processed with Binaryen's wasm2js tool
|
|
148
|
+
* to create the merged_js output.
|
|
149
|
+
*
|
|
150
|
+
* @param wasmBuffer - The input WASM binary buffer
|
|
151
|
+
* @param funcIdList - Optional newline-separated list of function IDs to include in main package
|
|
152
|
+
* @returns SplitH5Result containing main WASM, sub WASM, and sub_elem.json
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* import { splitWasmH5 } from '@aspect/wasm-tool-node';
|
|
157
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
158
|
+
* import { execSync } from 'child_process';
|
|
159
|
+
*
|
|
160
|
+
* const wasm = readFileSync('input.wasm');
|
|
161
|
+
* const result = splitWasmH5(wasm);
|
|
162
|
+
*
|
|
163
|
+
* if (result.success) {
|
|
164
|
+
* // Save main WASM
|
|
165
|
+
* writeFileSync('split_main_h5.wasm', result.mainWasmBuffer!);
|
|
166
|
+
*
|
|
167
|
+
* // Save sub_elem.json
|
|
168
|
+
* writeFileSync('split_main_h5.wasm.sub_elem.json', result.subElemJsonBuffer!);
|
|
169
|
+
*
|
|
170
|
+
* // Save sub WASM for wasm2js conversion
|
|
171
|
+
* writeFileSync('sub.wasm', result.subWasmBuffer!);
|
|
172
|
+
*
|
|
173
|
+
* // Convert sub WASM to JS using Binaryen's wasm2js
|
|
174
|
+
* // execSync('wasm2js sub.wasm -o merged_js');
|
|
175
|
+
* } else {
|
|
176
|
+
* console.error(result.error);
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export function splitWasmH5(wasmBuffer: Buffer, funcIdList?: string): SplitH5Result;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Result of wasm2js operation
|
|
184
|
+
*/
|
|
185
|
+
export interface Wasm2JsResult {
|
|
186
|
+
/** Whether the operation succeeded */
|
|
187
|
+
success: boolean;
|
|
188
|
+
/** Error message if operation failed */
|
|
189
|
+
error?: string;
|
|
190
|
+
/** The generated JavaScript code */
|
|
191
|
+
jsCode?: string;
|
|
192
|
+
/** The generated JavaScript code as a Buffer */
|
|
193
|
+
jsBuffer?: Buffer;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Options for wasm2js conversion
|
|
198
|
+
*/
|
|
199
|
+
export interface Wasm2JsOptions {
|
|
200
|
+
/** Path to wasm2js binary (defaults to 'wasm2js' in PATH, only used if native not available) */
|
|
201
|
+
wasm2jsPath?: string;
|
|
202
|
+
/** Whether to optimize the output JS */
|
|
203
|
+
optimize?: boolean;
|
|
204
|
+
/** Enable mutable globals support (external binary only) */
|
|
205
|
+
enableMutableGlobals?: boolean;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Individual function JS output
|
|
210
|
+
*/
|
|
211
|
+
export interface FunctionJs {
|
|
212
|
+
/** Function name */
|
|
213
|
+
name: string;
|
|
214
|
+
/** Generated JavaScript code for this function */
|
|
215
|
+
js: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Result of wasm2jsFunctions operation
|
|
220
|
+
*/
|
|
221
|
+
export interface Wasm2JsFunctionsResult {
|
|
222
|
+
/** Whether the operation succeeded */
|
|
223
|
+
success: boolean;
|
|
224
|
+
/** Error message if operation failed */
|
|
225
|
+
error?: string;
|
|
226
|
+
/** The full module JavaScript code */
|
|
227
|
+
fullJs?: string;
|
|
228
|
+
/** Individual function JavaScript code */
|
|
229
|
+
functions?: FunctionJs[];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if native Binaryen wasm2js support is available
|
|
234
|
+
*
|
|
235
|
+
* When native support is available, wasm2js operations are performed
|
|
236
|
+
* entirely within the native module without needing external binaries.
|
|
237
|
+
*
|
|
238
|
+
* @returns True if native Binaryen support is compiled in
|
|
239
|
+
*/
|
|
240
|
+
export function hasBinaryenSupport(): boolean;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Convert WASM to JavaScript using Binaryen's wasm2js
|
|
244
|
+
*
|
|
245
|
+
* If the module was built with WITH_BINARYEN=ON, this uses the native
|
|
246
|
+
* implementation which is faster and doesn't require external binaries.
|
|
247
|
+
* Otherwise, it falls back to calling the wasm2js binary from PATH.
|
|
248
|
+
*
|
|
249
|
+
* @param wasmBuffer - The input WASM binary buffer
|
|
250
|
+
* @param options - Options for wasm2js
|
|
251
|
+
* @returns Result containing the JavaScript code
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* import { wasm2js, hasBinaryenSupport } from '@aspect/wasm-tool-node';
|
|
256
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
257
|
+
*
|
|
258
|
+
* console.log('Native Binaryen:', hasBinaryenSupport() ? 'Yes' : 'No');
|
|
259
|
+
*
|
|
260
|
+
* const wasm = readFileSync('module.wasm');
|
|
261
|
+
* const result = wasm2js(wasm, { optimize: true });
|
|
262
|
+
*
|
|
263
|
+
* if (result.success) {
|
|
264
|
+
* writeFileSync('module.js', result.jsCode!);
|
|
265
|
+
* }
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export function wasm2js(wasmBuffer: Buffer, options?: Wasm2JsOptions): Wasm2JsResult;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Convert WASM to JavaScript with individual function output
|
|
272
|
+
*
|
|
273
|
+
* This allows extracting each function's JavaScript separately,
|
|
274
|
+
* which is useful for lazy loading or code splitting.
|
|
275
|
+
*
|
|
276
|
+
* Note: This function requires native Binaryen support.
|
|
277
|
+
* Check hasBinaryenSupport() before calling.
|
|
278
|
+
*
|
|
279
|
+
* @param wasmBuffer - The input WASM binary buffer
|
|
280
|
+
* @param options - Options for wasm2js
|
|
281
|
+
* @returns Result containing full JS and individual function JS
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* import { wasm2jsFunctions, hasBinaryenSupport } from '@aspect/wasm-tool-node';
|
|
286
|
+
* import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
287
|
+
*
|
|
288
|
+
* if (!hasBinaryenSupport()) {
|
|
289
|
+
* console.error('This feature requires native Binaryen support');
|
|
290
|
+
* process.exit(1);
|
|
291
|
+
* }
|
|
292
|
+
*
|
|
293
|
+
* const wasm = readFileSync('module.wasm');
|
|
294
|
+
* const result = wasm2jsFunctions(wasm);
|
|
295
|
+
*
|
|
296
|
+
* if (result.success) {
|
|
297
|
+
* mkdirSync('functions', { recursive: true });
|
|
298
|
+
* result.functions!.forEach((f, i) => {
|
|
299
|
+
* const filename = f.name || `func_${i}`;
|
|
300
|
+
* writeFileSync(`functions/${filename}.js`, f.js);
|
|
301
|
+
* });
|
|
302
|
+
* }
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
export function wasm2jsFunctions(wasmBuffer: Buffer, options?: Wasm2JsOptions): Wasm2JsFunctionsResult;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Complete result of splitWasmH5Full operation
|
|
309
|
+
*
|
|
310
|
+
* Final H5 artifacts:
|
|
311
|
+
* - split_main_h5.wasm (mainWasmBuffer)
|
|
312
|
+
* - split_main_h5.wasm.sub_elem.json (subElemJsonBuffer)
|
|
313
|
+
* - sub.data / merged_js (subDataBuffer) - concatenated JS code
|
|
314
|
+
*/
|
|
315
|
+
export interface SplitH5FullResult {
|
|
316
|
+
/** Whether the operation succeeded */
|
|
317
|
+
success: boolean;
|
|
318
|
+
/** Error message if operation failed */
|
|
319
|
+
error?: string;
|
|
320
|
+
/** Total number of functions in original WASM */
|
|
321
|
+
totalFuncCount?: number;
|
|
322
|
+
/** Number of functions in main package */
|
|
323
|
+
mainFuncCount?: number;
|
|
324
|
+
/** Number of functions in sub package */
|
|
325
|
+
subFuncCount?: number;
|
|
326
|
+
/** Table size after split */
|
|
327
|
+
tableSize?: number;
|
|
328
|
+
/** Global variable list info */
|
|
329
|
+
globalVarList?: string;
|
|
330
|
+
|
|
331
|
+
// === Final H5 artifacts ===
|
|
332
|
+
|
|
333
|
+
/** Main package WASM buffer (split_main_h5.wasm) */
|
|
334
|
+
mainWasmBuffer?: Buffer;
|
|
335
|
+
/** Sub element JSON buffer (split_main_h5.wasm.sub_elem.json) */
|
|
336
|
+
subElemJsonBuffer?: Buffer;
|
|
337
|
+
/** Sub element JSON string */
|
|
338
|
+
subElemJson?: string;
|
|
339
|
+
/** Sub package data buffer (sub.data / merged_js) - JS code from wasm2js */
|
|
340
|
+
subDataBuffer?: Buffer;
|
|
341
|
+
/** Sub package JS code string */
|
|
342
|
+
subDataJs?: string;
|
|
343
|
+
|
|
344
|
+
// === Aliases for backward compatibility ===
|
|
345
|
+
|
|
346
|
+
/** @deprecated Use subDataBuffer instead */
|
|
347
|
+
mergedJsBuffer?: Buffer;
|
|
348
|
+
/** @deprecated Use subDataJs instead */
|
|
349
|
+
mergedJs?: string;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Options for splitWasmH5Full
|
|
354
|
+
*/
|
|
355
|
+
export interface SplitH5FullOptions {
|
|
356
|
+
/** Newline-separated list of function IDs for main package */
|
|
357
|
+
funcIdList?: string;
|
|
358
|
+
/** Path to wasm2js binary */
|
|
359
|
+
wasm2jsPath?: string;
|
|
360
|
+
/** Whether to optimize the JS output */
|
|
361
|
+
optimize?: boolean;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Complete H5 WASM split workflow
|
|
366
|
+
*
|
|
367
|
+
* This performs the complete H5 split workflow:
|
|
368
|
+
* 1. Split WASM into main and sub packages
|
|
369
|
+
* 2. Convert sub WASM to JavaScript using wasm2js
|
|
370
|
+
* 3. Return all artifacts as buffers
|
|
371
|
+
*
|
|
372
|
+
* Requires Binaryen's wasm2js tool to be installed
|
|
373
|
+
*
|
|
374
|
+
* @param wasmBuffer - The input WASM binary buffer
|
|
375
|
+
* @param options - Options for the split operation
|
|
376
|
+
* @returns Complete H5 split result with all artifacts
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* import { splitWasmH5Full } from '@aspect/wasm-tool-node';
|
|
381
|
+
* import { readFileSync, writeFileSync } from 'fs';
|
|
382
|
+
*
|
|
383
|
+
* const wasm = readFileSync('input.wasm');
|
|
384
|
+
* const result = splitWasmH5Full(wasm, {
|
|
385
|
+
* wasm2jsPath: '/path/to/wasm2js',
|
|
386
|
+
* optimize: true
|
|
387
|
+
* });
|
|
388
|
+
*
|
|
389
|
+
* if (result.success) {
|
|
390
|
+
* writeFileSync('split_main_h5.wasm', result.mainWasmBuffer!);
|
|
391
|
+
* writeFileSync('split_main_h5.wasm.sub_elem.json', result.subElemJsonBuffer!);
|
|
392
|
+
* writeFileSync('merged_js', result.mergedJsBuffer!);
|
|
393
|
+
* }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
export function splitWasmH5Full(wasmBuffer: Buffer, options?: SplitH5FullOptions): SplitH5FullResult;
|
package/index.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const os = require('os');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { execSync, spawnSync } = require('child_process');
|
|
9
11
|
|
|
10
12
|
// Determine the correct binary path based on platform
|
|
11
13
|
function getBindingPath() {
|
|
@@ -98,7 +100,350 @@ function splitWasm(wasmBuffer, funcIdList) {
|
|
|
98
100
|
return binding.splitWasm(wasmBuffer, funcIdList || '');
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Split a WASM module for H5/web platform (complete workflow)
|
|
105
|
+
*
|
|
106
|
+
* This function performs the complete H5 split workflow in native code:
|
|
107
|
+
* 1. Split WASM into main and sub packages
|
|
108
|
+
* 2. Convert sub WASM to JavaScript using native wasm2js
|
|
109
|
+
* 3. Concatenate all function JS into a single buffer
|
|
110
|
+
* 4. Generate sub_elem_range.json with function name to [start, end] offset mapping
|
|
111
|
+
*
|
|
112
|
+
* Returns:
|
|
113
|
+
* - mainWasmBuffer: split_main_h5.wasm
|
|
114
|
+
* - mergedJsBuffer: subjs.data (all function JS concatenated)
|
|
115
|
+
* - subElemJsonBuffer: sub_elem_range.json ({"funcName":[start,end],...})
|
|
116
|
+
*
|
|
117
|
+
* @param {Buffer} wasmBuffer - The input WASM binary buffer
|
|
118
|
+
* @param {string} [funcIdList] - Optional newline-separated list of function IDs for main package
|
|
119
|
+
* @returns {import('./index').SplitH5Result} Result containing H5-specific split outputs
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```javascript
|
|
123
|
+
* const result = splitWasmH5(wasmBuffer);
|
|
124
|
+
* if (result.success) {
|
|
125
|
+
* // Save files
|
|
126
|
+
* fs.writeFileSync('split_main_h5.wasm', result.mainWasmBuffer);
|
|
127
|
+
* fs.writeFileSync('sub_elem_range.json', result.subElemJsonBuffer);
|
|
128
|
+
* fs.writeFileSync('subjs.data', result.mergedJsBuffer);
|
|
129
|
+
*
|
|
130
|
+
* // Runtime: extract specific function from merged buffer
|
|
131
|
+
* const subElem = JSON.parse(result.subElemJson);
|
|
132
|
+
* const [start, end] = subElem['f8'];
|
|
133
|
+
* const funcJs = result.mergedJsBuffer.slice(start, end + 1).toString('utf8');
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
function splitWasmH5(wasmBuffer, funcIdList) {
|
|
138
|
+
if (!Buffer.isBuffer(wasmBuffer)) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: 'Input must be a Buffer'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (wasmBuffer.length === 0) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
error: 'Empty buffer provided'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (funcIdList !== undefined && typeof funcIdList !== 'string') {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: 'funcIdList must be a string if provided'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return binding.splitWasmH5(wasmBuffer, funcIdList || '');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if native Binaryen wasm2js support is available
|
|
164
|
+
* @returns {boolean} True if native wasm2js is available
|
|
165
|
+
*/
|
|
166
|
+
function hasBinaryenSupport() {
|
|
167
|
+
return binding.hasBinaryenSupport === true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Convert WASM to JavaScript using Binaryen's wasm2js
|
|
172
|
+
*
|
|
173
|
+
* If WITH_BINARYEN is enabled at build time, this uses the native implementation.
|
|
174
|
+
* Otherwise, it falls back to calling the wasm2js binary (must be in PATH).
|
|
175
|
+
*
|
|
176
|
+
* @param {Buffer} wasmBuffer - The input WASM binary buffer
|
|
177
|
+
* @param {Object} [options] - Options for wasm2js
|
|
178
|
+
* @param {string} [options.wasm2jsPath] - Path to wasm2js binary (only used if native not available)
|
|
179
|
+
* @param {boolean} [options.optimize] - Whether to optimize the output JS
|
|
180
|
+
* @param {boolean} [options.enableMutableGlobals] - Enable mutable globals support (external binary only)
|
|
181
|
+
* @returns {{success: boolean, error?: string, jsCode?: string, jsBuffer?: Buffer}} Result containing JS code
|
|
182
|
+
*/
|
|
183
|
+
function wasm2js(wasmBuffer, options = {}) {
|
|
184
|
+
if (!Buffer.isBuffer(wasmBuffer)) {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
error: 'Input must be a Buffer'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (wasmBuffer.length === 0) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: 'Empty buffer provided'
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Try native implementation first
|
|
199
|
+
if (binding.hasBinaryenSupport && binding.wasm2jsNative) {
|
|
200
|
+
const result = binding.wasm2jsNative(wasmBuffer, options.optimize || false);
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Fall back to external binary
|
|
205
|
+
return wasm2jsExternal(wasmBuffer, options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Convert WASM to JavaScript using external wasm2js binary
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
function wasm2jsExternal(wasmBuffer, options = {}) {
|
|
213
|
+
const wasm2jsPath = options.wasm2jsPath || 'wasm2js';
|
|
214
|
+
|
|
215
|
+
// Create a temp file for the WASM input
|
|
216
|
+
const tmpDir = os.tmpdir();
|
|
217
|
+
const tmpWasmFile = path.join(tmpDir, `wasm2js_input_${Date.now()}_${Math.random().toString(36).slice(2)}.wasm`);
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
// Write WASM to temp file
|
|
221
|
+
fs.writeFileSync(tmpWasmFile, wasmBuffer);
|
|
222
|
+
|
|
223
|
+
// Build wasm2js command
|
|
224
|
+
const args = [tmpWasmFile];
|
|
225
|
+
|
|
226
|
+
// Enable mutable globals by default (our split WASM uses them)
|
|
227
|
+
if (options.enableMutableGlobals !== false) {
|
|
228
|
+
args.push('--enable-mutable-globals');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (options.optimize) {
|
|
232
|
+
args.push('-O');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Execute wasm2js
|
|
236
|
+
const result = spawnSync(wasm2jsPath, args, {
|
|
237
|
+
encoding: 'utf8',
|
|
238
|
+
maxBuffer: 100 * 1024 * 1024, // 100MB buffer for large JS output
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (result.error) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: `Failed to execute wasm2js: ${result.error.message}. Make sure Binaryen's wasm2js is installed and in PATH.`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (result.status !== 0) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
error: `wasm2js failed with exit code ${result.status}: ${result.stderr || result.stdout}`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
success: true,
|
|
257
|
+
jsCode: result.stdout,
|
|
258
|
+
jsBuffer: Buffer.from(result.stdout, 'utf8')
|
|
259
|
+
};
|
|
260
|
+
} finally {
|
|
261
|
+
// Clean up temp file
|
|
262
|
+
try {
|
|
263
|
+
fs.unlinkSync(tmpWasmFile);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
// Ignore cleanup errors
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Convert WASM to JavaScript with individual function output
|
|
272
|
+
* Only available when native Binaryen support is enabled
|
|
273
|
+
*
|
|
274
|
+
* @param {Buffer} wasmBuffer - The input WASM binary buffer
|
|
275
|
+
* @param {Object} [options] - Options
|
|
276
|
+
* @param {boolean} [options.optimize] - Whether to optimize the output JS
|
|
277
|
+
* @returns {{success: boolean, error?: string, fullJs?: string, functions?: Array<{name: string, js: string}>}}
|
|
278
|
+
*/
|
|
279
|
+
function wasm2jsFunctions(wasmBuffer, options = {}) {
|
|
280
|
+
if (!Buffer.isBuffer(wasmBuffer)) {
|
|
281
|
+
return {
|
|
282
|
+
success: false,
|
|
283
|
+
error: 'Input must be a Buffer'
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (wasmBuffer.length === 0) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: 'Empty buffer provided'
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!binding.hasBinaryenSupport || !binding.wasm2jsFunctions) {
|
|
295
|
+
return {
|
|
296
|
+
success: false,
|
|
297
|
+
error: 'wasm2jsFunctions requires native Binaryen support. Build with WITH_BINARYEN=ON'
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return binding.wasm2jsFunctions(wasmBuffer, options.optimize || false);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Complete H5 WASM split workflow - splits WASM and converts sub package to JS
|
|
306
|
+
*
|
|
307
|
+
* This is the complete workflow for H5 WASM splitting:
|
|
308
|
+
* 1. Split WASM into main and sub WASM (intermediate)
|
|
309
|
+
* 2. Convert sub WASM to JavaScript using wasm2js (per-function)
|
|
310
|
+
* 3. Concatenate all function JS into a single buffer
|
|
311
|
+
* 4. Generate sub_elem.json with function name -> [start, end] offset mapping
|
|
312
|
+
*
|
|
313
|
+
* Final H5 artifacts:
|
|
314
|
+
* - split_main_h5.wasm (mainWasmBuffer)
|
|
315
|
+
* - split_main_h5.wasm.sub_elem.json (subElemJsonBuffer) - format: {"f10000":[0,175],"f10001":[176,4638],...}
|
|
316
|
+
* - merged_js (mergedJsBuffer) - all function JS concatenated
|
|
317
|
+
*
|
|
318
|
+
* @param {Buffer} wasmBuffer - The input WASM binary buffer
|
|
319
|
+
* @param {Object} [options] - Options
|
|
320
|
+
* @param {string} [options.funcIdList] - Newline-separated list of function IDs for main package
|
|
321
|
+
* @param {string} [options.wasm2jsPath] - Path to wasm2js binary (fallback if no native support)
|
|
322
|
+
* @param {boolean} [options.optimize] - Whether to optimize the JS output
|
|
323
|
+
* @returns {import('./index').SplitH5FullResult} Complete H5 split result
|
|
324
|
+
*/
|
|
325
|
+
function splitWasmH5Full(wasmBuffer, options = {}) {
|
|
326
|
+
// First, perform the split (creates intermediate sub.wasm)
|
|
327
|
+
const h5Result = splitWasmH5(wasmBuffer, options.funcIdList);
|
|
328
|
+
|
|
329
|
+
if (!h5Result.success) {
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: h5Result.error
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check if we have native Binaryen support for per-function extraction
|
|
337
|
+
if (!binding.hasBinaryenSupport || !binding.wasm2jsFunctions) {
|
|
338
|
+
// Fallback: use full wasm2js (won't have per-function offsets)
|
|
339
|
+
const jsResult = wasm2js(h5Result.subWasmBuffer, {
|
|
340
|
+
wasm2jsPath: options.wasm2jsPath,
|
|
341
|
+
optimize: options.optimize
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (!jsResult.success) {
|
|
345
|
+
return {
|
|
346
|
+
success: false,
|
|
347
|
+
error: `WASM split succeeded but wasm2js conversion failed: ${jsResult.error}`,
|
|
348
|
+
mainWasmBuffer: h5Result.mainWasmBuffer,
|
|
349
|
+
totalFuncCount: h5Result.totalFuncCount,
|
|
350
|
+
mainFuncCount: h5Result.mainFuncCount,
|
|
351
|
+
subFuncCount: h5Result.subFuncCount,
|
|
352
|
+
tableSize: h5Result.tableSize,
|
|
353
|
+
globalVarList: h5Result.globalVarList
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Return without per-function sub_elem.json (use original format)
|
|
358
|
+
const subDataBuffer = Buffer.from(jsResult.jsCode, 'utf8');
|
|
359
|
+
return {
|
|
360
|
+
success: true,
|
|
361
|
+
mainWasmBuffer: h5Result.mainWasmBuffer,
|
|
362
|
+
subElemJsonBuffer: h5Result.subElemJsonBuffer,
|
|
363
|
+
subElemJson: h5Result.subElemJson,
|
|
364
|
+
mergedJsBuffer: subDataBuffer,
|
|
365
|
+
mergedJs: jsResult.jsCode,
|
|
366
|
+
totalFuncCount: h5Result.totalFuncCount,
|
|
367
|
+
mainFuncCount: h5Result.mainFuncCount,
|
|
368
|
+
subFuncCount: h5Result.subFuncCount,
|
|
369
|
+
tableSize: h5Result.tableSize,
|
|
370
|
+
globalVarList: h5Result.globalVarList
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Use native per-function wasm2js
|
|
375
|
+
const funcsResult = wasm2jsFunctions(h5Result.subWasmBuffer, { optimize: options.optimize });
|
|
376
|
+
|
|
377
|
+
if (!funcsResult.success) {
|
|
378
|
+
return {
|
|
379
|
+
success: false,
|
|
380
|
+
error: `WASM split succeeded but wasm2js conversion failed: ${funcsResult.error}`,
|
|
381
|
+
mainWasmBuffer: h5Result.mainWasmBuffer,
|
|
382
|
+
totalFuncCount: h5Result.totalFuncCount,
|
|
383
|
+
mainFuncCount: h5Result.mainFuncCount,
|
|
384
|
+
subFuncCount: h5Result.subFuncCount,
|
|
385
|
+
tableSize: h5Result.tableSize,
|
|
386
|
+
globalVarList: h5Result.globalVarList
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Parse the original sub_elem.json to get function names
|
|
391
|
+
const originalSubElem = JSON.parse(h5Result.subElemJson);
|
|
392
|
+
|
|
393
|
+
// Concatenate all function JS and build offset mapping
|
|
394
|
+
// Format: {"f10000": [start, end], "f10001": [start, end], ...}
|
|
395
|
+
const subElemMapping = {};
|
|
396
|
+
const jsChunks = [];
|
|
397
|
+
let currentOffset = 0;
|
|
398
|
+
|
|
399
|
+
funcsResult.functions.forEach((func, idx) => {
|
|
400
|
+
const jsBuffer = Buffer.from(func.js, 'utf8');
|
|
401
|
+
const startOffset = currentOffset;
|
|
402
|
+
const endOffset = currentOffset + jsBuffer.length - 1;
|
|
403
|
+
|
|
404
|
+
// Use original function name from sub_elem if available, otherwise use index-based name
|
|
405
|
+
const funcName = originalSubElem[idx] || `f${idx}`;
|
|
406
|
+
// Remove $ prefix if present for cleaner keys
|
|
407
|
+
const cleanName = funcName.startsWith('$') ? funcName.slice(1) : funcName;
|
|
408
|
+
|
|
409
|
+
subElemMapping[cleanName] = [startOffset, endOffset];
|
|
410
|
+
jsChunks.push(jsBuffer);
|
|
411
|
+
currentOffset = endOffset + 1;
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Concatenate all JS buffers
|
|
415
|
+
const mergedJsBuffer = Buffer.concat(jsChunks);
|
|
416
|
+
|
|
417
|
+
// Create new sub_elem.json with offset mapping
|
|
418
|
+
const subElemJson = JSON.stringify(subElemMapping);
|
|
419
|
+
const subElemJsonBuffer = Buffer.from(subElemJson, 'utf8');
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
success: true,
|
|
423
|
+
// Final H5 artifacts
|
|
424
|
+
mainWasmBuffer: h5Result.mainWasmBuffer, // split_main_h5.wasm
|
|
425
|
+
subElemJsonBuffer: subElemJsonBuffer, // split_main_h5.wasm.sub_elem.json (new format)
|
|
426
|
+
subElemJson: subElemJson,
|
|
427
|
+
mergedJsBuffer: mergedJsBuffer, // merged_js (concatenated function JS)
|
|
428
|
+
mergedJs: mergedJsBuffer.toString('utf8'),
|
|
429
|
+
// Metadata
|
|
430
|
+
totalFuncCount: h5Result.totalFuncCount,
|
|
431
|
+
mainFuncCount: h5Result.mainFuncCount,
|
|
432
|
+
subFuncCount: h5Result.subFuncCount,
|
|
433
|
+
tableSize: h5Result.tableSize,
|
|
434
|
+
globalVarList: h5Result.globalVarList,
|
|
435
|
+
// Additional info
|
|
436
|
+
functionCount: funcsResult.functions.length,
|
|
437
|
+
functionNames: Object.keys(subElemMapping)
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
101
441
|
module.exports = {
|
|
102
442
|
prepareWasm,
|
|
103
|
-
splitWasm
|
|
443
|
+
splitWasm,
|
|
444
|
+
splitWasmH5,
|
|
445
|
+
wasm2js,
|
|
446
|
+
wasm2jsFunctions,
|
|
447
|
+
splitWasmH5Full,
|
|
448
|
+
hasBinaryenSupport
|
|
104
449
|
};
|