Skip to content

refactor: move embedded runtime JS to real .js files (js2c) - #1989

Open
edusperoni wants to merge 1 commit into
feat/v8-14from
feat/js-builtins
Open

refactor: move embedded runtime JS to real .js files (js2c)#1989
edusperoni wants to merge 1 commit into
feat/v8-14from
feat/js-builtins

Conversation

@edusperoni

Copy link
Copy Markdown
Collaborator

Description

Android mirror of NativeScript/ios#411. Stacked on #1987 (feat/v8-14) — review only the last commit.

Moves the runtime JavaScript that was embedded as C++ string literals across eight files into real, version-controlled .js files under test-app/runtime/src/main/cpp/js/, compiled into the runtime at build time (Node-style js2c).

Extraction & build

  • tools/js2c.mjs (taken from the iOS runtime, including its later unsigned char fix for source bytes ≥ 0x80) converts js/*.js into a generated RuntimeBuiltins.{h,cpp} table — deterministic output, gitignored generated dir.
  • Codegen runs from CMake via add_custom_command (node is already a build requirement — the static-binding-generator shells out to it). The RUNTIME_BUILTIN_JS list is explicit; --check-dir fails the build loudly if it drifts from the directory contents. Incremental: a no-op rebuild skips codegen, touching a .js reruns it.
  • Extraction fidelity was machine-verified: every extracted file was proven AST-identical to the original embedded literal by byte-comparing esbuild-minified output of both (whitespace+syntax minify only).

Extracted builtins: weak-ref, message-loop-timer, smart-stringify, require-factory, json-helper, events, error-events, blob-url.

Loader

  • Builtins follow Node's module wrapper: each file is compiled as a function body via ScriptCompiler::CompileFunction with the fixed parameters exports, module and binding — natives arrive as properties of a bag object built by the C++ call site (Node's internalBinding idiom), and results come back through module.exports.
  • BuiltinLoader::RunBuiltin compiles with a proper internal/<name>.js script origin (runtime frames are identifiable in stack traces) and a process-wide bytecode cache: the first compile in the process uses kEagerCompile + CreateCodeCacheForFunction, later isolates consume via kConsumeCodeCache (with rejected-cache fallback). The cache is mutex-guarded — worker runtimes initialize on their own threads.
  • ESLint (eslint.config.mjs) declares exports, module, binding and the reachable native globals; no-undef is the typo net. Conventions are documented in test-app/runtime/src/main/cpp/js/README.md.

Behavior notes

  • Stack traces from runtime internals now show internal/<name>.js origins instead of anonymous frames.
  • JSONObjectHelper recompiled its JS→org.json serializer on every registration; it is now compiled once per isolate and released via the isolate-dispose hook (the Android analog of the iOS PR's recompile-per-use perf fixes).
  • __messageLoopTimerStart/__messageLoopTimerStop are no longer installed on the global object — nothing outside MessageLoopTimer referenced them; the builtin receives the pair through binding.

Related Pull Requests

Does your pull request have unit tests?

Covered by the full existing device suite: 605 specs, 0 failures on an emulator (all extracted paths — events, error events, URL/Blob, WeakRef, module require, console stringify, workers — are exercised by existing specs; workers exercise the cross-isolate bytecode cache).

The runtime's internal JavaScript lived as C++ string literals across eight
files, unlintable and invisible to tooling. It now lives in real .js files
under test-app/runtime/src/main/cpp/js, embedded into a generated C++ table by
tools/js2c.mjs at build time and executed through a new BuiltinLoader.

Each file is compiled with v8::ScriptCompiler::CompileFunction as a function
body with the fixed parameters `exports`, `module` and `binding` (Node's module
wrapper plus its internalBinding idiom): natives arrive as properties of a
binding bag built at the C++ call site, results come back through
module.exports, and the script origin is internal/<name>.js so runtime frames
stay identifiable in stack traces. Compilation goes through a process-wide
bytecode cache guarded by a mutex, since worker runtimes initialize on their
own threads.

Extracted: weak-ref, message-loop-timer, smart-stringify, require-factory,
json-helper, events, error-events and blob-url. Each extraction was verified
AST-identical to the original literal by byte-comparing esbuild-minified
output of both.

tools/js2c.mjs is taken from the iOS runtime's feat/ns-util branch, which
includes the later `unsigned char` fix for source bytes >= 0x80 (a narrowing
error in a plain char array). Its --filelist drift check is adapted to
--check-dir, comparing the explicit RUNTIME_BUILTIN_JS list in CMakeLists.txt
against the directory contents so a new builtin cannot be silently skipped on
incremental builds.

Two behavioural notes:

- JSONObjectHelper recompiled its JS->org.json serializer on every
  MetadataNode `from` registration. It is now compiled once per isolate and
  released via the isolate-dispose hook.
- __messageLoopTimerStart/__messageLoopTimerStop are no longer installed on
  the global object. Nothing outside MessageLoopTimer referenced them, and the
  timer's start/stop pair now reaches its builtin through the binding bag.

Mirrors NativeScript/ios#411.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 088c34a3-e64f-42c2-91a3-5def71e68902

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant