Small and modular JavaScript runtime for desktop and mobile. Like Node.js, it provides an asynchronous, event-driven architecture for writing applications in the lingua franca of modern software. Unlike Node.js, it makes embedding and cross-device support core use cases, aiming to run just as well on your phone as on your laptop. The result is a runtime ideal for networked, peer-to-peer applications that can run on a wide selection of hardware.
npm i -g barebare [flags] [filename] [...args]
Evaluate a script or start a REPL session if no script is provided.
Arguments:
[filename] Optional. The name of a script to evaluate
[...args] Additional arguments made available to the script
Flags:
--version|-v Print the Bare version
--eval|-e <script> Evaluate an inline script
--print|-p <script> Evaluate an inline script and print the result
--inspect Activate the inspector
--inspect-port <port> Configure the port on which the inspector will run (default: 9229)
--expose-gc Expose garbage collection APIs
--help|-h Show helpThe specified <script> or <filename> is run using Module.load(). For more information on the module system and the supported formats, see https://github.com/holepunchto/bare-module.
Bare is built on top of https://github.com/holepunchto/libjs, which provides low-level bindings to V8 in an engine independent manner, and https://github.com/libuv/libuv, which provides an asynchronous I/O event loop. Bare itself only adds a few missing pieces on top to support a wider ecosystem of modules:
- A module system supporting both CJS and ESM with bidirectional interoperability between the two.
- A native addon system supporting both statically and dynamically linked addons.
- Light-weight threads with synchronous joins and
SharedArrayBuffersupport.
Everything else if left to userland modules to implement using these primitives, keeping the runtime itself succinct and bare. By abstracting over both the underlying JavaScript engine using libjs and platform I/O operations using libuv, Bare allows module authors to implement native addons that can run on any JavaScript engine that implements the libjs ABI and any system that libuv supports.
Bare is designed to be embedded alongside code the embedder may not fully trust, and Bare.Addon.seal() is the mechanism for freezing the set of native code a process may load. What that does and does not promise is written down in docs/threat-model.md, which embedders should read before running untrusted JavaScript.
The core JavaScript API of Bare is available through the global Bare namespace.
The identifier of the operating system for which Bare was compiled. The possible values are android, darwin, ios, linux, and win32.
The identifier of the processor architecture for which Bare was compiled. The possible values are arm, arm64, ia32, mips, mipsel, riscv64, and x64.
The command line arguments passed to the process when launched.
The ID of the current process.
The code that will be returned once the process exits. If the process is exited using Bare.exit() without specifying a code, Bare.exitCode is used.
The Bare version string.
An object containing the version strings of Bare and its dependencies.
Immediately terminate the process or current thread with an exit status of code which defaults to Bare.exitCode.
Suspend the process and all threads. This will emit a suspend event signalling that all work should stop immediately. When all work has stopped and the process would otherwise exit, an idle event will be emitted. If the process is not resumed from an idle event listener, the loop will block until the process is resumed.
Wake the process and all threads during suspension. This will emit a wakeup event signalling that work may be performed until deadline is reached.
Immediately suspend the event loop and trigger the idle event.
Resume the process and all threads after suspension. This can be used to cancel suspension after the suspend event has been emitted and up until all idle event listeners have run.
Emitted when a JavaScript exception is thrown within an execution context without being caught by any exception handlers within that execution context. By default, uncaught exceptions are printed to stderr and the processes aborted. Adding an event listener for the uncaughtException event overrides the default behavior.
Emitted when a JavaScript promise is rejected within an execution context without that rejection being handled within that execution context. By default, unhandled rejections are printed to stderr and the process aborted. Adding an event listener for the unhandledRejection event overrides the default behavior.
Emitted when the loop runs out of work and before the process or current thread exits. This provides a chance to schedule additional work and keep the process from exiting. If additional work is scheduled, beforeExit will be emitted again once the loop runs out of work.
If the process is exited explicitly, such as by calling Bare.exit() or as the result of an uncaught exception, the beforeExit event will not be emitted.
Emitted when the process or current thread exits. If the process is forcefully terminated from an exit event listener, the remaining listeners will not run.
Caution
Additional work MUST NOT be scheduled from an exit event listener.
Emitted when the process or current thread is suspended. Any in-progress or outstanding work, such as network activity or file system access, should be deferred, cancelled, or paused when the suspend event is emitted and no additional work should be scheduled. A suspend event listener may call Bare.resume() to cancel the suspension.
Emitted when the process or current thread wakes up during suspension. Once the process becomes idle, or if the process is not idle by the time deadline has passed, the process will suspend itself again and an idle event be emitted. A wakeup event listener may call Bare.resume() to resume the process.
Emitted when the process or current thread becomes idle after suspension. After all handlers have run, the event loop will block and no additional work be performed until the process is resumed. An idle event listener may call Bare.resume() to cancel the suspension.
Emitted when the process or current thread resumes after suspension. Deferred and paused work should be continued when the resume event is emitted and new work may again be scheduled.
stateDiagram
direction LR
[*] --> Active
Active --> Suspending: Bare.suspend()
Active --> Terminated: Bare.exit()
Active --> Exiting
Suspending --> Active: Bare.resume()
Suspending --> Awake: Bare.wakeup()
Suspending --> Suspended: Bare.idle()
Suspending --> Terminated: Bare.exit()
Suspending --> Idle
Awake --> Active: Bare.resume()
Awake --> Suspended: Bare.idle()
Awake --> Terminated: Bare.exit()
Awake --> Idle
Idle --> Suspended
Idle --> Active: Bare.resume()
Idle --> Terminated: Bare.exit()
Suspended --> Active
Suspended --> Awake: Bare.wakeup()
Terminated --> Exiting
Exiting --> [*]
The Bare.Addon namespace provides support for loading native addons, which are typically written in C/C++ and distributed as shared libraries.
Note
This is an advanced API that users should never have to interact with directly.
The target triplet identifying the current addon host.
Whether addon loading has been sealed with Addon.seal().
Seal addon loading. Once sealed, no further dynamic addons can be loaded by the current thread or any other thread of the process, now or in the future; attempting to do so throws. Statically linked addons are compiled in and remain available.
This is a one-way operation that cannot be undone for the lifetime of the process. It is intended for embedders that wish to load a fixed set of trusted addons up front and then prevent any further native code from being introduced, such as when establishing a sandbox.
The seal applies to the process alone. Embedders running several Bare processes within the same operating system process may seal each of them independently, and sealing one has no effect on the addons the others may load. Addons are likewise owned by the process that loaded them and are unloaded when it is torn down.
Sealing also freezes the context registry that embedders publish handles to, after which nothing further may be published to it.
For what sealing guarantees, what it deliberately leaves alone, and what embedders are expected to do on top of it, see docs/threat-model.md.
Load a static or dynamic native addon identified by url. If url is not a static native addon, Bare will instead look for a matching dynamic object library.
The WHATWG URL identifier of the addon.
The exports of the addon.
The Bare.Thread namespace provides support for lightweight threads. Threads are similar to workers in Node.js, but provide only minimal API surface for creating and joining threads.
Note
This is an advanced API that users should never have to interact with directly.
true if the current thread is the main thread.
A reference to the current thread as a ThreadProxy object. Will be null on the main thread.
The data that was passed to the current thread on creation. Will be null if no data was passed.
Start a new thread that will run source, which is a string or a Buffer. If callback is provided, its function body will be used as the source instead and invoked on the new thread with Thread.self.data passed as an argument.
A thread is loaded through a protocol that reaches nothing, so it runs the source it was given and no more; filename names that source rather than locating it. Anything else the thread needs, including the modules it imports, must travel with it as source or data. To run a module graph on a thread, gather it into a https://github.com/holepunchto/bare-bundle first and pass the bundle as source, which is what https://github.com/holepunchto/bare-thread does.
Important
A thread does not inherit the module protocol of whoever spawned it. Reading a graph off disk and handing it over is the spawner's job, so that a thread never reaches further than the code that started it.
Options include:
{
// Optional data to pass to the thread
data: null,
// Optional transfer list
transfer: [],
// Optional source encoding if `source` is a string
encoding: 'utf8',
// Optional stack size in bytes, pass 0 for default
stackSize: 0
}Whether or not the thread has been joined with the current thread.
Block and wait for the thread to exit.
Suspend the thread. Equivalent to calling Bare.suspend() from within the thread.
Wake the thread. Equivalent to calling Bare.wakeup() from within the thread.
Resume the thread. Equivalent to calling Bare.resume() from within the thread.
Terminate the thread. Equivalent to calling Bare.exit() from within the thread.
The Bare.IPC namespace provides support for optional streaming communication between an embedder and JavaScript code. By default, its value is null indicating that streaming communication is not supported. If set by embedders, Bare.IPC is expected to be an instance of a https://github.com/holepunchto/bare-stream Duplex stream.
Note
This is an advanced API that users should never have to interact with directly.
Bare can easily be embedded using the C API defined in include/bare.h:
#include <bare.h>
#include <uv.h>
bare_t *bare;
bare_setup(uv_default_loop(), platform, &env /* Optional */, argc, argv, options, &bare);
bare_load(bare, filename, source, &module /* Optional */);
bare_run(bare, UV_RUN_DEFAULT);
int exit_code;
bare_teardown(bare, UV_RUN_DEFAULT, &exit_code);If source is NULL, the contents of filename will instead be read at runtime. For examples of how to embed Bare on mobile platforms, see https://github.com/holepunchto/bare-android and https://github.com/holepunchto/bare-ios.
An embedder whose thread belongs to a host loop, such as the run loop of a user interface, drives the loop with bare_poll() rather than bare_run(). It runs the loop without blocking and reports how long the host may sleep before calling again:
int timeout;
bare_poll(bare, &timeout);A timeout of -1 means that the host may sleep until the backend descriptor of the loop, as given by uv_backend_fd(), becomes readable. A host that sleeps on the timeout alone rather than on the descriptor will miss work that arrives from another thread.
bare_run() attaches the process to the thread while it runs, and so does loading an addon, so native code reached through either is already attached. A call the embedder makes itself is not, so attach the process around it:
bare_t *previous;
bare_attach(bare, &previous);
js_call_function(env, receiver, fn, argc, argv, &result);
bare_detach(bare, previous);
bare_run(bare, UV_RUN_NOWAIT);Attachments nest. bare_attach() hands back the process that was attached before, which bare_detach() attaches again, so detach on the same thread and in the reverse order of attaching. Detaching out of order returns -1 and restores nothing.
Attaching does not run the loop. The call may leave work behind that only the loop will run, which is why it is run above. A process that has terminated or exited cannot be attached.
Addons sometimes need a handle that only the embedder can produce, such as a JavaVM * on Android. As an addon is only ever passed a JavaScript environment and its exports, it has no route back to the embedder that started the process. The context registry provides that by having embedders publish opaque handles under agreed keys and addons retrieve them by key.
Embedders publish with bare_context_set(), which takes an optional destructor that is called when the process is torn down. The destructor runs on the main thread once every thread has been joined and the JavaScript environment has been destroyed, so it is a place to release a handle rather than to run anything that needs the environment:
bare_t *bare;
bare_setup(uv_default_loop(), platform, &env, argc, argv, options, &bare);
bare_context_set(bare, "bare.android.jvm.v1", jvm, NULL);
bare_load(bare, filename, source, NULL);Addons retrieve with bare_context_get(), which takes no bare_t * as an addon holds none. The process is instead the one whose runtime the calling thread has entered, which is well defined for as long as an addon can be called into, including while it initialises. An embedder calling into the environment of its own accord attaches the process itself:
static js_value_t *
addon_exports(js_env_t *env, js_value_t *exports) {
void *jvm;
if (bare_context_get("bare.android.jvm.v1", &jvm) == 0) {
// Use the handle, or stash it on our per-environment state.
}
return exports;
}A missing key is an ordinary outcome rather than a fatal one, and addons are expected to degrade when the handle they wanted was not published. Being asked from the wrong thread is not, so the two are reported apart: an addon retrieving the handle from a thread of its own gets -2 rather than a missing key it would otherwise degrade over silently and for good. Retrieve and stash the handle while the addon initialises if a thread of its own is going to need it.
Stash it in the state the addon builds for the environment it was initialised with, not in a file static. A static is one slot for the whole operating system process while context is published per Bare process, so an addon that caches a handle statically and is loaded by two of them keeps whichever initialised first and runs the other on a handle that was never published to it. Nothing reports this, as each lookup on its own answers correctly.
Entries last for as long as the process and are immutable once published, which is what makes it safe to hand the pointer to an addon: setting a key that is already taken fails rather than replacing it, and there is no way to withdraw one, as an addon that has been handed a pointer has no way of hearing that it went away. An embedder that needs a handle to change publishes the change under a key of its own and lets the addon go looking.
Publish everything the addons of the process need before the first bare_load(). An addon only ever sees what was published before it was loaded, and addons are loaded when the module graph first reaches them rather than at a point the embedder can predict, so publishing any later does not merely risk being late: it is seen by some addons and not others, and by some threads and not others, depending on the shape of the graph. Nothing reports this, which is why the rule is to publish up front.
The registry is scoped to the Bare process, like addons are. Embedders running several Bare processes within the same operating system process publish to each of them separately, and an addon loaded by two of them sees what each published and nothing of the other. Sealing with bare_seal() or Addon.seal() freezes the registry along with the addons, after which nothing further may be published. The seal is a single process-wide flag, so neither half can be sealed without the other.
Keys are compared by their contents and are namespaced by whoever owns the handle, with the version in the key rather than in the value so that an addon needing a different contract asks for a different key. The bare. namespace is Bare's own and is where the handles that Bare and its addons agree on live; anyone else should pick a namespace of their own. The keys in use are listed in docs/context-keys.md, which is also where a new one is written down.
Note
A handle is a power, and publishing one grants it to every addon in the process rather than to the one you had in mind. See docs/threat-model.md before publishing anything.
Bare provides a mechanism for implementing process suspension, which is needed for platforms with strict application lifecycle constraints, such as mobile platforms. When suspended, using either bare_suspend() from C or Bare.suspend() from JavaScript, a suspend event will be emitted on the Bare namespace. Then, when the loop has no work left and would otherwise exit, an idle event will be emitted and the loop blocked, keeping it from exiting. When the process is later resumed, using either bare_resume() from C or Bare.resume() from JavaScript, a resume event will be emitted and the loop unblocked, allowing it to exit when no work is left.
While suspended, the loop may also be woken up for limited periods of time to perform work, using either bare_wakeup() from C or Bare.wakeup() from JavaScript, which will emit a wakeup event. Each wakeup has an associated deadline after which the loop will be stopped and the process suspended again, emitting another idle event.
https://github.com/holepunchto/bare-make is used for compiling Bare. Start by installing the tool globally:
npm i -g bare-makeNext, install the required build and runtime dependencies:
npm iThen, generate the build system:
bare-make generateThis only has to be run once per repository checkout. When updating bare-make or your compiler toolchain it might also be necessary to regenerate the build system. To do so, run the command again with the --no-cache flag set to disregard the existing build system cache:
bare-make generate --no-cacheWith a build system generated, Bare can be compiled:
bare-make buildWhen completed, the bare(.exe) binary will be available in the build/bin directory and the libbare.(a|lib) and (lib)bare.(dylib|dll|lib) libraries will be available in the root of the build directory.
When linking against the static libbare.(a|lib) library, make sure to use whole archive linking as Bare relies on constructor functions for registering native addons. Without whole archive linking, the linker will remove the constructor functions as they aren't referenced by anything.
Bare provides a few compile options that can be configured to customize various aspects of the runtime. Compile options may be set by passing the --define option=value flag to the bare-make generate command when generating the build system.
Warning
The compile options are not covered by semantic versioning and are subject to change without warning.
| Option | Default | Description |
|---|---|---|
BARE_ENGINE |
github:holepunchto/libjs |
The JavaScript engine to use |
BARE_PREBUILDS |
ON |
Enable prebuilds for supported third-party dependencies |
BARE_MEMORY_LIMIT |
0 |
The default memory limit of each JavaScript heap |
Bare uses a tiered support system to manage expectations for the platforms that it targets. Targets may move between tiers between minor releases and as such a change in tier will not be considered a breaking change.
Tier 1: Platform targets for which prebuilds are provided as defined by the .github/workflows/prebuild.yml workflow. Compilation and test failures for these targets will cause workflow runs to go red.
Tier 2: Platform targets for which Bare is known to work, but without automated compilation and testing. Regressions may occur between releases and will be considered bugs.
Note
Development happens primarily on Apple hardware with Linux and Windows systems running as virtual machines.
| Platform | Architecture | Version | Tier | Notes |
|---|---|---|---|---|
| Linux | arm64 |
>= Linux 5.15, >= GNU C Library 2.35 | 1 | Ubuntu 22.04, Debian 12, OpenWrt 23.05 |
| Linux | x64 |
>= Linux 5.15, >= GNU C Library 2.35 | 1 | Ubuntu 22.04, Debian 12, OpenWrt 23.05 |
| Linux | riscv64 |
>= Linux 6.8, >= GNU C Library 2.39 | 2 | Ubuntu 24.04, Debian 13 |
| Linux | arm |
>= Linux 5.10, >= musl 1.2 | 2 | Alpine 3.13, OpenWrt 22.03 |
| Linux | arm64 |
>= Linux 5.10, >= musl 1.2 | 2 | Alpine 3.13, OpenWrt 22.03 |
| Linux | ia32 |
>= Linux 5.10, >= musl 1.2 | 2 | Alpine 3.13, OpenWrt 22.03 |
| Linux | x64 |
>= Linux 5.10, >= musl 1.2 | 2 | Alpine 3.13, OpenWrt 22.03 |
| Linux | riscv64 |
>= Linux 6.6, >= musl 1.2 | 2 | Alpine 3.20 |
| Linux | mips |
>= Linux 5.10, >= musl 1.2 | 2 | OpenWrt 22.03 |
| Linux | mipsel |
>= Linux 5.10, >= musl 1.2 | 2 | OpenWrt 22.03 |
| Android | arm |
>= 10 | 1 | |
| Android | arm64 |
>= 10 | 1 | |
| Android | ia32 |
>= 10 | 1 | |
| Android | x64 |
>= 10 | 1 | |
| macOS | arm64 |
>= 12.0 | 1 | |
| macOS | x64 |
>= 12.0 | 1 | |
| iOS | arm64 |
>= 14.0 | 1 | |
| iOS | x64 |
>= 14.0 | 1 | Simulator only |
| Windows | arm64 |
>= Windows 11 | 1 | |
| Windows | x64 |
>= Windows 10 | 1 |
Bare provides no standard library beyond the core JavaScript API available through the Bare namespace. Instead, we maintain a comprehensive collection of external modules built specifically for Bare.
| Module | Description | Version |
|---|---|---|
| bare-abort | Cause abnormal program termination and generate a crash report | |
| bare-ansi-escapes | Parse and produce ANSI escape sequences | |
| bare-assert | Assertion library for JavaScript | |
| bare-atomics | Native synchronization primitives for JavaScript | |
| bare-buffer | Native buffers for JavaScript | |
| bare-bundle | Application bundle format for JavaScript, inspired by https://github.com/electron/asar | |
| bare-channel | Inter-thread messaging for JavaScript | |
| bare-console | WHATWG debugging console for JavaScript | |
| bare-crypto | Cryptographic primitives for JavaScript | |
| bare-daemon | Create and manage daemon processes in JavaScript | |
| bare-dgram | Native UDP for JavaScript | |
| bare-dns | Domain name resolution for JavaScript | |
| bare-encoding | WHATWG text encoding interfaces for JavaScript | |
| bare-env | Environment variable support for JavaScript | |
| bare-events | Event emitters for JavaScript | |
| bare-fetch | WHATWG Fetch implementation for Bare | |
| bare-form-data | Form data support for Bare | |
| bare-format | String formatting for JavaScript | |
| bare-fs | Native file system for JavaScript | |
| bare-hrtime | High-resolution timers for JavaScript | |
| bare-http1 | HTTP/1 library for JavaScript | |
| bare-https | HTTPS library for JavaScript | |
| bare-inspect | Inspect objects as strings for debugging | |
| bare-inspector | V8 inspector support for Bare | |
| bare-ipc | Lightweight pipe-based IPC for Bare | |
| bare-logger | Low-level logger for Bare with system log integration | |
| bare-module | Module support for JavaScript | |
| bare-os | Operating system utilities for JavaScript | |
| bare-pack | Bundle packing for Bare | |
| bare-path | Path manipulation library for JavaScript | |
| bare-performance | Performance monitoring for Bare | |
| bare-pipe | Native I/O pipes for JavaScript | |
| bare-queue-microtask | Microtask queuing for Bare | |
| bare-readline | Line editing for interactive CLIs with command history | |
| bare-realm | Realm support for Bare | |
| bare-repl | Read-Evaluate-Print-Loop environment for JavaScript | |
| bare-rpc | https://github.com/holepunchto/librpc ABI compatible RPC for Bare | |
| bare-semver | Minimal semantic versioning library for Bare | |
| bare-signals | Native signal handling for JavaScript | |
| bare-storage | Minimal, cross‑platform directory locator for Bare | |
| bare-stream | Streaming data for JavaScript | |
| bare-structured-clone | Structured cloning algorithm for JavaScript | |
| bare-subprocess | Native process spawning for JavaScript | |
| bare-tap | Minimal TAP library for Bare | |
| bare-tcp | Native TCP sockets for JavaScript | |
| bare-thread | Thread support for Bare | |
| bare-timers | Native timers for JavaScript | |
| bare-tls | Transport Layer Security (TLS) streams for JavaScript | |
| bare-tty | Native TTY streams for JavaScript | |
| bare-type | Cross-realm type predicates for Bare | |
| bare-unpack | Bundle unpacking for Bare | |
| bare-url | WHATWG URL implementation for JavaScript | |
| bare-worker | Higher-level worker threads for JavaScript | |
| bare-ws | WebSocket library for JavaScript | |
| bare-zlib | Stream-based zlib bindings for JavaScript | |
| bare-zmq | Low-level ZeroMQ bindings for JavaScript |
Apache-2.0