wasm-bindgen-lite 0.1.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.lock +27 -0
- package/Cargo.toml +23 -0
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/bin/wasm-bindgen-lite.js +101 -0
- package/package.json +59 -0
- package/scripts/build.js +91 -0
- package/scripts/test-examples.sh +25 -0
- package/scripts/test.js +48 -0
- package/src/cli/build.js +83 -0
- package/src/cli/config.js +211 -0
- package/src/cli/emit.js +423 -0
- package/src/cli/index.js +79 -0
- package/src/cli/pkg.js +52 -0
- package/src/js/browser-inline.js +19 -0
- package/src/js/browser.js +30 -0
- package/src/js/core.js +57 -0
- package/src/js/node-inline.js +19 -0
- package/src/js/node.js +26 -0
- package/src/js/util.js +14 -0
- package/src/lib.rs +72 -0
package/scripts/test.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { init as initNode, process as processNode } from '../dist/node.js'
|
|
3
|
+
import {
|
|
4
|
+
init as initInline,
|
|
5
|
+
process as processInline,
|
|
6
|
+
} from '../dist/node-inline.js'
|
|
7
|
+
|
|
8
|
+
async function testNode() {
|
|
9
|
+
console.log('Testing Node external loader...')
|
|
10
|
+
await initNode()
|
|
11
|
+
|
|
12
|
+
const input = new Uint8Array([1, 2, 3])
|
|
13
|
+
const output = processNode(input)
|
|
14
|
+
|
|
15
|
+
console.log('Input:', input)
|
|
16
|
+
console.log('Output:', output)
|
|
17
|
+
|
|
18
|
+
assert.deepStrictEqual(Array.from(output), [2, 3, 4])
|
|
19
|
+
console.log('Node external loader test passed!')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function testInline() {
|
|
23
|
+
console.log('\nTesting Node inline loader...')
|
|
24
|
+
// Note: /inline exports are mapped in package.json to ./dist/node-inline.js for Node
|
|
25
|
+
await initInline()
|
|
26
|
+
|
|
27
|
+
const input = new Uint8Array([10, 20, 30])
|
|
28
|
+
const output = processInline(input)
|
|
29
|
+
|
|
30
|
+
console.log('Input:', input)
|
|
31
|
+
console.log('Output:', output)
|
|
32
|
+
|
|
33
|
+
assert.deepStrictEqual(Array.from(output), [11, 21, 31])
|
|
34
|
+
console.log('Node inline loader test passed!')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function runTests() {
|
|
38
|
+
try {
|
|
39
|
+
await testNode()
|
|
40
|
+
await testInline()
|
|
41
|
+
console.log('\nAll JS tests passed!')
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error('JS tests failed:', e)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
runTests()
|
package/src/cli/build.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { copyFileSync, mkdirSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
function runCargoBuild({ crateDir, release, simd }) {
|
|
6
|
+
const args = ['build', '--target', 'wasm32-unknown-unknown']
|
|
7
|
+
if (release) args.push('--release')
|
|
8
|
+
|
|
9
|
+
const env = { ...process.env }
|
|
10
|
+
if (simd) {
|
|
11
|
+
const base = process.env.RUSTFLAGS || ''
|
|
12
|
+
const extra = '-C target-feature=+simd128'
|
|
13
|
+
env.RUSTFLAGS = [base, extra].filter(Boolean).join(' ').trim()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
execSync(`cargo ${args.join(' ')}`, {
|
|
17
|
+
cwd: crateDir,
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
env,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function wasmPath({ crateDir, release, wasmFileStem }) {
|
|
24
|
+
const profile = release ? 'release' : 'debug'
|
|
25
|
+
return join(
|
|
26
|
+
crateDir,
|
|
27
|
+
'target',
|
|
28
|
+
'wasm32-unknown-unknown',
|
|
29
|
+
profile,
|
|
30
|
+
`${wasmFileStem}.wasm`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function maybeRunWasmOpt(wasmFile, wasmOpt) {
|
|
35
|
+
if (wasmOpt.mode === 'off') return
|
|
36
|
+
if (wasmOpt.mode === 'auto') {
|
|
37
|
+
try {
|
|
38
|
+
execSync('wasm-opt --version', { stdio: 'ignore' })
|
|
39
|
+
} catch {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const args = ['wasm-opt', ...wasmOpt.args, wasmFile, '-o', wasmFile]
|
|
45
|
+
execSync(args.join(' '), { stdio: 'inherit' })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function buildArtifacts({
|
|
49
|
+
crateDir,
|
|
50
|
+
wasmFileStem,
|
|
51
|
+
artifactBaseName,
|
|
52
|
+
outDir,
|
|
53
|
+
targets,
|
|
54
|
+
release,
|
|
55
|
+
wasmOpt,
|
|
56
|
+
}) {
|
|
57
|
+
mkdirSync(outDir, { recursive: true })
|
|
58
|
+
const wasmOutDir = join(outDir, 'wasm')
|
|
59
|
+
mkdirSync(wasmOutDir, { recursive: true })
|
|
60
|
+
|
|
61
|
+
let baselinePath = null
|
|
62
|
+
let simdPath = null
|
|
63
|
+
|
|
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
|
+
}
|
|
72
|
+
|
|
73
|
+
if (targets.simd) {
|
|
74
|
+
console.log('Building SIMD wasm...')
|
|
75
|
+
runCargoBuild({ crateDir, release, simd: true })
|
|
76
|
+
const built = wasmPath({ crateDir, release, wasmFileStem })
|
|
77
|
+
simdPath = join(wasmOutDir, `${artifactBaseName}.simd.wasm`)
|
|
78
|
+
copyFileSync(built, simdPath)
|
|
79
|
+
maybeRunWasmOpt(simdPath, wasmOpt)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { baselinePath, simdPath, wasmOutDir }
|
|
83
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
outDir: 'dist-wasm-bindgen-lite',
|
|
6
|
+
artifactBaseName: 'mod',
|
|
7
|
+
targets: {
|
|
8
|
+
baseline: true,
|
|
9
|
+
simd: true,
|
|
10
|
+
},
|
|
11
|
+
inline: true,
|
|
12
|
+
release: true,
|
|
13
|
+
wasmOpt: {
|
|
14
|
+
mode: 'auto', // auto | on | off
|
|
15
|
+
args: ['-Oz'],
|
|
16
|
+
},
|
|
17
|
+
js: {
|
|
18
|
+
emit: {
|
|
19
|
+
node: true,
|
|
20
|
+
browser: true,
|
|
21
|
+
inline: true,
|
|
22
|
+
},
|
|
23
|
+
custom: null, // path to custom JS file to include and re-export
|
|
24
|
+
},
|
|
25
|
+
exports: [
|
|
26
|
+
{
|
|
27
|
+
abi: 'process_bytes',
|
|
28
|
+
name: 'process',
|
|
29
|
+
return: 'bytes', // bytes | f32 | f64 | i32 | u32 | i16 | u16 | i8 | u8
|
|
30
|
+
reuseBuffer: false,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
autoInit: 'off', // off | lazy | eager
|
|
34
|
+
stream: {
|
|
35
|
+
enable: false,
|
|
36
|
+
export: 'process',
|
|
37
|
+
delimiter: null, // null | number (byte value)
|
|
38
|
+
},
|
|
39
|
+
wasmDelivery: {
|
|
40
|
+
type: 'relative', // relative | jsdelivr
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseTomlName(contents) {
|
|
45
|
+
// leniently find the first name in the [package] section
|
|
46
|
+
const pkgMatch = /\[package\]([\s\S]*?)(?:\n\[[^\]]|\r?\n\[[^\]])/m.exec(
|
|
47
|
+
contents + '\n['
|
|
48
|
+
) // sentinel [
|
|
49
|
+
if (!pkgMatch) return null
|
|
50
|
+
const body = pkgMatch[1]
|
|
51
|
+
const nameMatch = /name\s*=\s*["']([^"']+)["']/m.exec(body)
|
|
52
|
+
return nameMatch ? nameMatch[1] : null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readCrateName(crateDir) {
|
|
56
|
+
const cargoPath = join(crateDir, 'Cargo.toml')
|
|
57
|
+
const contents = readFileSync(cargoPath, 'utf8')
|
|
58
|
+
const crateName = parseTomlName(contents)
|
|
59
|
+
if (!crateName) {
|
|
60
|
+
throw new Error(`Could not find package.name in ${cargoPath}`)
|
|
61
|
+
}
|
|
62
|
+
return crateName
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeEmit(value) {
|
|
66
|
+
if (!value) return { node: true, browser: true, inline: true }
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
const set = new Set(value)
|
|
69
|
+
return {
|
|
70
|
+
node: set.has('node'),
|
|
71
|
+
browser: set.has('browser'),
|
|
72
|
+
inline: set.has('inline'),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === 'object') {
|
|
76
|
+
return {
|
|
77
|
+
node: value.node !== false,
|
|
78
|
+
browser: value.browser !== false,
|
|
79
|
+
inline: value.inline !== false,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { node: true, browser: true, inline: true }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeWasmOpt(input) {
|
|
86
|
+
if (!input || input.mode === 'auto' || input === 'auto') {
|
|
87
|
+
return { mode: 'auto', args: input?.args || DEFAULT_CONFIG.wasmOpt.args }
|
|
88
|
+
}
|
|
89
|
+
if (input === 'off' || input?.mode === 'off') {
|
|
90
|
+
return { mode: 'off', args: input?.args || DEFAULT_CONFIG.wasmOpt.args }
|
|
91
|
+
}
|
|
92
|
+
return { mode: 'on', args: input?.args || DEFAULT_CONFIG.wasmOpt.args }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function loadConfigFromCli(cliOpts = {}) {
|
|
96
|
+
const crateDir = resolve(cliOpts.crate || '.')
|
|
97
|
+
const cfgPath = cliOpts.configPath
|
|
98
|
+
? resolve(crateDir, cliOpts.configPath)
|
|
99
|
+
: resolve(crateDir, 'wasm-bindgen-lite.config.json')
|
|
100
|
+
|
|
101
|
+
let fileConfig = {}
|
|
102
|
+
if (existsSync(cfgPath)) {
|
|
103
|
+
fileConfig = JSON.parse(readFileSync(cfgPath, 'utf8'))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
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
|
+
|
|
176
|
+
const config = {
|
|
177
|
+
crateDir,
|
|
178
|
+
crateName,
|
|
179
|
+
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,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return config
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function summarizeConfig(cfg) {
|
|
197
|
+
return {
|
|
198
|
+
crateDir: cfg.crateDir,
|
|
199
|
+
outDir: cfg.outDir,
|
|
200
|
+
artifactBaseName: cfg.artifactBaseName,
|
|
201
|
+
targets: cfg.targets,
|
|
202
|
+
inline: cfg.inline,
|
|
203
|
+
wasmOpt: cfg.wasmOpt,
|
|
204
|
+
release: cfg.release,
|
|
205
|
+
jsEmit: cfg.js.emit,
|
|
206
|
+
exports: cfg.exports,
|
|
207
|
+
autoInit: cfg.autoInit,
|
|
208
|
+
stream: cfg.stream,
|
|
209
|
+
wasmDelivery: cfg.wasmDelivery,
|
|
210
|
+
}
|
|
211
|
+
}
|