Each subdirectory is one engine variant of the NativeScript Windows app runtime, published as
@nativescript/windows-<engine>. A variant is the same framework as @nativescript/windows
(the WinUI 3 app template, dotnet-bridge, and tools) — only the runtime DLL differs. All of
runtime/src/napi_engine/* is engine-neutral and reused verbatim; a package supplies the engine +
its Node-API provider + a thin adapter that exposes the runtime DLL's C ABI over that engine.
@nativescript/windows and @nativescript/windows-<engine> are drop-in replacements: an app swaps
the dependency and rebuilds, and it runs unchanged on the other engine. This works because every
variant ships the same framework and a nativescript.dll that exposes the same C ABI the
WinUI 3 host P/Invokes (runtime_init / runtime_runscript / runtime_pump_timers / …). The
package metadata is uniform too — same files, and a nativescript: { runtime, engine } block —
so tooling resolves any of them the same way.
Producing a variant is a build flag, not a separate template (see Building below):
template/build.ps1 # @nativescript/windows (classic V8, default)
template/build.ps1 -Engine quickjs # @nativescript/windows-quickjsSame convention as @nativescript/android / @nativescript/ios:
@nativescript/windows— the classic V8 app runtime (theruntimecrate + rusty_v8, default features), built via thenativescriptcdylib. The current/default runtime.@nativescript/windows-<engine>— the engine variants in this dir (-quickjs,-hermes,-v8,-jsc), each a napi-backed runtime. Like@nativescript/android-jsc. They build theruntimecrate without its defaultclassicfeature (the rusty_v8 engine), so only-v8links V8, as its engine.@nativescript/windows-napi— a separate consumption mode (the.nodein../windows-napi): run WinRT interop from an existing Node/Bun/Deno host. Not an app runtime; Windows-specific.
| Package | npm | Engine binary (Windows) | napi in binary? |
|---|---|---|---|
windows-quickjs |
@nativescript/windows-quickjs |
compiled from source (quickjs-ng + napi-android shim) | shim (compiled) |
windows-hermes |
@nativescript/windows-hermes |
prebuilt hermes.dll (Microsoft.JavaScript.Hermes NuGet) |
✅ yes (napi+jsr) |
windows-v8 |
@nativescript/windows-v8 |
in-tree: rusty_v8 (V8 14.7, prebuilt static lib + headers) | napi-android v8 shim, ported to V8 14.7 |
windows-jsc |
@nativescript/windows-jsc |
Playwright's webkit-win64 JavaScriptCore.dll (current; buildbot is dead) |
napi-android JSC shim over the public C API (+ 2 Win fixes) |
All four supported engines run the full WinRT runtime with no Node. (PrimJS was dropped — its
concurrent GC is hard-coded to POSIX mmap/pthreads and it has no Windows build.)
Prebuilt-availability notes (checked Jul 2026): V8 engine is already in-tree (rusty_v8). JSC:
the WebKit WinCairo buildbot is dead (last Windows build Sept 2024) and the microsoft.javascript.jsc
/ javascriptcore NuGet ids are 1-byte placeholders — the current source is Playwright's
webkit-win64 build (rebuilt continuously; ships JavaScriptCore.dll + ICU). microsoft.chakracore
(1.11.x) is a real Windows engine but JSRT, not napi; openkraken/jsc is Android/macOS only.
packages/windows-<engine>/
Cargo.toml # excluded from the workspace (C build / prebuilt link)
build.rs # compile engine + shim, or link the prebuilt + copy DLLs
src/lib.rs # engine bindings + `abi` module (the nativescript.dll C ABI)
src/abi.rs # engine → runtime-DLL adapter (host_dll feature); see below
src/main.rs # standalone host: bring up napi_env → napi_engine → run app
vendor/ # committed engine binaries (android's model: in-repo, no LFS)
package.json # @nativescript/windows-<engine> (same contract as classic)
framework/ # generated by the engine build — not committed (see .gitignore)
README.md
Shared host code (native crash reporter, demo harness) is in packages/common
(ns-windows-common).
The WinUI 3 host loads framework/libs/<arch>/nativescript.dll and P/Invokes its C ABI
(runtime_init, runtime_deinit, runtime_runscript, runtime_pump_timers,
runtime_notify_app_event, runtime_set_local_folder, runtime_get_last_js_error /
runtime_free_js_error, runtime_install_ctrlc_handler, runtime_has_devtools). The classic
runtime implements this in the nativescript crate over rusty_v8. Each engine variant implements
the same ABI in src/abi.rs, compiled into the package's cdylib under the host_dll feature.
The engine-neutral work — WinRT init, installing the globals + Windows namespace, and one turn of
the event loop — is shared in runtime::napi_engine::host_abi (initialize_runtime / pump_once). An
adapter only wires the three engine-specific pieces on top: create the napi_env (the engine's
shim), evaluate a script, and drain microtasks. windows-quickjs/src/abi.rs is the reference
implementation; the other engines follow the identical shape with their own shim.
Build status: all four per-engine
nativescript.dllcdylibs have been built and validated here (cargo build --features host_dllfor each package). Each was loaded by a foreign .NET host — a C# program that P/Invokes the runtime C ABI exactly like the WinUI 3 app — whereruntime_initsucceeds,runtime_runscriptruns JS, and a real WinRT round-trip (Windows.Data.Json→{"a":7}) works. This required one Windows-port fix: the napi engine DLLs resolve theirnapi_*provider from the loaded module (the.exeOR the cdylib) rather than always the process.exe— seepackages/vendor/napi-sys(a[patch.crates-io]in each engine package). The classic runtime uses rusty_v8 (no napi-sys) and never hit this. Building a cdylib still needs the engine toolchain + vendored engine binaries (each package is excluded from the workspace), so a plain workspacecargo builddoes not compile them.
nativescript-windows path/to/app.js # e.g. packages/test/test-app.jspackages/test/interop-check.js exercises the shared NSWinRT.interop surface (typed-value/
reference boxing, Pointer/OutParam, buffer + DateTime helpers) and is expected to pass on
all four engines, as is packages/test/enum-check.js (generics closed over an enum:
IAsyncOperation<Enum>, IVector<Enum>, IReference<Enum>). With a script argument the host
runs it and then drives the standalone event loop
(runtime::napi_engine::event_loop) until the app goes idle: Windows message pump (STA WinRT
async completions, delegate invokes), setTimeout/setInterval timers
(napi_engine::timers, install-if-missing), and the engine's microtask drain. The prelude
(packages/common/src/prelude.rs) provides queueMicrotask and NSWinRT.toPromise(op);
toPromise ref-counts the loop via the __nsLoopRetain/__nsLoopRelease natives, so awaited
WinRT operations keep the process alive exactly until they settle. Without a script argument the
host runs its staged demo (including an async event loop stage) and exits.
Per-engine loop notes: Hermes is created with jsr_config_set_explicit_microtasks (bare hosts
have no setImmediate for its RN-style promise scheduling); V8/JSC ship inspector-only inert
console objects that the hosts delete so the runtime console installs; microtask drain hooks
are qjs_execute_pending_jobs / js_execute_pending_jobs / jsr_drain_microtasks.
The framework is built by template/build.ps1; -Engine selects the runtime DLL. It's the same
template for every engine — only framework/libs/ differs.
# Classic V8 — stages into template/framework (x64 + arm64, release + devtools).
template/build.ps1
# An engine variant — copies the shared scaffolding from template/framework, builds the engine
# cdylib as framework/libs/x64/nativescript.dll, and stages it into packages/windows-<engine>/framework.
# (Build classic once first so the shared scaffolding exists to copy. Variants are x64, no devtools.)
template/build.ps1 -Engine quickjs
template/build.ps1 -Engine hermes
template/build.ps1 -Engine jsc # also needs a real JavaScriptCore.lib in windows-jsc/vendorEach variant's package.json build script is just that invocation, so npm run build inside a
package produces its framework. Prebuilt engine binaries are committed in each package's vendor/
(provenance + refresh in each vendor/README.md), the same way napi-android commits its libs/.
@nativescript/windows + @nativescript/windows-<engine> — app runtimes: build the framework
for the engine (above), then npm publish the package (each is os:["win32"], cpu:["x64"]; the
classic package also ships arm64 + devtools). The CLI picks one, like android's jsEngine — and
because the packages are interchangeable, switching is a dependency swap.
@nativescript/windows-napi — the Node/Bun/Deno .node (../windows-napi): released by
.github/workflows/npm_release.yml with the packages above (engine: napi, also part of all).
The build runs napi build --platform --release --target <triple> for x86_64-pc-windows-msvc
and aarch64-pc-windows-msvc and packs both windows.<triple>.node files into the one package;
the generated loader picks the one for the running CPU. .github/workflows/windows-napi.yml
builds both triples and runs the test suite on pull requests. index.js/index.d.ts/
package-lock.json are generated but committed.