The .js files in this directory are the runtime's internal JavaScript. At
build time the "Generate RuntimeBuiltins" Xcode phase runs tools/js2c.mjs,
which embeds them into NativeScript/runtime/generated/RuntimeBuiltins.cpp;
at runtime BuiltinLoader::RunBuiltin compiles and executes them with an
internal/<name>.js script origin and a process-wide bytecode cache.
Every file is compiled as a function body via v8::ScriptCompiler::CompileFunction
with the fixed parameters exports, require, module, binding and
primordials:
const { someNative, anotherNative } = binding;
const { ArrayPrototypeSlice, ObjectCreate } = primordials;
const { inspect } = require("ns:util");
module.exports = somethingTheCallSiteNeeds;bindingis a plain object of natives built by the C++ call site; a file that needs nothing from C++ simply doesn't mention it.requireresolves builtin specifiers only (ns:util,node:util, …), never a path or a package; an unknown one throwsNo such built-in module: <specifier>. It is how anode:shim consumes thens:module it adapts, and it materializes that module on first use. Requiring a module that is still loading throws rather than recursing.primordialsis the frozen intrinsics snapshot built byprimordials.js(see below), the same object for every builtin in an isolate.module.exportsis the export channel — whatever it holds when the file finishes is whatRunBuiltinhands back to C++ (used for factory functions and init results). Both CommonJS styles work: replace the whole export withmodule.exports = x, or hang properties offexports. A file that only installs globals exports nothing and the call site ignores the value.- No top-level
return. It would work — these are function bodies — but every tool that isn't reading this repo's ESLint config (editors' TS server, prettier, review bots) rejects the file as invalid JavaScript. - Strict mode is per-file: start the file with
"use strict";to opt in. inspect.jsis the console formatter (util.inspect-lite, exposed as the internal__inspectglobal): budgeted output, no getter invocation, tamper-immune via primordials. Console routes all object formatting through it.ns-util.jsis thens:utilmodule app code requires andnode-util.jsthenode:utilshim: one source file per specifier, the shim owning every bit of Node compatibility. Seedocs/ns-builtin-modules.mdfor the cross-runtime contract.- Destructure
bindingandprimordialsonce, at the top of the file, so the file's dependencies are visible and greppable.
- Run at isolate init, before any user code: capture any global you rely on
(e.g.
globalThis.Event) eagerly so later monkey-patching can't break you. For intrinsics that is whatprimordialsis; for everything else (URLSearchParams, …) capture it into a file-levelconst. - No
import/export— these are classic function bodies, not modules. - ESLint (
eslint.config.mjsat the repo root, run by lint-staged) declaresexports,require,module,binding,primordialsand the reachable native globals;no-undefis the typo net. If a builtin starts using a new native global, add it there.no-restricted-propertiesfails the lint on direct use of the captured statics (JSON.stringify,Object.defineProperty, …). Uncurried instance methods can't be matched that way, solist.slice()instead ofArrayPrototypeSlice(list)is caught by review, not by the linter. - File names are kebab-case; the name determines the
BuiltinIdenum value (promise-proxy.js→kPromiseProxy) and the script origin. New files must also be added totools/js2c-inputs.xcfilelist— the build fails with an explicit message if that list drifts out of sync (js2c.mjs --filelist).
primordials.js runs first in every isolate — lazily, on the first
RunBuiltin call, which happens during runtime init — and its frozen,
null-prototype export is cached per isolate (Caches::Primordials) and
handed to every other builtin, so a builtin that compiles later in the
isolate's life still sees intrinsics as they were before user code ran.
Naming follows Node: statics keep their path (JSONStringify,
ObjectDefineProperty), instance methods are uncurried so the receiver
becomes the first argument:
ArrayPrototypeSlice(list, 1) // not list.slice(1)
FunctionPrototypeCall(cb, this, event) // not cb.call(this, event)Uncurrying is Function.prototype.bind.bind(Function.prototype.call), which on
the jitless configuration the runtime ships is both faster than a captured
fn.call(...) and immune to a replaced Function.prototype.call.
Add only what a builtin actually needs; this is not a mirror of Node's list.
Plain constructor calls made once at init time (new Map() while
bootstrapping) may stay direct — the rule targets code in closures that
outlive init.