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 +1 -0
- package/LICENSE +1 -0
- package/package.json +3 -2
- package/scripts/build.js +1 -1
- package/scripts/test-examples.sh +1 -0
- package/src/cli/build.js +26 -20
- package/src/cli/config.js +75 -81
- package/src/cli/emit.js +494 -267
- package/src/cli/index.js +1 -1
- package/src/cli/pkg.js +17 -10
- package/src/js/util.js +1 -1
package/Cargo.toml
CHANGED
package/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wasm-bindgen-lite",
|
|
3
|
-
"version": "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
package/scripts/test-examples.sh
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
let simdPath = null
|
|
69
|
+
const paths = { baselinePath: null, simdPath: null, wasmOutDir }
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
234
|
+
b.blank()
|
|
235
|
+
if (!w.reuseBuffer) {
|
|
236
|
+
b.line('free(inPtr, len);')
|
|
237
|
+
b.line('free(outPtr, outLen);')
|
|
158
238
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
201
|
-
_inst = instance;
|
|
202
|
-
refreshViews();
|
|
363
|
+
return b.toString()
|
|
203
364
|
}
|
|
204
365
|
|
|
205
|
-
export function
|
|
206
|
-
|
|
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
|
-
|
|
408
|
+
const ret = needsEnsure ? `Promise<${tsRetType}>` : tsRetType
|
|
409
|
+
b.line(`export function ${w.fnName}(input: WasmInput): ${ret};`)
|
|
410
|
+
})
|
|
210
411
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
return _inst.exports.alloc_bytes(len) >>> 0;
|
|
419
|
+
return b.toString()
|
|
218
420
|
}
|
|
219
421
|
|
|
220
|
-
export function
|
|
221
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
324
|
-
return
|
|
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
|
-
|
|
48
|
-
|
|
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
|
|
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' }
|