wasm-bindgen-lite 0.1.0 → 0.3.0

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/Cargo.toml CHANGED
@@ -21,3 +21,4 @@ codegen-units = 1
21
21
  panic = "abort"
22
22
  strip = true
23
23
 
24
+
package/LICENSE CHANGED
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wasm-bindgen-lite",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "CLI tool to build Rust crates into minimal, SIMD-optimized WASM packages with JS loaders",
6
6
  "repository": {
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "scripts": {
31
31
  "build": "node bin/wasm-bindgen-lite.js build --crate . --out dist --no-update-package-json",
32
- "test": "cargo test && node scripts/test.js",
32
+ "test": "npm run test:unit && cargo test && node scripts/test.js",
33
+ "test:unit": "node --test test/*.test.js",
33
34
  "test:examples": "./scripts/test-examples.sh",
34
35
  "lint": "npm run lint:js && npm run lint:rust",
35
36
  "lint:js": "eslint . && prettier --check .",
package/scripts/build.js CHANGED
@@ -58,7 +58,7 @@ try {
58
58
  run(
59
59
  `wasm-opt -Oz ${join(DIST, 'wasm/mod.simd.wasm')} -o ${join(DIST, 'wasm/mod.simd.wasm')}`
60
60
  )
61
- } catch (e) {
61
+ } catch {
62
62
  console.warn('wasm-opt not found, skipping optimization.')
63
63
  }
64
64
 
@@ -23,3 +23,4 @@ for dir in examples/*; do
23
23
  cd ../..
24
24
  fi
25
25
  done
26
+
package/src/cli/build.js CHANGED
@@ -2,6 +2,15 @@ import { execSync } from 'node:child_process'
2
2
  import { copyFileSync, mkdirSync } from 'node:fs'
3
3
  import { join } from 'node:path'
4
4
 
5
+ function exec(cmd, options = {}) {
6
+ try {
7
+ execSync(cmd, { stdio: 'inherit', ...options })
8
+ } catch {
9
+ console.error(`\nError: Command failed: ${cmd}`)
10
+ process.exit(1)
11
+ }
12
+ }
13
+
5
14
  function runCargoBuild({ crateDir, release, simd }) {
6
15
  const args = ['build', '--target', 'wasm32-unknown-unknown']
7
16
  if (release) args.push('--release')
@@ -13,9 +22,8 @@ function runCargoBuild({ crateDir, release, simd }) {
13
22
  env.RUSTFLAGS = [base, extra].filter(Boolean).join(' ').trim()
14
23
  }
15
24
 
16
- execSync(`cargo ${args.join(' ')}`, {
25
+ exec(`cargo ${args.join(' ')}`, {
17
26
  cwd: crateDir,
18
- stdio: 'inherit',
19
27
  env,
20
28
  })
21
29
  }
@@ -42,7 +50,7 @@ function maybeRunWasmOpt(wasmFile, wasmOpt) {
42
50
  }
43
51
 
44
52
  const args = ['wasm-opt', ...wasmOpt.args, wasmFile, '-o', wasmFile]
45
- execSync(args.join(' '), { stdio: 'inherit' })
53
+ exec(args.join(' '))
46
54
  }
47
55
 
48
56
  export function buildArtifacts({
@@ -58,26 +66,24 @@ export function buildArtifacts({
58
66
  const wasmOutDir = join(outDir, 'wasm')
59
67
  mkdirSync(wasmOutDir, { recursive: true })
60
68
 
61
- let baselinePath = null
62
- let simdPath = null
69
+ const paths = { baselinePath: null, simdPath: null, wasmOutDir }
63
70
 
64
- if (targets.baseline) {
65
- console.log('Building baseline wasm...')
66
- runCargoBuild({ crateDir, release, simd: false })
67
- const built = wasmPath({ crateDir, release, wasmFileStem })
68
- baselinePath = join(wasmOutDir, `${artifactBaseName}.base.wasm`)
69
- copyFileSync(built, baselinePath)
70
- maybeRunWasmOpt(baselinePath, wasmOpt)
71
- }
71
+ const build = (isSimd, suffix) => {
72
+ const label = isSimd ? 'SIMD' : 'baseline'
73
+ console.log(`Building ${label} wasm...`)
74
+
75
+ runCargoBuild({ crateDir, release, simd: isSimd })
72
76
 
73
- if (targets.simd) {
74
- console.log('Building SIMD wasm...')
75
- runCargoBuild({ crateDir, release, simd: true })
76
77
  const built = wasmPath({ crateDir, release, wasmFileStem })
77
- simdPath = join(wasmOutDir, `${artifactBaseName}.simd.wasm`)
78
- copyFileSync(built, simdPath)
79
- maybeRunWasmOpt(simdPath, wasmOpt)
78
+ const dest = join(wasmOutDir, `${artifactBaseName}.${suffix}.wasm`)
79
+
80
+ copyFileSync(built, dest)
81
+ maybeRunWasmOpt(dest, wasmOpt)
82
+ return dest
80
83
  }
81
84
 
82
- return { baselinePath, simdPath, wasmOutDir }
85
+ if (targets.baseline) paths.baselinePath = build(false, 'base')
86
+ if (targets.simd) paths.simdPath = build(true, 'simd')
87
+
88
+ return paths
83
89
  }
package/src/cli/config.js CHANGED
@@ -19,6 +19,7 @@ const DEFAULT_CONFIG = {
19
19
  node: true,
20
20
  browser: true,
21
21
  inline: true,
22
+ types: true,
22
23
  },
23
24
  custom: null, // path to custom JS file to include and re-export
24
25
  },
@@ -63,13 +64,14 @@ function readCrateName(crateDir) {
63
64
  }
64
65
 
65
66
  function normalizeEmit(value) {
66
- if (!value) return { node: true, browser: true, inline: true }
67
+ if (!value) return { node: true, browser: true, inline: true, types: true }
67
68
  if (Array.isArray(value)) {
68
69
  const set = new Set(value)
69
70
  return {
70
71
  node: set.has('node'),
71
72
  browser: set.has('browser'),
72
73
  inline: set.has('inline'),
74
+ types: set.has('types') || !set.has('no-types'), // default to true if not specified
73
75
  }
74
76
  }
75
77
  if (typeof value === 'object') {
@@ -77,9 +79,10 @@ function normalizeEmit(value) {
77
79
  node: value.node !== false,
78
80
  browser: value.browser !== false,
79
81
  inline: value.inline !== false,
82
+ types: value.types !== false,
80
83
  }
81
84
  }
82
- return { node: true, browser: true, inline: true }
85
+ return { node: true, browser: true, inline: true, types: true }
83
86
  }
84
87
 
85
88
  function normalizeWasmOpt(input) {
@@ -104,90 +107,81 @@ export function loadConfigFromCli(cliOpts = {}) {
104
107
  }
105
108
 
106
109
  const crateName = readCrateName(crateDir)
107
- const artifactBaseName =
108
- cliOpts.artifactBaseName ??
109
- fileConfig.artifactBaseName ??
110
- DEFAULT_CONFIG.artifactBaseName
111
-
112
- const outDir = resolve(
113
- crateDir,
114
- cliOpts.out ?? fileConfig.outDir ?? DEFAULT_CONFIG.outDir
115
- )
116
-
117
- const release =
118
- typeof cliOpts.release === 'boolean'
119
- ? cliOpts.release
120
- : (fileConfig.release ?? DEFAULT_CONFIG.release)
121
-
122
- const targets = {
123
- baseline:
124
- cliOpts.baseline ??
125
- fileConfig.targets?.baseline ??
126
- DEFAULT_CONFIG.targets.baseline,
127
- simd:
128
- typeof cliOpts.simd === 'boolean'
129
- ? cliOpts.simd
130
- : (fileConfig.targets?.simd ?? DEFAULT_CONFIG.targets.simd),
131
- }
132
-
133
- const inline =
134
- typeof cliOpts.inline === 'boolean'
135
- ? cliOpts.inline
136
- : (fileConfig.inline ?? DEFAULT_CONFIG.inline)
137
-
138
- const wasmOpt = normalizeWasmOpt(
139
- cliOpts.wasmOptMode
140
- ? { mode: cliOpts.wasmOptMode, args: cliOpts.wasmOptArgs }
141
- : (fileConfig.wasmOpt ?? DEFAULT_CONFIG.wasmOpt)
142
- )
143
-
144
- const jsEmit = normalizeEmit(fileConfig.js?.emit ?? DEFAULT_CONFIG.js.emit)
145
- const jsCustom = fileConfig.js?.custom ?? DEFAULT_CONFIG.js.custom
146
-
147
- const exportsList =
148
- fileConfig.exports &&
149
- Array.isArray(fileConfig.exports) &&
150
- fileConfig.exports.length
151
- ? fileConfig.exports
152
- : DEFAULT_CONFIG.exports
153
-
154
- const autoInit =
155
- fileConfig.autoInit === 'lazy' ||
156
- fileConfig.autoInit === 'eager' ||
157
- fileConfig.autoInit === 'off'
158
- ? fileConfig.autoInit
159
- : DEFAULT_CONFIG.autoInit
160
-
161
- const streamCfg = {
162
- enable: fileConfig.stream?.enable ?? DEFAULT_CONFIG.stream.enable,
163
- export:
164
- fileConfig.stream?.export ??
165
- exportsList[0]?.name ??
166
- DEFAULT_CONFIG.stream.export,
167
- delimiter: fileConfig.stream?.delimiter ?? DEFAULT_CONFIG.stream.delimiter,
168
- }
169
-
170
- const wasmDelivery = {
171
- type: fileConfig.wasmDelivery?.type ?? DEFAULT_CONFIG.wasmDelivery.type,
172
- package: fileConfig.wasmDelivery?.package ?? fileConfig.name ?? crateName,
173
- version: fileConfig.wasmDelivery?.version ?? fileConfig.version ?? 'latest',
174
- }
175
110
 
111
+ // Merge defaults, file config, and CLI options
176
112
  const config = {
177
113
  crateDir,
178
114
  crateName,
179
115
  wasmFileStem: crateName.replace(/-/g, '_'),
180
- artifactBaseName,
181
- outDir,
182
- release,
183
- inline,
184
- targets,
185
- wasmOpt,
186
- js: { emit: jsEmit, custom: jsCustom },
187
- exports: exportsList,
188
- autoInit,
189
- stream: streamCfg,
190
- wasmDelivery,
116
+
117
+ artifactBaseName:
118
+ cliOpts.artifactBaseName ??
119
+ fileConfig.artifactBaseName ??
120
+ DEFAULT_CONFIG.artifactBaseName,
121
+
122
+ outDir: resolve(
123
+ crateDir,
124
+ cliOpts.out ?? fileConfig.outDir ?? DEFAULT_CONFIG.outDir
125
+ ),
126
+
127
+ release:
128
+ typeof cliOpts.release === 'boolean'
129
+ ? cliOpts.release
130
+ : (fileConfig.release ?? DEFAULT_CONFIG.release),
131
+
132
+ targets: {
133
+ baseline:
134
+ cliOpts.baseline ??
135
+ fileConfig.targets?.baseline ??
136
+ DEFAULT_CONFIG.targets.baseline,
137
+ simd:
138
+ typeof cliOpts.simd === 'boolean'
139
+ ? cliOpts.simd
140
+ : (fileConfig.targets?.simd ?? DEFAULT_CONFIG.targets.simd),
141
+ },
142
+
143
+ inline:
144
+ typeof cliOpts.inline === 'boolean'
145
+ ? cliOpts.inline
146
+ : (fileConfig.inline ?? DEFAULT_CONFIG.inline),
147
+
148
+ wasmOpt: normalizeWasmOpt(
149
+ cliOpts.wasmOptMode
150
+ ? { mode: cliOpts.wasmOptMode, args: cliOpts.wasmOptArgs }
151
+ : (fileConfig.wasmOpt ?? DEFAULT_CONFIG.wasmOpt)
152
+ ),
153
+
154
+ js: {
155
+ emit: normalizeEmit(fileConfig.js?.emit ?? DEFAULT_CONFIG.js.emit),
156
+ custom: fileConfig.js?.custom ?? DEFAULT_CONFIG.js.custom,
157
+ },
158
+
159
+ exports:
160
+ fileConfig.exports && Array.isArray(fileConfig.exports)
161
+ ? fileConfig.exports
162
+ : DEFAULT_CONFIG.exports,
163
+
164
+ autoInit: ['lazy', 'eager', 'off'].includes(fileConfig.autoInit)
165
+ ? fileConfig.autoInit
166
+ : DEFAULT_CONFIG.autoInit,
167
+
168
+ stream: {
169
+ enable: fileConfig.stream?.enable ?? DEFAULT_CONFIG.stream.enable,
170
+ export:
171
+ fileConfig.stream?.export ??
172
+ fileConfig.exports?.[0]?.name ??
173
+ DEFAULT_CONFIG.stream.export,
174
+ delimiter:
175
+ fileConfig.stream?.delimiter ?? DEFAULT_CONFIG.stream.delimiter,
176
+ blockSize: fileConfig.stream?.blockSize ?? null,
177
+ },
178
+
179
+ wasmDelivery: {
180
+ type: fileConfig.wasmDelivery?.type ?? DEFAULT_CONFIG.wasmDelivery.type,
181
+ package: fileConfig.wasmDelivery?.package ?? fileConfig.name ?? crateName,
182
+ version:
183
+ fileConfig.wasmDelivery?.version ?? fileConfig.version ?? 'latest',
184
+ },
191
185
  }
192
186
 
193
187
  return config
package/src/cli/emit.js CHANGED
@@ -1,272 +1,469 @@
1
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
 
5
5
  const UTIL_PATH = fileURLToPath(new URL('../js/util.js', import.meta.url))
6
6
 
7
- function createCore({ exportsList, autoInit, stream }) {
8
- const needsEnsure = autoInit === 'lazy'
9
- const toBytesHelper = `function toBytes(input) {
10
- if (input instanceof Uint8Array) return input;
11
- if (ArrayBuffer.isView(input)) {
12
- return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
13
- }
14
- if (input instanceof ArrayBuffer) return new Uint8Array(input);
15
- throw new TypeError("Expected a TypedArray or ArrayBuffer");
16
- }
17
- `
18
- const scalarSizeHelper = `function scalarSize(type) {
19
- switch (type) {
20
- case "f64": return 8;
21
- case "f32":
22
- case "i32":
23
- case "u32": return 4;
24
- case "i16":
25
- case "u16": return 2;
26
- case "i8":
27
- case "u8": return 1;
28
- case "u32_array":
29
- case "i32_array":
30
- case "f32_array": return 1024 * 1024; // Default large buffer for arrays, or we can improve this
31
- default: return 0;
32
- }
33
- }
34
- `
35
- const decodeHelper = `function decodeReturn(view, type) {
36
- switch (type) {
37
- case "f32": return view.getFloat32(0, true);
38
- case "f64": return view.getFloat64(0, true);
39
- case "i32": return view.getInt32(0, true);
40
- case "u32": return view.getUint32(0, true);
41
- case "i16": return view.getInt16(0, true);
42
- case "u16": return view.getUint16(0, true);
43
- case "i8": return view.getInt8(0);
44
- case "u8": return view.getUint8(0);
45
- case "u32_array": return new Uint32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
46
- case "i32_array": return new Int32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
47
- case "f32_array": return new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
48
- default: return null;
49
- }
7
+ export function buildWrapperIR(exportsList) {
8
+ return exportsList.map((entry) => {
9
+ const { abi, name, return: retType, reuseBuffer, outSize } = entry
10
+ const returnType = retType || 'bytes'
11
+ const fnName = name || abi
12
+ const outSizeExpr =
13
+ returnType !== 'bytes'
14
+ ? `(scalarSize('${returnType}') || 4)`
15
+ : outSize
16
+ ? outSize.replace(/\blen\b/g, 'len')
17
+ : 'Math.max(len, 4)'
18
+
19
+ return {
20
+ abi,
21
+ fnName,
22
+ returnType,
23
+ reuseBuffer: !!reuseBuffer,
24
+ outSizeExpr,
25
+ }
26
+ })
50
27
  }
51
- `
52
-
53
- const wrappers = exportsList
54
- .map(({ abi, name, return: retType, reuseBuffer }) => {
55
- const returnType = retType || 'bytes'
56
- const fnName = name || abi
57
- const outSizeExpr =
58
- returnType === 'bytes'
59
- ? 'Math.max(len, 4)'
60
- : `(scalarSize('${returnType}') || 4)`
61
-
62
- const stateVars = reuseBuffer
63
- ? `let _${fnName}_in = { ptr: 0, len: 0 };
64
- let _${fnName}_out = { ptr: 0, len: 0 };`
65
- : ''
66
-
67
- const allocIn = reuseBuffer
68
- ? `if (_${fnName}_in.len < len) {
69
- if (_${fnName}_in.ptr) free(_${fnName}_in.ptr, _${fnName}_in.len);
70
- _${fnName}_in.ptr = alloc(len);
71
- _${fnName}_in.len = len;
72
- }
73
- const inPtr = _${fnName}_in.ptr;`
74
- : `const inPtr = alloc(len);`
75
-
76
- const allocOut = reuseBuffer
77
- ? `if (_${fnName}_out.len < outLen) {
78
- if (_${fnName}_out.ptr) free(_${fnName}_out.ptr, _${fnName}_out.len);
79
- _${fnName}_out.ptr = alloc(outLen);
80
- _${fnName}_out.len = outLen;
81
- }
82
- const outPtr = _${fnName}_out.ptr;`
83
- : `const outPtr = alloc(outLen);`
84
-
85
- const freeIn = reuseBuffer ? '' : `free(inPtr, len);`
86
- const freeOut = reuseBuffer ? '' : `free(outPtr, outLen);`
87
-
88
- const body = `
89
- if (!_inst) throw new Error("WASM instance not initialized");
90
- const view = toBytes(input);
91
- const len = view.byteLength;
92
- ${allocIn}
93
- memoryU8().set(view, inPtr);
94
- const outLen = ${outSizeExpr};
95
- ${allocOut}
96
- const written = _inst.exports.${abi}(
97
- inPtr, len,
98
- outPtr, outLen
99
- );
100
- if (written < 0) {
101
- ${reuseBuffer ? '' : `free(inPtr, len); free(outPtr, outLen);`}
102
- throw new Error("${abi} failed: " + written);
103
- }
104
- ${
105
- returnType === 'bytes'
106
- ? `const result = memoryU8().slice(outPtr, outPtr + written);`
107
- : `const retView = new DataView(memoryU8().buffer, outPtr, written);
108
- const ret = decodeReturn(retView, "${returnType}");`
109
- }
110
- ${freeIn}
111
- ${freeOut}
112
- ${returnType === 'bytes' ? 'return result;' : 'return ret;'}`
113
-
114
- const wrapper = needsEnsure
115
- ? `async function ${fnName}(input) { await ensureReady(); ${body} }`
116
- : `function ${fnName}(input) { ${body} }`
117
28
 
118
- return `${stateVars}\n${wrapper}\nexport { ${fnName} };`
29
+ export function createCore({ exportsList, autoInit, stream }) {
30
+ const needsEnsure = autoInit === 'lazy'
31
+ const wrappersIR = buildWrapperIR(exportsList)
32
+ const b = code()
33
+
34
+ b.line('let _inst = null;')
35
+ b.line('let _memU8 = null;')
36
+ b.line('let _initFn = null;')
37
+ b.blank()
38
+
39
+ b.line('function refreshViews() {')
40
+ b.indent(() => {
41
+ b.line('_memU8 = new Uint8Array(_inst.exports.memory.buffer);')
42
+ })
43
+ b.line('}')
44
+ b.blank()
45
+
46
+ b.line('export function setInstance(instance) {')
47
+ b.indent(() => {
48
+ b.line('_inst = instance;')
49
+ b.line('refreshViews();')
50
+ })
51
+ b.line('}')
52
+ b.blank()
53
+
54
+ b.line('export function wasmExports() {')
55
+ b.indent(() => {
56
+ b.line('return _inst.exports;')
57
+ })
58
+ b.line('}')
59
+ b.blank()
60
+
61
+ if (needsEnsure) {
62
+ b.line('let _ready = null;')
63
+ b.line('export function registerInit(fn) { _initFn = fn; }')
64
+ b.blank()
65
+ b.line('async function ensureReady() {')
66
+ b.indent(() => {
67
+ b.line('if (_ready) return _ready;')
68
+ b.line('if (!_initFn) throw new Error("init not registered");')
69
+ b.line('_ready = _initFn();')
70
+ b.line('return _ready;')
119
71
  })
120
- .join('\n\n')
121
-
122
- const streamHelper = stream?.enable
123
- ? `
124
- const __exports = { ${exportsList.map(({ name, abi }) => `${name || abi}: ${name || abi}`).join(', ')} };
125
-
126
- export function createTransformStream(fnName = "${stream.export}") {
127
- const fn = __exports[fnName];
128
- if (!fn) throw new Error("Unknown export for streaming: " + fnName);
129
-
130
- ${
131
- stream.delimiter !== null
132
- ? `let buffer = new Uint8Array(0);
133
- const delimiter = ${stream.delimiter};`
134
- : ''
72
+ b.line('}')
73
+ } else {
74
+ b.line('export function registerInit(fn) { _initFn = fn; }')
135
75
  }
76
+ b.blank()
136
77
 
137
- return new TransformStream({
138
- async transform(chunk, controller) {
139
- const bytes = toBytes(chunk);
140
- const processed = ${needsEnsure ? 'await fn(bytes)' : 'fn(bytes)'};
141
-
142
- ${
143
- stream.delimiter !== null
144
- ? `// Split and buffer
145
- const combined = new Uint8Array(buffer.length + processed.length);
146
- combined.set(buffer, 0);
147
- combined.set(processed, buffer.length);
148
-
149
- let start = 0;
150
- for (let i = 0; i < combined.length; i += 1) {
151
- if (combined[i] === delimiter) {
152
- controller.enqueue(combined.subarray(start, i));
153
- start = i + 1;
154
- }
78
+ b.line('export function memoryU8() {')
79
+ b.indent(() => {
80
+ b.line(
81
+ 'if (_memU8 && _memU8.buffer !== _inst.exports.memory.buffer) refreshViews();'
82
+ )
83
+ b.line('return _memU8;')
84
+ })
85
+ b.line('}')
86
+ b.blank()
87
+
88
+ b.line('export function alloc(len) {')
89
+ b.indent(() => {
90
+ b.line('return _inst.exports.alloc_bytes(len) >>> 0;')
91
+ })
92
+ b.line('}')
93
+ b.blank()
94
+
95
+ b.line('export function free(ptr, len) {')
96
+ b.indent(() => {
97
+ b.line('_inst.exports.free_bytes(ptr >>> 0, len >>> 0);')
98
+ })
99
+ b.line('}')
100
+ b.blank()
101
+
102
+ // Runtime Helpers
103
+ b.line('function toBytes(input) {')
104
+ b.indent(() => {
105
+ b.line('if (input instanceof Uint8Array) return input;')
106
+ b.line(
107
+ 'if (ArrayBuffer.isView(input)) return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);'
108
+ )
109
+ b.line('if (input instanceof ArrayBuffer) return new Uint8Array(input);')
110
+ b.line('throw new TypeError("Expected a TypedArray or ArrayBuffer");')
111
+ })
112
+ b.line('}')
113
+ b.blank()
114
+
115
+ b.line('function scalarSize(type) {')
116
+ b.indent(() => {
117
+ b.line('switch (type) {')
118
+ b.line(' case "f64": return 8;')
119
+ b.line(' case "f32":')
120
+ b.line(' case "i32":')
121
+ b.line(' case "u32": return 4;')
122
+ b.line(' case "i16":')
123
+ b.line(' case "u16": return 2;')
124
+ b.line(' case "i8":')
125
+ b.line(' case "u8": return 1;')
126
+ b.line(' case "u32_array":')
127
+ b.line(' case "i32_array":')
128
+ b.line(' case "f32_array": return 1024 * 1024;')
129
+ b.line(' default: return 0;')
130
+ b.line('}')
131
+ })
132
+ b.line('}')
133
+ b.blank()
134
+
135
+ b.line('function decodeReturn(view, type) {')
136
+ b.indent(() => {
137
+ b.line('switch (type) {')
138
+ b.line(' case "f32": return view.getFloat32(0, true);')
139
+ b.line(' case "f64": return view.getFloat64(0, true);')
140
+ b.line(' case "i32": return view.getInt32(0, true);')
141
+ b.line(' case "u32": return view.getUint32(0, true);')
142
+ b.line(' case "i16": return view.getInt16(0, true);')
143
+ b.line(' case "u16": return view.getUint16(0, true);')
144
+ b.line(' case "i8": return view.getInt8(0);')
145
+ b.line(' case "u8": return view.getUint8(0);')
146
+ b.line(
147
+ ' case "u32_array": return new Uint32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));'
148
+ )
149
+ b.line(
150
+ ' case "i32_array": return new Int32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));'
151
+ )
152
+ b.line(
153
+ ' case "f32_array": return new Float32Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));'
154
+ )
155
+ b.line(' default: return null;')
156
+ b.line('}')
157
+ })
158
+ b.line('}')
159
+ b.blank()
160
+
161
+ b.line('function callWasm(abi, input, outLen, reuse) {')
162
+ b.indent(() => {
163
+ b.line('if (!_inst) throw new Error("WASM instance not initialized");')
164
+ b.line('const view = toBytes(input);')
165
+ b.line('const len = view.byteLength;')
166
+ b.blank()
167
+ b.line('let inPtr, outPtr;')
168
+ b.line('if (reuse) {')
169
+ b.indent(() => {
170
+ b.line('if (reuse.in.len < len) {')
171
+ b.indent(() => {
172
+ b.line('if (reuse.in.ptr) free(reuse.in.ptr, reuse.in.len);')
173
+ b.line('reuse.in.ptr = alloc(len);')
174
+ b.line('reuse.in.len = len;')
175
+ })
176
+ b.line('}')
177
+ b.line('if (reuse.out.len < outLen) {')
178
+ b.indent(() => {
179
+ b.line('if (reuse.out.ptr) free(reuse.out.ptr, reuse.out.len);')
180
+ b.line('reuse.out.ptr = alloc(outLen);')
181
+ b.line('reuse.out.len = outLen;')
182
+ })
183
+ b.line('}')
184
+ b.line('inPtr = reuse.in.ptr;')
185
+ b.line('outPtr = reuse.out.ptr;')
186
+ })
187
+ b.line('} else {')
188
+ b.indent(() => {
189
+ b.line('inPtr = alloc(len);')
190
+ b.line('outPtr = alloc(outLen);')
191
+ })
192
+ b.line('}')
193
+ b.blank()
194
+ b.line('memoryU8().set(view, inPtr);')
195
+ b.line('const written = _inst.exports[abi](inPtr, len, outPtr, outLen);')
196
+ b.line('if (written < 0) {')
197
+ b.indent(() => {
198
+ b.line('if (!reuse) { free(inPtr, len); free(outPtr, outLen); }')
199
+ b.line('throw new Error(abi + " failed: " + written);')
200
+ })
201
+ b.line('}')
202
+ b.blank()
203
+ b.line('return { inPtr, outPtr, len, outLen, written };')
204
+ })
205
+ b.line('}')
206
+ b.blank()
207
+
208
+ // Wrappers
209
+ wrappersIR.forEach((w) => {
210
+ if (w.reuseBuffer) {
211
+ b.line(
212
+ `const _${w.fnName}_reuse = { in: { ptr: 0, len: 0 }, out: { ptr: 0, len: 0 } };`
213
+ )
214
+ }
215
+ const asyncPrefix = needsEnsure ? 'async ' : ''
216
+ b.line(`${asyncPrefix}function ${w.fnName}(input) {`)
217
+ b.indent(() => {
218
+ if (needsEnsure) b.line('await ensureReady();')
219
+ b.line('const view = toBytes(input);')
220
+ b.line('const len = view.byteLength;')
221
+ b.line(`const outLen = ${w.outSizeExpr};`)
222
+ b.line(
223
+ `const { outPtr, written, inPtr } = callWasm("${w.abi}", view, outLen, ${w.reuseBuffer ? `_${w.fnName}_reuse` : 'null'});`
224
+ )
225
+ b.blank()
226
+ if (w.returnType === 'bytes') {
227
+ b.line('const result = memoryU8().slice(outPtr, outPtr + written);')
228
+ } else {
229
+ b.line(
230
+ 'const retView = new DataView(memoryU8().buffer, outPtr, written);'
231
+ )
232
+ b.line(`const result = decodeReturn(retView, "${w.returnType}");`)
155
233
  }
156
- buffer = combined.slice(start);`
157
- : 'controller.enqueue(processed);'
234
+ b.blank()
235
+ if (!w.reuseBuffer) {
236
+ b.line('free(inPtr, len);')
237
+ b.line('free(outPtr, outLen);')
158
238
  }
159
- }${
160
- stream.delimiter !== null
161
- ? `,
162
- flush(controller) {
163
- if (buffer.length) controller.enqueue(buffer);
164
- }`
165
- : ''
166
- }
167
- });
168
- }
169
- `
170
- : ''
171
-
172
- const ensure = needsEnsure
173
- ? `
174
- let _ready = null;
175
- export function registerInit(fn) {
176
- _initFn = fn;
177
- }
178
-
179
- async function ensureReady() {
180
- if (_ready) return _ready;
181
- if (!_initFn) throw new Error("init not registered");
182
- _ready = _initFn();
183
- return _ready;
184
- }
185
- `
186
- : `
187
- export function registerInit(fn) {
188
- _initFn = fn;
189
- }
190
- `
239
+ b.line('return result;')
240
+ })
241
+ b.line('}')
242
+ b.line(`export { ${w.fnName} };`)
243
+ b.blank()
244
+ })
245
+
246
+ // Streaming
247
+ if (stream?.enable) {
248
+ b.line('const __exports = {')
249
+ b.indent(() => {
250
+ exportsList.forEach((e) => {
251
+ b.line(
252
+ `${e.name || e.abi}: { fn: ${e.name || e.abi}, blockSize: ${e.blockSize || 'null'} },`
253
+ )
254
+ })
255
+ })
256
+ b.line('};')
257
+ b.blank()
191
258
 
192
- return `let _inst = null;
193
- let _memU8 = null;
194
- let _initFn = null;
259
+ b.line(
260
+ 'export function createChunkTransform(processFn, { blockSize = null, delimiter = null } = {}) {'
261
+ )
262
+ b.indent(() => {
263
+ b.line('let buffer = new Uint8Array(0);')
264
+ b.line('return new TransformStream({')
265
+ b.indent(() => {
266
+ b.line('async transform(chunk, controller) {')
267
+ b.indent(() => {
268
+ b.line('const bytes = toBytes(chunk);')
269
+ b.line('let input = bytes;')
270
+ b.blank()
271
+ b.line(
272
+ 'if (buffer.length > 0 || blockSize !== null || delimiter !== null) {'
273
+ )
274
+ b.indent(() => {
275
+ b.line(
276
+ 'const combined = new Uint8Array(buffer.length + bytes.length);'
277
+ )
278
+ b.line('combined.set(buffer, 0);')
279
+ b.line('combined.set(bytes, buffer.length);')
280
+ b.line('input = combined;')
281
+ })
282
+ b.line('}')
283
+ b.blank()
284
+ b.line('if (delimiter !== null) {')
285
+ b.indent(() => {
286
+ b.line('let start = 0;')
287
+ b.line('for (let i = 0; i < input.length; i++) {')
288
+ b.indent(() => {
289
+ b.line('if (input[i] === delimiter) {')
290
+ b.indent(() => {
291
+ b.line(
292
+ 'controller.enqueue(await processFn(input.subarray(start, i)));'
293
+ )
294
+ b.line('start = i + 1;')
295
+ })
296
+ b.line('}')
297
+ })
298
+ b.line('}')
299
+ b.line('buffer = input.slice(start);')
300
+ })
301
+ b.line('} else if (blockSize !== null) {')
302
+ b.indent(() => {
303
+ b.line(
304
+ 'const processLen = input.length - (input.length % blockSize);'
305
+ )
306
+ b.line('if (processLen > 0) {')
307
+ b.indent(() => {
308
+ b.line(
309
+ 'controller.enqueue(await processFn(input.subarray(0, processLen)));'
310
+ )
311
+ b.line('buffer = input.slice(processLen);')
312
+ })
313
+ b.line('} else {')
314
+ b.indent(() => {
315
+ b.line('buffer = input;')
316
+ })
317
+ b.line('}')
318
+ })
319
+ b.line('} else {')
320
+ b.indent(() => {
321
+ b.line('controller.enqueue(await processFn(input));')
322
+ b.line('buffer = new Uint8Array(0);')
323
+ })
324
+ b.line('}')
325
+ })
326
+ b.line('},')
327
+ b.line('async flush(controller) {')
328
+ b.indent(() => {
329
+ b.line('if (buffer.length > 0) {')
330
+ b.indent(() => {
331
+ b.line('controller.enqueue(await processFn(buffer));')
332
+ b.line('buffer = new Uint8Array(0);')
333
+ })
334
+ b.line('}')
335
+ })
336
+ b.line('}')
337
+ })
338
+ b.line('});')
339
+ })
340
+ b.line('}')
341
+ b.blank()
195
342
 
196
- function refreshViews() {
197
- _memU8 = new Uint8Array(_inst.exports.memory.buffer);
198
- }
343
+ b.line(
344
+ `export function createTransformStream(fnName = "${stream.export}") {`
345
+ )
346
+ b.indent(() => {
347
+ b.line('const entry = __exports[fnName];')
348
+ b.line(
349
+ 'if (!entry) throw new Error("Unknown export for streaming: " + fnName);'
350
+ )
351
+ b.line('const { fn, blockSize: entryBlockSize } = entry;')
352
+ b.line(
353
+ `const blockSize = entryBlockSize ?? ${stream.blockSize !== null ? stream.blockSize : 'null'};`
354
+ )
355
+ b.line(
356
+ `const delimiter = ${stream.delimiter !== null ? stream.delimiter : 'null'};`
357
+ )
358
+ b.line('return createChunkTransform(fn, { blockSize, delimiter });')
359
+ })
360
+ b.line('}')
361
+ }
199
362
 
200
- export function setInstance(instance) {
201
- _inst = instance;
202
- refreshViews();
363
+ return b.toString()
203
364
  }
204
365
 
205
- export function wasmExports() {
206
- return _inst.exports;
207
- }
366
+ export function createCoreTypes({ exportsList, autoInit, stream }) {
367
+ const needsEnsure = autoInit === 'lazy'
368
+ const wrappersIR = buildWrapperIR(exportsList)
369
+ const b = code()
370
+
371
+ b.line('export type WasmInput = Uint8Array | ArrayBufferView | ArrayBuffer;')
372
+ b.blank()
373
+
374
+ b.line('export function setInstance(instance: WebAssembly.Instance): void;')
375
+ b.line('export function wasmExports(): WebAssembly.Exports;')
376
+ b.line('export function memoryU8(): Uint8Array;')
377
+ b.line('export function alloc(len: number): number;')
378
+ b.line('export function free(ptr: number, len: number): void;')
379
+ b.blank()
380
+
381
+ wrappersIR.forEach((w) => {
382
+ let tsRetType
383
+ switch (w.returnType) {
384
+ case 'f32':
385
+ case 'f64':
386
+ case 'i32':
387
+ case 'u32':
388
+ case 'i16':
389
+ case 'u16':
390
+ case 'i8':
391
+ case 'u8':
392
+ tsRetType = 'number'
393
+ break
394
+ case 'u32_array':
395
+ tsRetType = 'Uint32Array'
396
+ break
397
+ case 'i32_array':
398
+ tsRetType = 'Int32Array'
399
+ break
400
+ case 'f32_array':
401
+ tsRetType = 'Float32Array'
402
+ break
403
+ case 'bytes':
404
+ default:
405
+ tsRetType = 'Uint8Array'
406
+ }
208
407
 
209
- ${ensure}
408
+ const ret = needsEnsure ? `Promise<${tsRetType}>` : tsRetType
409
+ b.line(`export function ${w.fnName}(input: WasmInput): ${ret};`)
410
+ })
210
411
 
211
- export function memoryU8() {
212
- if (_memU8 && _memU8.buffer !== _inst.exports.memory.buffer) refreshViews();
213
- return _memU8;
214
- }
412
+ if (stream?.enable) {
413
+ b.blank()
414
+ b.line(
415
+ 'export function createTransformStream(fnName?: string): TransformStream<WasmInput, Uint8Array>;'
416
+ )
417
+ }
215
418
 
216
- export function alloc(len) {
217
- return _inst.exports.alloc_bytes(len) >>> 0;
419
+ return b.toString()
218
420
  }
219
421
 
220
- export function free(ptr, len) {
221
- _inst.exports.free_bytes(ptr >>> 0, len >>> 0);
422
+ export function code() {
423
+ const lines = []
424
+ let indent = 0
425
+ const api = {
426
+ line(s = '') {
427
+ lines.push(' '.repeat(indent) + s)
428
+ return api
429
+ },
430
+ blank() {
431
+ lines.push('')
432
+ return api
433
+ },
434
+ indent(fn) {
435
+ indent++
436
+ fn()
437
+ indent--
438
+ return api
439
+ },
440
+ toString() {
441
+ return lines.join('\n')
442
+ },
443
+ }
444
+ return api
222
445
  }
223
446
 
224
- ${wrappers}
225
- ${scalarSizeHelper}
226
- ${decodeHelper}
227
- ${toBytesHelper}
228
- ${streamHelper}
447
+ export function createLoaderTypes({ exportFrom }) {
448
+ return `export function init(imports?: WebAssembly.Imports): Promise<void>;
449
+ export * from "${exportFrom}";
229
450
  `
230
451
  }
231
452
 
232
- function createBrowserLoader({ name, autoInit, customJs, wasmDelivery }) {
453
+ export function createLoader({ exportFrom, autoInit, getBytesSrc }) {
233
454
  const eager =
234
455
  autoInit === 'eager'
235
456
  ? '\nregisterInit(init);\ninit();'
236
457
  : '\nregisterInit(init);'
237
- const exportFrom = customJs ? './custom.js' : './core.js'
238
-
239
- let simdUrl, baseUrl
240
- if (wasmDelivery.type === 'jsdelivr') {
241
- const pkg = wasmDelivery.package
242
- const ver = wasmDelivery.version
243
- simdUrl = `https://cdn.jsdelivr.net/npm/${pkg}@${ver}/dist/wasm/${name}.simd.wasm`
244
- baseUrl = `https://cdn.jsdelivr.net/npm/${pkg}@${ver}/dist/wasm/${name}.base.wasm`
245
- } else {
246
- simdUrl = `new URL("./wasm/${name}.simd.wasm", import.meta.url)`
247
- baseUrl = `new URL("./wasm/${name}.base.wasm", import.meta.url)`
248
- }
249
458
 
250
459
  return `import { setInstance, registerInit } from "./core.js";
251
460
  import { instantiateWithFallback } from "./util.js";
252
-
253
- const simdUrl = ${wasmDelivery.type === 'jsdelivr' ? `"${simdUrl}"` : simdUrl};
254
- const baseUrl = ${wasmDelivery.type === 'jsdelivr' ? `"${baseUrl}"` : baseUrl};
461
+ ${getBytesSrc}
255
462
 
256
463
  let _ready = null;
257
-
258
464
  export function init(imports = {}) {
259
465
  return (_ready ??= (async () => {
260
- const [simdRes, baseRes] = await Promise.all([
261
- fetch(simdUrl),
262
- fetch(baseUrl)
263
- ]);
264
-
265
- const [simdBytes, baseBytes] = await Promise.all([
266
- simdRes.arrayBuffer(),
267
- baseRes.arrayBuffer()
268
- ]);
269
-
466
+ const { simdBytes, baseBytes } = await getWasmBytes();
270
467
  const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
271
468
  setInstance(instance);
272
469
  })());
@@ -276,59 +473,61 @@ export * from "${exportFrom}";
276
473
  `
277
474
  }
278
475
 
476
+ function createBrowserLoader({ name, autoInit, customJs, wasmDelivery }) {
477
+ const exportFrom = customJs ? './custom.js' : './core.js'
478
+
479
+ let simdUrl, baseUrl
480
+ if (wasmDelivery.type === 'jsdelivr') {
481
+ const pkg = wasmDelivery.package
482
+ const ver = wasmDelivery.version
483
+ simdUrl = `"https://cdn.jsdelivr.net/npm/${pkg}@${ver}/dist/wasm/${name}.simd.wasm"`
484
+ baseUrl = `"https://cdn.jsdelivr.net/npm/${pkg}@${ver}/dist/wasm/${name}.base.wasm"`
485
+ } else {
486
+ simdUrl = `new URL("./wasm/${name}.simd.wasm", import.meta.url)`
487
+ baseUrl = `new URL("./wasm/${name}.base.wasm", import.meta.url)`
488
+ }
489
+
490
+ const getBytesSrc = `
491
+ const simdUrl = ${simdUrl};
492
+ const baseUrl = ${baseUrl};
493
+
494
+ async function getWasmBytes() {
495
+ const [simdRes, baseRes] = await Promise.all([fetch(simdUrl), fetch(baseUrl)]);
496
+ const [simdBytes, baseBytes] = await Promise.all([simdRes.arrayBuffer(), baseRes.arrayBuffer()]);
497
+ return { simdBytes, baseBytes };
498
+ }
499
+ `
500
+ return createLoader({ exportFrom, autoInit, getBytesSrc })
501
+ }
502
+
279
503
  function createNodeLoader({ name, autoInit, customJs }) {
280
- const eager =
281
- autoInit === 'eager'
282
- ? '\nregisterInit(init);\ninit();'
283
- : '\nregisterInit(init);'
284
504
  const exportFrom = customJs ? './custom.js' : './core.js'
285
- return `import { readFile } from "node:fs/promises";
505
+ const getBytesSrc = `
506
+ import { readFile } from "node:fs/promises";
286
507
  import { fileURLToPath } from "node:url";
287
- import { setInstance, registerInit } from "./core.js";
288
- import { instantiateWithFallback } from "./util.js";
289
508
 
290
509
  const simdPath = fileURLToPath(new URL("./wasm/${name}.simd.wasm", import.meta.url));
291
510
  const basePath = fileURLToPath(new URL("./wasm/${name}.base.wasm", import.meta.url));
292
511
 
293
- let _ready = null;
294
-
295
- export function init(imports = {}) {
296
- return (_ready ??= (async () => {
297
- const [simdBytes, baseBytes] = await Promise.all([
298
- readFile(simdPath),
299
- readFile(basePath)
300
- ]);
301
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
302
- setInstance(instance);
303
- })());
512
+ async function getWasmBytes() {
513
+ const [simdBytes, baseBytes] = await Promise.all([readFile(simdPath), readFile(basePath)]);
514
+ return { simdBytes, baseBytes };
304
515
  }
305
- ${eager}
306
- export * from "${exportFrom}";
307
516
  `
517
+ return createLoader({ exportFrom, autoInit, getBytesSrc })
308
518
  }
309
519
 
310
520
  function createInlineLoader({ name, autoInit, customJs }) {
311
- const eager =
312
- autoInit === 'eager'
313
- ? '\nregisterInit(init);\ninit();'
314
- : '\nregisterInit(init);'
315
521
  const exportFrom = customJs ? './custom.js' : './core.js'
316
- return `import { wasmBytes as simdBytes } from "./wasm-inline/${name}.simd.wasm.js";
522
+ const getBytesSrc = `
523
+ import { wasmBytes as simdBytes } from "./wasm-inline/${name}.simd.wasm.js";
317
524
  import { wasmBytes as baseBytes } from "./wasm-inline/${name}.base.wasm.js";
318
- import { setInstance, registerInit } from "./core.js";
319
- import { instantiateWithFallback } from "./util.js";
320
-
321
- let _ready = null;
322
525
 
323
- export function init(imports = {}) {
324
- return (_ready ??= (async () => {
325
- const { instance } = await instantiateWithFallback(simdBytes, baseBytes, imports);
326
- setInstance(instance);
327
- })());
526
+ async function getWasmBytes() {
527
+ return { simdBytes, baseBytes };
328
528
  }
329
- ${eager}
330
- export * from "${exportFrom}";
331
529
  `
530
+ return createLoader({ exportFrom, autoInit, getBytesSrc })
332
531
  }
333
532
 
334
533
  function createInlineModule(bytes) {
@@ -365,6 +564,7 @@ export function emitRuntime({
365
564
  emitNode,
366
565
  emitBrowser,
367
566
  emitInline,
567
+ emitTypes,
368
568
  wasmPaths,
369
569
  exportsList,
370
570
  autoInit,
@@ -377,14 +577,36 @@ export function emitRuntime({
377
577
  if (customJs) {
378
578
  const customJsContent = readFileSync(join(process.cwd(), customJs), 'utf8')
379
579
  writeFileSync(join(outDir, 'custom.js'), customJsContent)
580
+
581
+ if (emitTypes) {
582
+ const customTsPath = customJs.replace(/\.js$/, '.d.ts')
583
+ if (existsSync(join(process.cwd(), customTsPath))) {
584
+ writeFileSync(
585
+ join(outDir, 'custom.d.ts'),
586
+ readFileSync(join(process.cwd(), customTsPath), 'utf8')
587
+ )
588
+ }
589
+ }
380
590
  }
381
591
 
382
592
  writeFileSync(
383
593
  join(outDir, 'core.js'),
384
594
  createCore({ exportsList, autoInit, stream })
385
595
  )
596
+ if (emitTypes) {
597
+ writeFileSync(
598
+ join(outDir, 'core.d.ts'),
599
+ createCoreTypes({ exportsList, autoInit, stream })
600
+ )
601
+ }
386
602
  writeFileSync(join(outDir, 'util.js'), readFileSync(UTIL_PATH, 'utf8'))
387
603
 
604
+ const loaderTypes = emitTypes
605
+ ? createLoaderTypes({
606
+ exportFrom: customJs ? './custom.js' : './core.js',
607
+ })
608
+ : null
609
+
388
610
  if (emitBrowser) {
389
611
  writeFileSync(
390
612
  join(outDir, 'browser.js'),
@@ -395,6 +617,7 @@ export function emitRuntime({
395
617
  wasmDelivery,
396
618
  })
397
619
  )
620
+ if (emitTypes) writeFileSync(join(outDir, 'browser.d.ts'), loaderTypes)
398
621
  }
399
622
 
400
623
  if (emitNode) {
@@ -402,6 +625,7 @@ export function emitRuntime({
402
625
  join(outDir, 'node.js'),
403
626
  createNodeLoader({ name: artifactBaseName, autoInit, customJs })
404
627
  )
628
+ if (emitTypes) writeFileSync(join(outDir, 'node.d.ts'), loaderTypes)
405
629
  }
406
630
 
407
631
  if (emitInline && wasmPaths.baselinePath) {
@@ -409,10 +633,13 @@ export function emitRuntime({
409
633
  join(outDir, 'browser-inline.js'),
410
634
  createInlineLoader({ name: artifactBaseName, autoInit, customJs })
411
635
  )
636
+ if (emitTypes)
637
+ writeFileSync(join(outDir, 'browser-inline.d.ts'), loaderTypes)
412
638
  writeFileSync(
413
639
  join(outDir, 'node-inline.js'),
414
640
  createInlineLoader({ name: artifactBaseName, autoInit, customJs })
415
641
  )
642
+ if (emitTypes) writeFileSync(join(outDir, 'node-inline.d.ts'), loaderTypes)
416
643
  writeInlineModules({
417
644
  outDir,
418
645
  artifactBaseName,
package/src/cli/index.js CHANGED
@@ -24,6 +24,7 @@ export async function runBuild(cliOpts) {
24
24
  emitNode: cfg.js.emit.node,
25
25
  emitBrowser: cfg.js.emit.browser,
26
26
  emitInline: cfg.inline && cfg.js.emit.inline,
27
+ emitTypes: cfg.js.emit.types,
27
28
  wasmPaths,
28
29
  exportsList: cfg.exports,
29
30
  autoInit: cfg.autoInit,
@@ -36,7 +37,6 @@ export async function runBuild(cliOpts) {
36
37
  updatePackageJson({
37
38
  crateDir: cfg.crateDir,
38
39
  outDir: cfg.outDir,
39
- artifactBaseName: cfg.artifactBaseName,
40
40
  js: cfg.js,
41
41
  inline: cfg.inline,
42
42
  })
package/src/cli/pkg.js CHANGED
@@ -1,13 +1,7 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from 'node:fs'
2
2
  import { join, relative } from 'node:path'
3
3
 
4
- export function updatePackageJson({
5
- crateDir,
6
- outDir,
7
- artifactBaseName,
8
- js,
9
- inline,
10
- }) {
4
+ export function updatePackageJson({ crateDir, outDir, js, inline }) {
11
5
  const pkgPath = join(crateDir, 'package.json')
12
6
  if (!existsSync(pkgPath)) return
13
7
 
@@ -17,6 +11,9 @@ export function updatePackageJson({
17
11
  if (!pkg.exports) pkg.exports = {}
18
12
 
19
13
  const mainExports = {}
14
+ if (js.emit.types) {
15
+ mainExports.types = `${relOutDir}/node.d.ts`
16
+ }
20
17
  if (js.emit.browser) {
21
18
  mainExports.browser = `${relOutDir}/browser.js`
22
19
  }
@@ -31,6 +28,9 @@ export function updatePackageJson({
31
28
 
32
29
  if (inline && js.emit.inline) {
33
30
  const inlineExports = {}
31
+ if (js.emit.types) {
32
+ inlineExports.types = `${relOutDir}/node-inline.d.ts`
33
+ }
34
34
  if (js.emit.browser) {
35
35
  inlineExports.browser = `${relOutDir}/browser-inline.js`
36
36
  }
@@ -44,9 +44,16 @@ export function updatePackageJson({
44
44
  }
45
45
  }
46
46
 
47
- // Also update main/module/types if they are missing?
48
- // For now let's just focus on exports as it's the modern way.
47
+ if (js.emit.types) {
48
+ pkg.types = `${relOutDir}/node.d.ts`
49
+ }
50
+ if (js.emit.node) {
51
+ pkg.main = `${relOutDir}/node.js`
52
+ }
53
+ if (js.emit.browser) {
54
+ pkg.module = `${relOutDir}/browser.js`
55
+ }
49
56
 
50
57
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
51
- console.log('Updated package.json exports')
58
+ console.log('Updated package.json exports and fields')
52
59
  }
package/src/js/util.js CHANGED
@@ -6,7 +6,7 @@ export async function instantiateWithFallback(
6
6
  try {
7
7
  const { instance } = await WebAssembly.instantiate(trySimdBytes, imports)
8
8
  return { instance, backend: 'wasm-simd' }
9
- } catch (e) {
9
+ } catch {
10
10
  // If SIMD fails (not supported), try baseline
11
11
  const { instance } = await WebAssembly.instantiate(baseBytes, imports)
12
12
  return { instance, backend: 'wasm' }