@napi-rs/cli 3.9.1 → 3.10.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/README.md +17 -4
- package/dist/cli.js +1490 -178
- package/dist/index.cjs +1490 -178
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +1490 -178
- package/docs/wasi.md +318 -4
- package/package.json +3 -3
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +3963 -9
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/build-regressions.spec.ts +210 -3
- package/src/api/__tests__/build.spec.ts +2139 -3
- package/src/api/__tests__/create-npm-dirs.spec.ts +101 -0
- package/src/api/__tests__/pre-publish.spec.ts +153 -1
- package/src/api/__tests__/templates.spec.ts +1247 -0
- package/src/api/build.ts +637 -31
- package/src/api/create-npm-dirs.ts +21 -9
- package/src/api/pre-publish.ts +29 -1
- package/src/api/templates/binding-target.ts +176 -0
- package/src/api/templates/index.ts +1 -0
- package/src/api/templates/js-binding.ts +48 -3
- package/src/api/templates/load-wasi-template.ts +641 -41
- package/src/utils/__tests__/reconciliation.spec.ts +676 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/misc.ts +347 -74
- package/src/utils/typegen.ts +367 -12
package/docs/wasi.md
CHANGED
|
@@ -40,6 +40,121 @@ Without `NAPI_RS_WASI_FLAVOR`, existing behavior is unchanged.
|
|
|
40
40
|
lazy native fallback, while `NAPI_RS_FORCE_WASI=error` requires some generated
|
|
41
41
|
WASI flavor to load.
|
|
42
42
|
|
|
43
|
+
## Identifying the loaded artifact
|
|
44
|
+
|
|
45
|
+
Every generated loader exports `__napiBindingTarget`, a string naming the
|
|
46
|
+
artifact that actually loaded:
|
|
47
|
+
|
|
48
|
+
| value | artifact |
|
|
49
|
+
| ----------------- | -------------------------- |
|
|
50
|
+
| `'native'` | a `.node` addon |
|
|
51
|
+
| `'wasm32-wasi'` | the threaded WASI flavor |
|
|
52
|
+
| `'wasm32-wasip1'` | the threadless WASI flavor |
|
|
53
|
+
|
|
54
|
+
The WASI values are the same flavor identities `NAPI_RS_WASI_FLAVOR` accepts,
|
|
55
|
+
so a pinned flavor round-trips:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
process.env.NAPI_RS_WASI_FLAVOR = 'wasm32-wasip1'
|
|
59
|
+
const binding = require('<package>')
|
|
60
|
+
binding.__napiBindingTarget // 'wasm32-wasip1'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Remember that `wasm32-wasi` is the _threaded_ flavor; see the target aliases at
|
|
64
|
+
the top of this page.
|
|
65
|
+
|
|
66
|
+
The root Node.js entry sets the value from the fallback candidate it resolved,
|
|
67
|
+
not from anything the WASI loader reports, so it is correct even for a loader
|
|
68
|
+
that fails to initialize its own exports. The one exception is
|
|
69
|
+
`NAPI_RS_NATIVE_LIBRARY_PATH`: that override can point at a generated WASI
|
|
70
|
+
loader, so the root entry adopts the `__napiBindingTarget` the required module
|
|
71
|
+
reports and falls back to `'native'` when it reports none.
|
|
72
|
+
|
|
73
|
+
Each flavor's own loaders (the CommonJS loader, the browser loader and the
|
|
74
|
+
deferred `./workerd` loader) carry their own fixed flavor identity, and they
|
|
75
|
+
carry it on the binding object they hand out — not only as a module export. So
|
|
76
|
+
the browser loader's default export, `instantiate()`'s result and
|
|
77
|
+
`createInstance().exports` all answer `__napiBindingTarget`, which is what the
|
|
78
|
+
generated declarations promise:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import { instantiate } from '<package>/workerd'
|
|
82
|
+
const binding = await instantiate(wasmModule)
|
|
83
|
+
binding.__napiBindingTarget // 'wasm32-wasip1'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
An addon that seals or freezes its exports in a `#[napi(module_exports)]` hook
|
|
87
|
+
makes the loader skip the stamp on the binding object rather than fail the load,
|
|
88
|
+
and what survives that skip follows the entry point. The browser and the
|
|
89
|
+
deferred `./workerd` entries go on reporting the flavor from their module-level
|
|
90
|
+
`__napiBindingTarget` export; only the copy on the binding object they hand out
|
|
91
|
+
is missing. The CommonJS entries hand back the binding object itself as
|
|
92
|
+
`module.exports`, so there `__napiBindingTarget` reads `undefined` —
|
|
93
|
+
deliberately, because failing an otherwise successful load over a metadata
|
|
94
|
+
string is the worse trade.
|
|
95
|
+
|
|
96
|
+
The CommonJS loaders assign the stamp helper's return value
|
|
97
|
+
(`module.exports.__napiBindingTarget = __napiStampBindingTarget(...)`) rather
|
|
98
|
+
than calling it as a statement, so `cjs-module-lexer` — Node's CommonJS-to-ESM
|
|
99
|
+
named export detection — keeps seeing the name and
|
|
100
|
+
`import { __napiBindingTarget } from '<package>'` goes on working. On a frozen
|
|
101
|
+
binding the import still links; the value is `undefined`, matching the skipped
|
|
102
|
+
stamp.
|
|
103
|
+
|
|
104
|
+
Each loader stamps exactly once, and always in the same place: after the async
|
|
105
|
+
runtime hosts are installed — addon registration functions get the exports
|
|
106
|
+
object first, so the guard reads its final state — and inside the initialization
|
|
107
|
+
guard, so a conflict fails the load through the rollback rather than past it.
|
|
108
|
+
The CommonJS loaders additionally assign onto their own `module.exports` rather
|
|
109
|
+
than onto the addon's object, which leaves an addon accessor with a refusing
|
|
110
|
+
setter untouched; the root entry stamps before it aliases the binding, for the
|
|
111
|
+
same reason.
|
|
112
|
+
|
|
113
|
+
The name is reserved by the builds that emit a loader. `napi build` rejects an
|
|
114
|
+
export of that name it can see in the type-def metadata, but only when this
|
|
115
|
+
build writes a loader to carry it — a root loader (`--platform` without
|
|
116
|
+
`--no-js`), or a WASI flavor loader set. A plain `.node` build writes neither,
|
|
117
|
+
declares nothing, and is free to export the name itself. A name attached
|
|
118
|
+
dynamically from a `#[napi(module_exports)]` hook is invisible at build time, so
|
|
119
|
+
the loader rejects it at load with `ERR_NAPI_BINDING_TARGET_CONFLICT` instead of
|
|
120
|
+
silently overwriting it. In a WASI loader that rejection happens inside the
|
|
121
|
+
initialization boundary, so the conflict rolls the environment back — no
|
|
122
|
+
emnapi context and no `'exit'` listener survive the failed `require()`.
|
|
123
|
+
|
|
124
|
+
Use it to branch on capabilities a native addon has and a WASI build does not
|
|
125
|
+
(worker threads, blocking calls, host timers) without probing:
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
if (binding.__napiBindingTarget !== 'native') {
|
|
129
|
+
// running on WebAssembly
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
When napi-rs type generation is enabled the export is declared in the generated
|
|
134
|
+
declaration files, so the check narrows in TypeScript. Which type a declaration
|
|
135
|
+
gives it follows the entry it types:
|
|
136
|
+
|
|
137
|
+
- The **root entry**'s declaration is a literal union of `'native'` and every
|
|
138
|
+
WASI flavor napi-rs can build, not only the flavors the package itself
|
|
139
|
+
builds. `NAPI_RS_NATIVE_LIBRARY_PATH` can point the root entry at any
|
|
140
|
+
generated WASI loader, so even a package that ships only a native addon can
|
|
141
|
+
report a WASI flavor, and a narrower union would reject comparisons the
|
|
142
|
+
override can actually reach.
|
|
143
|
+
- A **flavor's own** declaration — the CommonJS and browser `.d.cts` and the
|
|
144
|
+
deferred `./workerd` `.d.ts` alike — is that one flavor's exact literal. Those
|
|
145
|
+
loaders bake their flavor in at generation time and read no override, so a
|
|
146
|
+
consumer importing a fixed artifact narrows to a single value, which is what
|
|
147
|
+
the generated declarations promise above.
|
|
148
|
+
|
|
149
|
+
Both bullets describe an extensible binding. For a sealed or frozen addon the
|
|
150
|
+
root **CommonJS** entry reports `undefined` at runtime — the skipped stamp
|
|
151
|
+
above — which its declaration does not admit. The root ESM entry is unaffected,
|
|
152
|
+
because there the export is a module-level binding the loader never stamps. A
|
|
153
|
+
flavor's own `.d.cts` literal is not exposed either: that loader publishes its
|
|
154
|
+
`Symbol.dispose` implementation with `Object.defineProperty` before it stamps,
|
|
155
|
+
and `Object.defineProperty` throws on a non-extensible object, so a sealed or
|
|
156
|
+
frozen addon fails that load well before the stamp is reached.
|
|
157
|
+
|
|
43
158
|
The root package exposes deferred workerd and Wasm entries. In a Workers
|
|
44
159
|
project built by Wrangler:
|
|
45
160
|
|
|
@@ -99,9 +214,13 @@ fresh singleton after cleanup rather than exports that are being destroyed. If
|
|
|
99
214
|
the first singleton is still initializing, cleanup waits for that initialization
|
|
100
215
|
to settle before destroying its context.
|
|
101
216
|
`createInstance()` creates an independent instance and returns
|
|
102
|
-
`{ exports, dispose }`; call and await the
|
|
103
|
-
instance is no longer needed. It consistently
|
|
104
|
-
when emnapi cleanup completes synchronously.
|
|
217
|
+
`{ exports, memory, memoryBytes, disposed, dispose }`; call and await the
|
|
218
|
+
returned `dispose()` when that instance is no longer needed. It consistently
|
|
219
|
+
returns a promise, including when emnapi cleanup completes synchronously.
|
|
220
|
+
`memoryBytes` is that instance's current linear-memory size and reads `0` once
|
|
221
|
+
disposal has completed — it is declared address space, not a host's
|
|
222
|
+
committed-memory metric, so compare it against platform telemetry rather than
|
|
223
|
+
treating it as a quota. Independent instances are not
|
|
105
224
|
automatically disposed at
|
|
106
225
|
`beforeExit`, while initializing or after success, so retained exports remain
|
|
107
226
|
usable if a listener schedules more work; their cleanup ownership stays
|
|
@@ -113,7 +232,60 @@ API without a broken import of the declaration-less root package. If
|
|
|
113
232
|
initialization fails and immediate context rollback also fails, the loader
|
|
114
233
|
retains that cleanup ownership so a later `beforeExit` pass can retry it.
|
|
115
234
|
`dispose()` still attempts those retained rollbacks when singleton cleanup
|
|
116
|
-
fails, while preserving the singleton error as the primary rejection.
|
|
235
|
+
fails, while preserving the singleton error as the primary rejection. The
|
|
236
|
+
deferred loader also exports `__napiBindingTarget` (see "Identifying the loaded
|
|
237
|
+
artifact"), typed as its exact flavor.
|
|
238
|
+
|
|
239
|
+
A second argument to `createInstance()` selects that instance's linear memory:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
const instance = await createInstance(wasmModule, {
|
|
243
|
+
initialMemoryPages: 1024,
|
|
244
|
+
maximumMemoryPages: 65536,
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
or hand it one you allocated yourself:
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
const memory = new WebAssembly.Memory({ initial: 1024, maximum: 65536 })
|
|
252
|
+
const instance = await createInstance(wasmModule, { memory })
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`memory` and the page options are mutually exclusive. The generated
|
|
256
|
+
declaration models that as a union — `WasiInstanceOptions` is
|
|
257
|
+
`WasiCallerMemoryOptions | WasiAllocatedMemoryOptions`, each form declaring the
|
|
258
|
+
other form's properties as `never` — so TypeScript rejects an option bag mixing
|
|
259
|
+
the two before the loader throws. Page counts go straight to
|
|
260
|
+
`new WebAssembly.Memory`, so the engine's own bounds and messages apply. A
|
|
261
|
+
caller-provided `WebAssembly.Memory` must be unshared — this loader has no
|
|
262
|
+
threads, and shared growth does not detach, so external views handed to the
|
|
263
|
+
addon would silently outlive the bytes they describe — and it must come from the
|
|
264
|
+
**loader's own realm**. The layers underneath it (`WASI.setMemory` in
|
|
265
|
+
`@napi-rs/wasm-runtime`, and emnapi) identify a Memory with a realm-local
|
|
266
|
+
`instanceof`, so a genuine Memory built in a `node:vm` context or another frame
|
|
267
|
+
is rejected up front with a `TypeError` rather than failing somewhere inside
|
|
268
|
+
initialization.
|
|
269
|
+
|
|
270
|
+
Every Memory an instance runs on is **single-use**, the one the loader allocates
|
|
271
|
+
for you included: once a validated initialization attempt begins, passing the
|
|
272
|
+
same Memory again throws, including after that attempt fails, after the instance
|
|
273
|
+
is disposed, and when it is the `memory` a previous handle published. A failed
|
|
274
|
+
initialization may already have written into linear memory, so those bytes are
|
|
275
|
+
not a clean slate, and two live instances on one Memory would each overwrite the
|
|
276
|
+
emnapi and WASI state the other is still running on. A rejected option bag — a
|
|
277
|
+
foreign Memory, a shared one, `memory` together with the page options — claims
|
|
278
|
+
nothing, so the same Memory is still usable once the call is corrected. The
|
|
279
|
+
claim is tracked per evaluated loader module; two independently bundled copies
|
|
280
|
+
of the loader in one isolate do not see each other's claims.
|
|
281
|
+
|
|
282
|
+
`WASM_MEMORY` exports the descriptor compiled into the loader — `initialPages`,
|
|
283
|
+
`maximumPages`, `pageBytes`, `initialBytes`, `maximumBytes` — so a caller can
|
|
284
|
+
size its own Memory from it. `getDeferredRuntimeStats()` reports
|
|
285
|
+
`{ createdInstances, liveInstances, declaredInitialMemoryBytes }` for instances
|
|
286
|
+
created by that loader module evaluation, not process-wide; `liveInstances`
|
|
287
|
+
drops when an instance's `dispose()` resolves, so a `dispose()` that throws
|
|
288
|
+
leaves it counted and retryable.
|
|
117
289
|
|
|
118
290
|
`Context.destroy()` is synchronous in emnapi's public contract. The deferred
|
|
119
291
|
loader also contains nonconforming promise-like results defensively. Keep and
|
|
@@ -127,6 +299,30 @@ Replacement `instantiate()` calls wait for the complete public cleanup,
|
|
|
127
299
|
including every retained failed-initialization rollback present before cleanup
|
|
128
300
|
finishes.
|
|
129
301
|
|
|
302
|
+
Every generated loader shadows `destroy` on the emnapi context it creates, so
|
|
303
|
+
`napi_prepare_wasm_env_cleanup` runs before the environment stops accepting
|
|
304
|
+
JavaScript calls even when `destroy()` is invoked directly — by an embedder
|
|
305
|
+
holding the context, by a test harness, or by emnapi's `beforeExit` auto-destroy
|
|
306
|
+
on a host where `suppressDestroy()` is unavailable. The shadow is best-effort: a
|
|
307
|
+
context whose `destroy` cannot be read or redefined is used unchanged. It does
|
|
308
|
+
not replace `dispose()`, which additionally yields event-loop turns until
|
|
309
|
+
`napi_wasm_env_cleanup_pending` reports zero; a direct `destroy()` still cannot
|
|
310
|
+
wait for a settlement produced on another thread.
|
|
311
|
+
|
|
312
|
+
The barrier settles the promises it cancels synchronously, so a promise hook
|
|
313
|
+
(`node:v8` `promiseHooks`, or the `async_hooks` hook `AsyncLocalStorage`
|
|
314
|
+
installs) can run while it is still in flight. A `destroy()` called from such a
|
|
315
|
+
hook is a no-op — the frame that started the barrier destroys as soon as it
|
|
316
|
+
returns, and `Context.destroy()` returns `void`, so nothing observable is lost.
|
|
317
|
+
|
|
318
|
+
`dispose()` is the one frame that does not destroy the moment the barrier
|
|
319
|
+
returns: it yields for the settlement drain first. So a `dispose()` called from
|
|
320
|
+
such a hook joins the disposal already running instead of starting a second
|
|
321
|
+
one — every loader publishes its disposal promise before the barrier runs. A
|
|
322
|
+
second frame would otherwise reach the context destroyer while the first is
|
|
323
|
+
still parked in its drain, and the no-op above would be recorded there as a
|
|
324
|
+
completed destroy, leaving the context retained with its cleanup hooks unrun.
|
|
325
|
+
|
|
130
326
|
The eager CommonJS WASI loader keeps its emnapi context alive for the process
|
|
131
327
|
lifetime. Node.js can emit `beforeExit` repeatedly when a listener schedules
|
|
132
328
|
more work, and cached eager exports must remain usable after every such cycle.
|
|
@@ -175,6 +371,88 @@ override the default in either direction:
|
|
|
175
371
|
}
|
|
176
372
|
```
|
|
177
373
|
|
|
374
|
+
## Shared async runtime hosts
|
|
375
|
+
|
|
376
|
+
`CurrentThread` is the only async-runtime flavor on WebAssembly, for both
|
|
377
|
+
`wasm32-wasip1` and `wasm32-wasip1-threads`. An addon built with the
|
|
378
|
+
`napi-async-runtime` crate therefore makes no progress until a JavaScript task
|
|
379
|
+
host publishes its runnable turns, and its timers never fire until a timer host
|
|
380
|
+
relays them. Set `napi.wasm.asyncRuntime` and the generated loaders install
|
|
381
|
+
both for you:
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"napi": {
|
|
386
|
+
"wasm": {
|
|
387
|
+
"asyncRuntime": true
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
This affects WASI output only. Native `.node` bindings run the `MultiThread`
|
|
394
|
+
flavor on real threads and need no JavaScript host.
|
|
395
|
+
|
|
396
|
+
With the flag on:
|
|
397
|
+
|
|
398
|
+
- The Node CommonJS and browser loaders call
|
|
399
|
+
`installCurrentThreadHosts(exports)` from `@napi-rs/async-runtime` right after
|
|
400
|
+
instantiation, and unregister both hosts before the emnapi context is
|
|
401
|
+
destroyed — on `dispose()`, on the initialization rollback, and at process
|
|
402
|
+
`exit`.
|
|
403
|
+
- The deferred `./workerd` loader registers one task host and one timer host
|
|
404
|
+
**per instance** (`registerWorkerdCurrentThreadTaskHost` /
|
|
405
|
+
`registerWorkerdTimerHost`) and disposes them with that instance, so
|
|
406
|
+
independent instances never share or cancel each other's registrations. It
|
|
407
|
+
imports them from the `@napi-rs/async-runtime/workerd` subpath rather than the
|
|
408
|
+
package root: the root entry also pulls in the Node-lane relay
|
|
409
|
+
(the realm-global installation registry and its timer-handle bookkeeping),
|
|
410
|
+
which a worker bundle never runs and which a CommonJS entry cannot be
|
|
411
|
+
tree-shaken out of. Both entries are equally isolate-safe — neither touches a
|
|
412
|
+
`node:` builtin or `process` — so the subpath is purely about bundle size.
|
|
413
|
+
- The generated `<packageName>-wasm32-*` packages declare
|
|
414
|
+
`@napi-rs/async-runtime` as a dependency. Add it to your own
|
|
415
|
+
`devDependencies` so local `napi build` output can load.
|
|
416
|
+
|
|
417
|
+
`napi pre-publish` requires that dependency in every WASI package whose loaders
|
|
418
|
+
import it, so a project that turned the flag on after scaffolding must rerun
|
|
419
|
+
`napi create-npm-dirs` — the root `devDependencies` entry hides a stale
|
|
420
|
+
manifest locally, but consumers of the published package would fail at load.
|
|
421
|
+
|
|
422
|
+
Detection is done at runtime against the instantiated module:
|
|
423
|
+
`@napi-rs/async-runtime` reads the seven host exports off the binding, checks
|
|
424
|
+
the task-host contract version (`4`), validates the reservation identity and
|
|
425
|
+
the liveness probe, and rolls back every registration it created if any step
|
|
426
|
+
fails. A binding that does not actually expose the contract therefore fails at
|
|
427
|
+
load with `ERR_NAPI_ASYNC_RUNTIME_BINDING_MISMATCH` rather than hanging later.
|
|
428
|
+
|
|
429
|
+
`napi build` cross-checks the flag against the same seven names up front, but it
|
|
430
|
+
reads them from the type definitions, so the check needs the `type-def` feature
|
|
431
|
+
of `napi-derive`. Without it there is no export list to check against: the build
|
|
432
|
+
prints a warning and leaves the verdict to the runtime detection above.
|
|
433
|
+
|
|
434
|
+
The bootstrap runs after instantiation, so it runs after `#[module_init]` and
|
|
435
|
+
any other Rust code that executes during module registration: the host
|
|
436
|
+
registration functions are exports of the binding itself and do not exist
|
|
437
|
+
before registration completes. Code that runs during registration must not
|
|
438
|
+
create timers (`sleep_until` fails loud at creation when no timer host is
|
|
439
|
+
registered) and cannot expect task progress until the loader has returned.
|
|
440
|
+
Register the runtime backend there; create tasks and timers from exports that
|
|
441
|
+
run later.
|
|
442
|
+
|
|
443
|
+
The flag itself is still needed because the loaders `import` the package with a
|
|
444
|
+
bare specifier: bundlers resolve static imports at build time, so an optional
|
|
445
|
+
one is not expressible. Leave it unset and every generated loader is byte-for-
|
|
446
|
+
byte what earlier CLI versions produced.
|
|
447
|
+
|
|
448
|
+
Only the loader's own thread is bootstrapped. WASI worker threads
|
|
449
|
+
(`wasi-worker.mjs`, `wasi-worker-browser.mjs`) instantiate the module with
|
|
450
|
+
`childThread: true` and do not register a host.
|
|
451
|
+
|
|
452
|
+
`napi build` cross-checks the flag against the addon's real export list: it
|
|
453
|
+
fails when the flag is set but the host exports are missing, and warns when the
|
|
454
|
+
exports are present but the flag is not set.
|
|
455
|
+
|
|
178
456
|
`napi.wasm.initialMemory` is measured in 64 KiB WebAssembly pages. The regular
|
|
179
457
|
Node and browser loaders retain the historical 4,000-page (250 MiB) default.
|
|
180
458
|
The deferred `./workerd` loader defaults to 1,024 pages (64 MiB), leaving
|
|
@@ -182,6 +460,42 @@ headroom under workerd's 128 MiB isolate limit. An explicit
|
|
|
182
460
|
`napi.wasm.initialMemory` value applies to every loader, so keep it within the
|
|
183
461
|
target isolate's limit after measuring the addon's actual requirements.
|
|
184
462
|
|
|
463
|
+
`napi.wasm.threadlessInitialMemory` overrides that value for the threadless
|
|
464
|
+
(`wasm32-wasip1`) loaders only — the Node CJS loader, the browser loader, and
|
|
465
|
+
the deferred `./workerd` loader. The two flavors want different floors:
|
|
466
|
+
|
|
467
|
+
- The threaded loader allocates one `shared: true` memory and hands it to every
|
|
468
|
+
wasi-threads worker, so every worker stack and every thread's allocations are
|
|
469
|
+
carved out of it and growing it is a cross-thread event. Browser builds
|
|
470
|
+
pre-create `asyncWorkPoolSize + hardwareConcurrency` workers, so this is
|
|
471
|
+
sized for the whole pool up front.
|
|
472
|
+
- The threadless loader has one thread and a plain growable `ArrayBuffer`. Its
|
|
473
|
+
only hard floor is the module's own `env.memory` minimum — the link-time
|
|
474
|
+
`-zstack-size` plus static data, both fixed by `napi-build` — and it grows on
|
|
475
|
+
demand. That is what lets the same addon fit a host with a hard isolate cap.
|
|
476
|
+
|
|
477
|
+
```json
|
|
478
|
+
{
|
|
479
|
+
"napi": {
|
|
480
|
+
"wasm": {
|
|
481
|
+
"initialMemory": 16384,
|
|
482
|
+
"threadlessInitialMemory": 1027,
|
|
483
|
+
"maximumMemory": 65536
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Without the key the threadless loaders keep following `napi.wasm.initialMemory`,
|
|
490
|
+
so nothing changes for an existing project.
|
|
491
|
+
|
|
492
|
+
Both values must be integers in `1..=65536` pages — memory32 tops out at 4 GiB,
|
|
493
|
+
which is also the `--max-memory` the WASI link uses — and neither may exceed
|
|
494
|
+
`napi.wasm.maximumMemory`. `napi build` fails the WASI target otherwise. An
|
|
495
|
+
`initial` _below_ the module's own `env.memory` minimum is **not** caught by the
|
|
496
|
+
CLI: it surfaces as a `LinkError` at instantiation, so re-measure whenever the
|
|
497
|
+
addon's static data or stack size grows.
|
|
498
|
+
|
|
185
499
|
Threaded browser loaders always pre-create a pool of wasi-threads workers at
|
|
186
500
|
module initialization, sized as `asyncWorkPoolSize + hardwareConcurrency`
|
|
187
501
|
(logical cores, floored at 2, with a fallback for privacy-fuzzed values),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@napi-rs/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "Cli tools for napi-rs",
|
|
5
5
|
"author": "LongYinan <lynweklm@gmail.com>",
|
|
6
6
|
"homepage": "https://napi.rs/",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"colorette": "^2.0.20",
|
|
70
70
|
"es-toolkit": "^1.47.0",
|
|
71
71
|
"js-yaml": "^5.0.0",
|
|
72
|
-
"obug": "^
|
|
72
|
+
"obug": "^3.0.0",
|
|
73
73
|
"semver": "^7.8.2",
|
|
74
74
|
"typanion": "^3.14.0",
|
|
75
75
|
"typescript": "^6.0.3"
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"emnapi": "^2.0.0-alpha.4",
|
|
88
88
|
"empathic": "^2.0.1",
|
|
89
89
|
"env-paths": "^4.0.0",
|
|
90
|
-
"oxc-parser": "^0.
|
|
90
|
+
"oxc-parser": "^0.150.0",
|
|
91
91
|
"tsdown": "^0.23.0",
|
|
92
92
|
"tslib": "^2.8.1"
|
|
93
93
|
},
|