Bun provides a V8 Compatibility Layer that allows native addons written for the V8 engine (Node.js's default engine) to run on JavaScriptCore (JSC). This layer acts as a shim, mapping V8's C++ API calls to equivalent operations in JSC, while also supporting the Node-API (N-API) standard and internal Node.js C++ bindings.
The compatibility layer is primarily composed of C++ wrappers that emulate V8 classes like v8::Value, v8::String, and v8::Object. These wrappers encapsulate JSC's JSValue and JSObject types, providing the method signatures expected by native modules. This allows Bun to support both the modern Node-API and legacy native modules that depend directly on V8 headers.
| Component | Description |
|---|---|
| V8 Shims | C++ classes (v8::Value, v8::Isolate, etc.) that wrap JSC entities. |
| HandleScope | Emulates V8's stack-based memory management for JS values. |
| node.cpp | Implements Node.js-specific internal C++ APIs like node_module_register. |
| node-gyp integration | Bun provides a wrapper for node-gyp to compile native addons against its own headers. |
Sources: src/symbols.txt1-15 test/v8/v8.test.ts67-86
The following diagram illustrates how a native addon's call to a V8 API is routed through Bun's shim layer into the JavaScriptCore engine.
This diagram shows how a native addon's V8 API call is routed through Bun's shim layer to JavaScriptCore, with the key C++ symbols involved.
Sources: src/symbols.txt1-48 src/symbols.dyn1-49 test/napi/napi-app/main.cpp57-63
Bun implements the most commonly used V8 types by wrapping JSC types. These are defined in the v8 namespace to provide source-level compatibility.
Sources: src/symbols.txt21-81 test/v8/v8-module/main.cpp1-80
In V8, v8::Local<T> is a handle to a GC-managed object. In Bun, these handles are pointers to JSC::JSValue storage managed by a v8::HandleScope. The v8::api_internal::GlobalizeReference function src/symbols.txt8 pins a value outside a HandleScope for use in v8::Global<T>.
v8::Value: Base class for all JavaScript values.
IsBoolean() src/symbols.txt67 IsNumber() src/symbols.txt63 IsString() src/symbols.txt65 IsObject() src/symbols.txt64 IsFunction() src/symbols.txt51IsUndefined() src/symbols.txt57 IsNull() src/symbols.txt60 IsNullOrUndefined() src/symbols.txt59IsTrue() src/symbols.txt61 IsFalse() src/symbols.txt62IsArray() src/symbols.txt53 IsMap() src/symbols.txt52IsInt32() src/symbols.txt54 IsUint32() src/symbols.txt66 IsBigInt() src/symbols.txt55Uint32Value() src/symbols.txt58 StrictEquals() src/symbols.txt68v8::MaybeLocal<T>: A nullable handle type used for operations that may fail. Uses v8::api_internal::ToLocalEmpty src/symbols.txt6 when the optional value is absent. Callers use IsEmpty(), ToLocal(), and ToLocalChecked() to safely unwrap values. If ToLocalChecked() is called on an empty MaybeLocal, it calls v8::api_internal::FromJustIsNothing src/symbols.txt7 which aborts.
v8::String: Maps to JSC::JSString.
NewFromUtf8() src/symbols.txt36 NewFromOneByte() src/symbols.txt37Length() (UTF-16 code units) src/symbols.txt75 Utf8Length() src/symbols.txt71IsOneByte() src/symbols.txt76 ContainsOnlyOneByte() src/symbols.txt74IsExternal() src/symbols.txt70 IsExternalOneByte() src/symbols.txt72 IsExternalTwoByte() src/symbols.txt73WriteUtf8() src/symbols.txt77: Writes UTF-8 bytes to a caller-supplied buffer. Utf8Length() saturates at INT32_MAX for strings whose UTF-8 expansion exceeds that limit test/v8/v8.test.ts400-516v8::Boolean: Boolean::New(isolate, bool) src/symbols.txt38 Value() src/symbols.txt78
v8::Number: Number::New(isolate, double) src/symbols.txt27 Value() src/symbols.txt69
v8::Object: Maps to JSC::JSObject.
New() src/symbols.txt31 Set() src/symbols.txt32-33 Get() src/symbols.txt34-35SetInternalField() src/symbols.txt29 GetInternalField() src/symbols.txt28: Store and retrieve opaque C++ data on a JS object slot. SlowGetInternalField() src/symbols.txt30 is the out-of-line fallback.v8::Array: Maps to JSC::JSArray.
New(isolate, Local<Value>*, size_t) src/symbols.txt21: Creates from a C array of Local<Value>.New(isolate, int) src/symbols.txt23: Creates with a pre-allocated length.New(context, size_t, callback) src/symbols.txt24: Creates with a factory callback.Length() src/symbols.txt22 Iterate(context, callback, data) src/symbols.txt25CheckCast(Value*) src/symbols.txt26: Used internally by Local<Array>::Cast.v8::External: Wraps a raw void* C++ pointer as an opaque JS value. Created with External::New(isolate, ptr) src/symbols.txt43 and read back with Value() src/symbols.txt79 Used to associate native data with ObjectTemplate internal fields.
v8::Function: SetName() src/symbols.txt44 GetName() src/symbols.txt80
v8::Global<T>: A persistent handle that lives outside any HandleScope. Backed by v8::api_internal::GlobalizeReference src/symbols.txt8 (pins value in JSC heap) and v8::api_internal::DisposeGlobal src/symbols.txt6 (releases pin). Used via Global<T>::Reset() and Global<T>::Get().
v8::HandleScope: Stack-scoped container for Local<T> handles. Constructor src/symbols.txt2 pushes a new frame onto the handle stack; destructor src/symbols.txt3-4 pops it. CreateHandle src/symbols.txt1 allocates a new slot.
v8::EscapableHandleScope: Extends HandleScope to allow a single value to escape to the enclosing scope. EscapeSlot() src/symbols.txt19 marks the escape slot.
v8::FunctionTemplate: Blueprint for creating JavaScript functions with native callbacks.
New(isolate, callback, data, ...) src/symbols.txt14: Creates a template. The data parameter is kept alive and accessible via v8::api_internal::GetFunctionTemplateData src/symbols.txt9GetFunction(context) src/symbols.txt13: Instantiates the template as a concrete v8::Function.v8::ObjectTemplate: Blueprint for creating JavaScript objects with a fixed set of internal fields.
New(isolate, FunctionTemplate?) src/symbols.txt12SetInternalFieldCount(int) src/symbols.txt11 InternalFieldCount() src/symbols.txt49NewInstance(context) src/symbols.txt10: Creates a v8::Object from the template.v8::Isolate: Represents an isolated JSC VM instance.
GetCurrent() src/symbols.txt40 TryGetCurrent() src/symbols.txt41GetCurrentContext() src/symbols.txt42v8::Context: GetIsolate() src/symbols.txt39
v8::internal::IsolateFromNeverReadOnlySpaceObject src/symbols.txt45: Internal helper used by V8's inline fast paths (e.g., Local<T> casting internals).
Sources: src/symbols.txt1-81 src/symbols.dyn1-81 test/v8/v8-module/main.cpp37-690
Native addons are typically compiled using node-gyp. Bun supports this workflow by providing the necessary symbols and headers.
Native modules register themselves using node_module_register or via Node-API exports. Bun's loader resolves these symbols and executes the registration function, passing a v8::Local<v8::Object> as the exports object.
Sources: src/linker.lds1-5 test/v8/v8.test.ts67-86
Modules use hooks to clean up resources when the process or environment terminates.
node::AddEnvironmentCleanupHook: Registers a callback to be executed when the environment is destroyed src/symbols.txt47node::RemoveEnvironmentCleanupHook: Unregisters a previously added cleanup hook src/symbols.txt48Bun explicitly exports V8, Node, Node-API, and libuv symbols via its linker scripts so that dynamically loaded .node modules can resolve them at runtime.
| File | Platform | Purpose |
|---|---|---|
src/symbols.txt | macOS | Master list of mangled C++ V8/Node symbols and C napi_*/uv_* symbols src/symbols.txt1-554 |
src/symbols.dyn | macOS/Linux | Dynamic symbol version of the same list, used with -exported_symbols_list or --dynamic-list src/symbols.dyn1-555 |
src/symbols.def | Windows | PE export definitions for uv_* and napi_* symbols src/symbols.def1-341 |
src/linker.lds | Linux | Linker version script; exports napi*, node_*, uv_*, v8::*, and node::* under the BUN_1.2 symbol version src/linker.lds1-332 |
The linker script uses an extern "C++" glob to capture all V8 and Node symbols by their C++ namespace src/linker.lds326-329 All other symbols default to local visibility.
Bun also exports the full libuv (uv_*) API surface so that native addons linking against libuv can resolve those symbols from the Bun binary. This covers everything from uv_timer_*, uv_fs_*, uv_thread_* to uv_os_* and uv_loop_* src/symbols.txt239-554
Sources: src/symbols.txt1-554 src/symbols.dyn1-555 src/linker.lds1-332 src/symbols.def1-341
Bun's V8 compatibility is verified through a suite of tests that compile and run actual native code against both Bun and Node.js to ensure consistent behavior.
The test suite in test/v8/v8.test.ts performs the following steps:
bun install to fetch dependencies like node-gyp test/v8/v8.test.ts47-59node-gyp rebuild using Bun to compile the native addon test/v8/v8.test.ts67-86IsMap, IsArray, IsInt32, and IsBigInt across various JS values test/v8/v8.test.ts145-164StrictEquals test/v8/v8.test.ts205-215Sources: test/v8/v8.test.ts1-215 test/v8/v8-module/main.cpp1-128
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.