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 CHANGED
@@ -1,11 +1,11 @@
1
- # @aspect/wasm-tool-node
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 @aspect/wasm-tool-node
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 '@aspect/wasm-tool-node';
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ttmg-wasm-tool",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Native Node.js module for WASM preparation and splitting",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",