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/Cargo.lock
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "wasm-bindgen-lite"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
|
|
9
|
+
[[package]]
|
|
10
|
+
name = "wasm-bindgen-lite-example-browser"
|
|
11
|
+
version = "0.1.0"
|
|
12
|
+
|
|
13
|
+
[[package]]
|
|
14
|
+
name = "wasm-bindgen-lite-example-node"
|
|
15
|
+
version = "0.1.0"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "wasm-bindgen-lite-example-offset-split"
|
|
19
|
+
version = "0.1.0"
|
|
20
|
+
|
|
21
|
+
[[package]]
|
|
22
|
+
name = "wasm-bindgen-lite-example-simd-sum"
|
|
23
|
+
version = "0.1.0"
|
|
24
|
+
|
|
25
|
+
[[package]]
|
|
26
|
+
name = "wasm-bindgen-lite-example-streaming-lines"
|
|
27
|
+
version = "0.1.0"
|
package/Cargo.toml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "wasm-bindgen-lite"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "CLI tool and runtime to build Rust crates into minimal, SIMD-optimized WASM packages with JS loaders"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
repository = "https://github.com/addmaple/wasm-bindgen-lite"
|
|
8
|
+
|
|
9
|
+
[workspace]
|
|
10
|
+
members = ["examples/*"]
|
|
11
|
+
|
|
12
|
+
[lib]
|
|
13
|
+
crate-type = ["cdylib", "rlib"]
|
|
14
|
+
|
|
15
|
+
[dependencies]
|
|
16
|
+
|
|
17
|
+
[profile.release]
|
|
18
|
+
opt-level = "s"
|
|
19
|
+
lto = true
|
|
20
|
+
codegen-units = 1
|
|
21
|
+
panic = "abort"
|
|
22
|
+
strip = true
|
|
23
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wasm-bindgen-lite contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# wasm-bindgen-lite
|
|
2
|
+
|
|
3
|
+
An ultra-minimal, high-performance CLI tool and runtime for building Rust WebAssembly packages.
|
|
4
|
+
|
|
5
|
+
`wasm-bindgen-lite` is designed for developers who want the performance of Rust WASM without the overhead and complexity of `wasm-bindgen`. It provides a thin, manual-memory-management layer that is perfect for performance-critical utilities like encoders, decoders, crypto, and data processing.
|
|
6
|
+
|
|
7
|
+
## Why "Lite"?
|
|
8
|
+
|
|
9
|
+
Traditional `wasm-bindgen` generates a lot of glue code and often hides the underlying memory management. While convenient, it can introduce overhead and make it difficult to optimize data transfers.
|
|
10
|
+
|
|
11
|
+
**`wasm-bindgen-lite` gives you:**
|
|
12
|
+
|
|
13
|
+
- **Zero-cost ABI**: Uses standard `extern "C"` functions.
|
|
14
|
+
- **Manual Control**: Explicit `alloc` and `free` for maximum efficiency.
|
|
15
|
+
- **SIMD by Default**: Built-in support for SIMD detection and fallback.
|
|
16
|
+
- **Tiny Runtime**: A minimal JS wrapper (usually < 2KB) that works in Node.js and browsers.
|
|
17
|
+
- **Modern ESM**: Pure ESM output for modern build tools and runtimes.
|
|
18
|
+
|
|
19
|
+
## Key Features
|
|
20
|
+
|
|
21
|
+
- 🚀 **SIMD Fallback**: Automatically compiles two versions of your WASM (baseline and SIMD). The runtime detects support and loads the fastest one.
|
|
22
|
+
- 📦 **Dual Loaders**: Supports standard `.wasm` files (best for caching) or inlined base64 (best for single-file distribution).
|
|
23
|
+
- 🔄 **Streaming Support**: Built-in integration with `TransformStream` for processing data chunks on the fly.
|
|
24
|
+
- 🛠️ **Configurable**: Simple `wasm-bindgen-lite.config.json` to define your exports and behavior.
|
|
25
|
+
- ⚡ **Optimized**: Integrated with `wasm-opt` for smallest possible binaries.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -D wasm-bindgen-lite
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Prepare your Rust code
|
|
36
|
+
|
|
37
|
+
Expose `alloc_bytes` and `free_bytes` along with your functions. No `wasm-bindgen` dependency required!
|
|
38
|
+
|
|
39
|
+
```rust
|
|
40
|
+
use std::alloc::{alloc, dealloc, Layout};
|
|
41
|
+
use std::mem;
|
|
42
|
+
|
|
43
|
+
#[no_mangle]
|
|
44
|
+
pub unsafe extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
|
|
45
|
+
let layout = Layout::from_size_align(len, mem::align_of::<u8>()).unwrap();
|
|
46
|
+
alloc(layout)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[no_mangle]
|
|
50
|
+
pub unsafe extern "C" fn free_bytes(ptr: *mut u8, len: usize) {
|
|
51
|
+
let layout = Layout::from_size_align(len, mem::align_of::<u8>()).unwrap();
|
|
52
|
+
dealloc(ptr, layout);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[no_mangle]
|
|
56
|
+
pub unsafe extern "C" fn my_transform(
|
|
57
|
+
in_ptr: *const u8, in_len: usize,
|
|
58
|
+
out_ptr: *mut u8, _out_len: usize,
|
|
59
|
+
) -> isize {
|
|
60
|
+
let input = std::slice::from_raw_parts(in_ptr, in_len);
|
|
61
|
+
let output = std::slice::from_raw_parts_mut(out_ptr, in_len);
|
|
62
|
+
|
|
63
|
+
for i in 0..in_len {
|
|
64
|
+
output[i] = input[i].wrapping_add(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
in_len as isize
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Build
|
|
72
|
+
|
|
73
|
+
Run the CLI to compile and generate JS loaders:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
rustup target add wasm32-unknown-unknown
|
|
77
|
+
npx wasm-bindgen-lite build --crate . --out ./wasm-dist
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Use in JavaScript
|
|
81
|
+
|
|
82
|
+
If you've published your output as an npm package (e.g., `my-wasm-pkg`), consumers can simply import it. Modern runtimes and bundlers will automatically pick the correct version (Node vs Browser) via conditional exports.
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { init, my_transform } from 'my-wasm-pkg'
|
|
86
|
+
|
|
87
|
+
await init()
|
|
88
|
+
|
|
89
|
+
const input = new Uint8Array([1, 2, 3])
|
|
90
|
+
const output = my_transform(input)
|
|
91
|
+
console.log(output) // Uint8Array([2, 3, 4])
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Publishing & Automatic Export Configuration
|
|
95
|
+
|
|
96
|
+
When you build your project, `wasm-bindgen-lite` automatically detects your `package.json` and adds or updates the `exports` field to point to the generated artifacts.
|
|
97
|
+
|
|
98
|
+
This ensures that modern runtimes and bundlers automatically pick the correct version (Node vs Browser) via conditional exports.
|
|
99
|
+
|
|
100
|
+
### Generated Exports Configuration
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"exports": {
|
|
105
|
+
".": {
|
|
106
|
+
"browser": "./wasm-dist/browser.js",
|
|
107
|
+
"node": "./wasm-dist/node.js",
|
|
108
|
+
"default": "./wasm-dist/node.js"
|
|
109
|
+
},
|
|
110
|
+
"./inline": {
|
|
111
|
+
"browser": "./wasm-dist/browser-inline.js",
|
|
112
|
+
"node": "./wasm-dist/node-inline.js",
|
|
113
|
+
"default": "./wasm-dist/node-inline.js"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Import Examples
|
|
120
|
+
|
|
121
|
+
#### Standard (Automatic Environment Detection)
|
|
122
|
+
|
|
123
|
+
Used by Vite, Webpack, and Node.js.
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
import { init, my_transform } from 'my-wasm-pkg'
|
|
127
|
+
await init()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Inline (Zero External Files)
|
|
131
|
+
|
|
132
|
+
Perfect for serverless or single-file distribution.
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
import { init, my_transform } from 'my-wasm-pkg/inline'
|
|
136
|
+
await init()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### CDN (jsDelivr, unpkg, etc.)
|
|
140
|
+
|
|
141
|
+
Because the browser loader uses modern `import.meta.url` resolution, it works out-of-the-box on CDNs. No extra configuration is needed.
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
import {
|
|
145
|
+
init,
|
|
146
|
+
my_transform,
|
|
147
|
+
} from 'https://cdn.jsdelivr.net/npm/my-wasm-pkg/wasm-dist/browser.js'
|
|
148
|
+
await init()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
> **Note**: For advanced users, there is a `wasmDelivery: { type: "jsdelivr" }` config option if you want to bundle the JS locally but fetch WASM binaries from a CDN (offloading).
|
|
152
|
+
|
|
153
|
+
## Initialization Modes (`autoInit`)
|
|
154
|
+
|
|
155
|
+
The `autoInit` setting controls how and when the WASM module is instantiated.
|
|
156
|
+
|
|
157
|
+
### 1. `off` (Default)
|
|
158
|
+
|
|
159
|
+
You must manually call `init()` and wait for it before calling any WASM functions.
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
import { init, process } from 'my-wasm-pkg'
|
|
163
|
+
await init()
|
|
164
|
+
const result = process(data) // Sync call
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 2. `lazy` (Automatic & Async)
|
|
168
|
+
|
|
169
|
+
The generated wrapper functions are `async`. They will automatically call `init()` on the first invocation.
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
import { process } from 'my-wasm-pkg'
|
|
173
|
+
const result = await process(data) // First call inits automatically
|
|
174
|
+
const result2 = await process(data2) // Subsequent calls use existing init
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 3. `eager` (Immediate)
|
|
178
|
+
|
|
179
|
+
`init()` is called immediately when the module is imported.
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
import { init, process } from 'my-wasm-pkg'
|
|
183
|
+
// init() is already running in the background
|
|
184
|
+
await init() // Ensure it's finished
|
|
185
|
+
const result = process(data)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Custom JS Wrapper
|
|
189
|
+
|
|
190
|
+
You can provide a `js.custom` file to add high-level APIs. It has access to the internal `core.js` utilities.
|
|
191
|
+
|
|
192
|
+
**Example `src/wrapper.js`:**
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
import { createTransformStream } from './core.js'
|
|
196
|
+
export * from './core.js' // Re-export everything from core
|
|
197
|
+
|
|
198
|
+
const decoder = new TextDecoder()
|
|
199
|
+
|
|
200
|
+
export function createLineStream() {
|
|
201
|
+
// createTransformStream handles alloc/free for you
|
|
202
|
+
const wasmSplit = createTransformStream('splitLines')
|
|
203
|
+
|
|
204
|
+
return wasmSplit.readable.pipeThrough(
|
|
205
|
+
new TransformStream({
|
|
206
|
+
transform(chunk, controller) {
|
|
207
|
+
controller.enqueue(decoder.decode(chunk))
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Specify it in your config:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"js": { "custom": "src/wrapper.js" }
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Configuration (`wasm-bindgen-lite.config.json`)
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"outDir": "wasm-dist",
|
|
227
|
+
"artifactBaseName": "mod",
|
|
228
|
+
"inline": true,
|
|
229
|
+
"targets": { "baseline": true, "simd": true },
|
|
230
|
+
"wasmOpt": { "mode": "auto", "args": ["-Oz"] },
|
|
231
|
+
"js": {
|
|
232
|
+
"emit": ["node", "browser", "inline"],
|
|
233
|
+
"custom": "src/wrapper.js"
|
|
234
|
+
},
|
|
235
|
+
"exports": [
|
|
236
|
+
{
|
|
237
|
+
"abi": "my_transform",
|
|
238
|
+
"name": "process",
|
|
239
|
+
"return": "bytes",
|
|
240
|
+
"reuseBuffer": true
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"autoInit": "lazy",
|
|
244
|
+
"stream": {
|
|
245
|
+
"enable": true,
|
|
246
|
+
"export": "process",
|
|
247
|
+
"delimiter": 10
|
|
248
|
+
},
|
|
249
|
+
"wasmDelivery": { "type": "relative" }
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Configuration Options
|
|
254
|
+
|
|
255
|
+
| Option | Description | Default |
|
|
256
|
+
| ----------------------- | ------------------------------------------------------------ | ------------- |
|
|
257
|
+
| `outDir` | Directory for generated files | `"dist"` |
|
|
258
|
+
| `artifactBaseName` | Base name for `.wasm` files | `"mod"` |
|
|
259
|
+
| `inline` | Whether to generate inline JS modules | `false` |
|
|
260
|
+
| `autoInit` | `"off"`, `"lazy"`, `"eager"` | `"off"` |
|
|
261
|
+
| `exports` | List of WASM functions to wrap | `[]` |
|
|
262
|
+
| `exports[].abi` | Name of the `extern "C"` function in Rust | required |
|
|
263
|
+
| `exports[].name` | Name of the exported JS function | same as `abi` |
|
|
264
|
+
| `exports[].return` | Return type: `bytes`, `f32`, `i32`, `u32`, etc. | `"bytes"` |
|
|
265
|
+
| `exports[].reuseBuffer` | If true, reuses the same memory buffer to reduce allocations | `false` |
|
|
266
|
+
| `stream.enable` | Generates a `createTransformStream()` helper | `false` |
|
|
267
|
+
| `js.custom` | Path to a custom JS file to include in the runtime | `null` |
|
|
268
|
+
|
|
269
|
+
## Advanced Usage
|
|
270
|
+
|
|
271
|
+
### SIMD Acceleration
|
|
272
|
+
|
|
273
|
+
`wasm-bindgen-lite` automatically compiles your Rust code with SIMD features enabled for the `.simd.wasm` target. The runtime detects support and picks the optimal binary.
|
|
274
|
+
|
|
275
|
+
### Streaming Processing
|
|
276
|
+
|
|
277
|
+
Use `createTransformStream()` for high-performance data pipelines:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
import { init, createTransformStream } from 'my-wasm-pkg'
|
|
281
|
+
await init()
|
|
282
|
+
|
|
283
|
+
const response = await fetch('data.bin')
|
|
284
|
+
const processed = response.body.pipeThrough(createTransformStream())
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## CLI Reference
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
wasm-bindgen-lite build [options]
|
|
291
|
+
|
|
292
|
+
Options:
|
|
293
|
+
--crate <path> Path to Rust crate (default: ".")
|
|
294
|
+
--out <path> Output directory
|
|
295
|
+
--release Build in release mode (default)
|
|
296
|
+
--debug Build in debug mode
|
|
297
|
+
--inline Generate inline JS loaders
|
|
298
|
+
--no-simd Disable SIMD build
|
|
299
|
+
--wasm-opt Enable wasm-opt (default)
|
|
300
|
+
--wasm-opt-args Custom args for wasm-opt
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Examples
|
|
304
|
+
|
|
305
|
+
- [node-basic](./examples/node-basic): Minimal Node.js setup.
|
|
306
|
+
- [browser-vite](./examples/browser-vite): Modern browser setup with Vite.
|
|
307
|
+
- [simd-sum](./examples/simd-sum): SIMD-accelerated array processing.
|
|
308
|
+
- [streaming-lines](./examples/streaming-lines): Streaming data with custom wrappers.
|
|
309
|
+
- [offset-split](./examples/offset-split): Advanced buffer management and complex ABI.
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
MIT
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runBuild, runClean, printHelp } from '../src/cli/index.js'
|
|
3
|
+
|
|
4
|
+
function parseArgs(raw) {
|
|
5
|
+
const [command, ...rest] = raw
|
|
6
|
+
const opts = {}
|
|
7
|
+
const unknown = []
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
10
|
+
const arg = rest[i]
|
|
11
|
+
switch (arg) {
|
|
12
|
+
case '--crate':
|
|
13
|
+
opts.crate = rest[++i]
|
|
14
|
+
break
|
|
15
|
+
case '--out':
|
|
16
|
+
opts.out = rest[++i]
|
|
17
|
+
break
|
|
18
|
+
case '--config':
|
|
19
|
+
opts.configPath = rest[++i]
|
|
20
|
+
break
|
|
21
|
+
case '--release':
|
|
22
|
+
opts.release = true
|
|
23
|
+
break
|
|
24
|
+
case '--debug':
|
|
25
|
+
opts.release = false
|
|
26
|
+
break
|
|
27
|
+
case '--inline':
|
|
28
|
+
opts.inline = true
|
|
29
|
+
break
|
|
30
|
+
case '--no-inline':
|
|
31
|
+
opts.inline = false
|
|
32
|
+
break
|
|
33
|
+
case '--simd':
|
|
34
|
+
opts.simd = true
|
|
35
|
+
break
|
|
36
|
+
case '--no-simd':
|
|
37
|
+
opts.simd = false
|
|
38
|
+
break
|
|
39
|
+
case '--wasm-opt':
|
|
40
|
+
opts.wasmOptMode = 'on'
|
|
41
|
+
break
|
|
42
|
+
case '--no-wasm-opt':
|
|
43
|
+
opts.wasmOptMode = 'off'
|
|
44
|
+
break
|
|
45
|
+
case '--wasm-opt-args':
|
|
46
|
+
opts.wasmOptArgs = (rest[++i] || '').split(' ').filter(Boolean)
|
|
47
|
+
break
|
|
48
|
+
case '--update-package-json':
|
|
49
|
+
opts.updatePackageJson = true
|
|
50
|
+
break
|
|
51
|
+
case '--no-update-package-json':
|
|
52
|
+
opts.updatePackageJson = false
|
|
53
|
+
break
|
|
54
|
+
case '--help':
|
|
55
|
+
case '-h':
|
|
56
|
+
opts.help = true
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
if (arg.startsWith('-')) {
|
|
60
|
+
unknown.push(arg)
|
|
61
|
+
} else {
|
|
62
|
+
unknown.push(arg)
|
|
63
|
+
}
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { command, opts, unknown }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main() {
|
|
72
|
+
const { command, opts, unknown } = parseArgs(process.argv.slice(2))
|
|
73
|
+
|
|
74
|
+
if (opts.help || !command || command === 'help') {
|
|
75
|
+
printHelp()
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (unknown.length) {
|
|
80
|
+
console.warn(`Ignoring unknown arguments: ${unknown.join(', ')}`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (command === 'build') {
|
|
84
|
+
await runBuild(opts)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (command === 'clean') {
|
|
89
|
+
await runClean(opts)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.error(`Unknown command: ${command}`)
|
|
94
|
+
printHelp()
|
|
95
|
+
process.exitCode = 1
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
main().catch((err) => {
|
|
99
|
+
console.error(err?.stack || err)
|
|
100
|
+
process.exitCode = 1
|
|
101
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wasm-bindgen-lite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI tool to build Rust crates into minimal, SIMD-optimized WASM packages with JS loaders",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/addmaple/wasm-bindgen-lite.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/addmaple/wasm-bindgen-lite#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/addmaple/wasm-bindgen-lite/issues"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"src/",
|
|
17
|
+
"scripts/",
|
|
18
|
+
"Cargo.toml",
|
|
19
|
+
"Cargo.lock",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"wasm-bindgen-lite": "./bin/wasm-bindgen-lite.js"
|
|
25
|
+
},
|
|
26
|
+
"main": "./src/cli/index.js",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.0.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "node bin/wasm-bindgen-lite.js build --crate . --out dist --no-update-package-json",
|
|
32
|
+
"test": "cargo test && node scripts/test.js",
|
|
33
|
+
"test:examples": "./scripts/test-examples.sh",
|
|
34
|
+
"lint": "npm run lint:js && npm run lint:rust",
|
|
35
|
+
"lint:js": "eslint . && prettier --check .",
|
|
36
|
+
"lint:rust": "cargo clippy --workspace -- -D warnings",
|
|
37
|
+
"format": "prettier --write . && cargo fmt"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"wasm",
|
|
41
|
+
"rust",
|
|
42
|
+
"simd",
|
|
43
|
+
"minimal",
|
|
44
|
+
"loader"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^9.39.2",
|
|
49
|
+
"base64-js": "^1.5.1",
|
|
50
|
+
"eslint": "^9.39.2",
|
|
51
|
+
"eslint-config-prettier": "^10.1.8",
|
|
52
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
53
|
+
"globals": "^16.5.0",
|
|
54
|
+
"prettier": "^3.7.4"
|
|
55
|
+
},
|
|
56
|
+
"exports": {
|
|
57
|
+
".": "./src/cli/index.js"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs'
|
|
3
|
+
import { join, dirname } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const ROOT = join(__dirname, '..')
|
|
8
|
+
const DIST = join(ROOT, 'dist')
|
|
9
|
+
const SRC_JS = join(ROOT, 'src/js')
|
|
10
|
+
|
|
11
|
+
function run(cmd, env = {}) {
|
|
12
|
+
console.log(`> ${cmd}`)
|
|
13
|
+
execSync(cmd, { stdio: 'inherit', env: { ...process.env, ...env } })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 1. Ensure target directory exists
|
|
17
|
+
mkdirSync(join(DIST, 'wasm'), { recursive: true })
|
|
18
|
+
mkdirSync(join(DIST, 'wasm-inline'), { recursive: true })
|
|
19
|
+
|
|
20
|
+
// 2. Build WASM (Baseline)
|
|
21
|
+
console.log('Building baseline WASM...')
|
|
22
|
+
run('cargo build --target wasm32-unknown-unknown --release')
|
|
23
|
+
const baselineWasm = join(
|
|
24
|
+
ROOT,
|
|
25
|
+
'target/wasm32-unknown-unknown/release/wasm_bindgen_lite.wasm'
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
// 3. Build WASM (SIMD)
|
|
29
|
+
console.log('Building SIMD WASM...')
|
|
30
|
+
run('cargo build --target wasm32-unknown-unknown --release', {
|
|
31
|
+
RUSTFLAGS: '-C target-feature=+simd128',
|
|
32
|
+
})
|
|
33
|
+
const simdWasm = join(
|
|
34
|
+
ROOT,
|
|
35
|
+
'target/wasm32-unknown-unknown/release/wasm_bindgen_lite.wasm'
|
|
36
|
+
)
|
|
37
|
+
// Note: Since we are building the same crate, we need to move the baseline first or build into different targets.
|
|
38
|
+
// Actually, cargo build will overwrite. Let's build baseline, move it, then build SIMD.
|
|
39
|
+
|
|
40
|
+
// Let's redo step 2 and 3 properly
|
|
41
|
+
console.log('Building baseline WASM...')
|
|
42
|
+
run('cargo build --target wasm32-unknown-unknown --release')
|
|
43
|
+
copyFileSync(baselineWasm, join(DIST, 'wasm/mod.base.wasm'))
|
|
44
|
+
|
|
45
|
+
console.log('Building SIMD WASM...')
|
|
46
|
+
run('cargo build --target wasm32-unknown-unknown --release', {
|
|
47
|
+
RUSTFLAGS: '-C target-feature=+simd128',
|
|
48
|
+
})
|
|
49
|
+
copyFileSync(simdWasm, join(DIST, 'wasm/mod.simd.wasm'))
|
|
50
|
+
|
|
51
|
+
// 4. Optimize (Optional - check for wasm-opt)
|
|
52
|
+
try {
|
|
53
|
+
run('wasm-opt --version')
|
|
54
|
+
console.log('Optimizing WASM...')
|
|
55
|
+
run(
|
|
56
|
+
`wasm-opt -Oz ${join(DIST, 'wasm/mod.base.wasm')} -o ${join(DIST, 'wasm/mod.base.wasm')}`
|
|
57
|
+
)
|
|
58
|
+
run(
|
|
59
|
+
`wasm-opt -Oz ${join(DIST, 'wasm/mod.simd.wasm')} -o ${join(DIST, 'wasm/mod.simd.wasm')}`
|
|
60
|
+
)
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.warn('wasm-opt not found, skipping optimization.')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 5. Generate inline JS modules
|
|
66
|
+
function generateInline(name, wasmPath) {
|
|
67
|
+
const bytes = readFileSync(wasmPath)
|
|
68
|
+
const content = `// auto-generated\nexport const wasmBytes = new Uint8Array([${bytes.join(',')}]);\n`
|
|
69
|
+
writeFileSync(join(DIST, `wasm-inline/mod.${name}.wasm.js`), content)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('Generating inline WASM modules...')
|
|
73
|
+
generateInline('base', join(DIST, 'wasm/mod.base.wasm'))
|
|
74
|
+
generateInline('simd', join(DIST, 'wasm/mod.simd.wasm'))
|
|
75
|
+
|
|
76
|
+
// 6. Copy JS files
|
|
77
|
+
console.log('Copying JS loaders...')
|
|
78
|
+
const jsFiles = [
|
|
79
|
+
'core.js',
|
|
80
|
+
'util.js',
|
|
81
|
+
'browser.js',
|
|
82
|
+
'node.js',
|
|
83
|
+
'browser-inline.js',
|
|
84
|
+
'node-inline.js',
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
for (const f of jsFiles) {
|
|
88
|
+
copyFileSync(join(SRC_JS, f), join(DIST, f))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log('Build complete!')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Build the root package first
|
|
5
|
+
npm run build
|
|
6
|
+
|
|
7
|
+
for dir in examples/*; do
|
|
8
|
+
if [ -d "$dir" ]; then
|
|
9
|
+
echo "--- Testing $dir ---"
|
|
10
|
+
cd "$dir"
|
|
11
|
+
npm install
|
|
12
|
+
npm run build:wasm
|
|
13
|
+
if npm run test --if-present; then
|
|
14
|
+
echo "Tests passed for $dir"
|
|
15
|
+
fi
|
|
16
|
+
if npm run demo --if-present; then
|
|
17
|
+
echo "Demo passed for $dir"
|
|
18
|
+
fi
|
|
19
|
+
if [ -d "tests" ] && [ -f "playwright.config.js" ]; then
|
|
20
|
+
npx playwright install chromium
|
|
21
|
+
npm run test:pw
|
|
22
|
+
fi
|
|
23
|
+
cd ../..
|
|
24
|
+
fi
|
|
25
|
+
done
|