├── .gitignore ├── host-functions ├── README.md ├── demo.js ├── natives.h ├── natives.cpp ├── CMakeLists.txt └── host-functions.cpp ├── runner ├── README.md ├── hello.js ├── CMakeLists.txt └── runner.cpp ├── hf-runner ├── demo.js ├── registerNatives.h ├── myprint.cpp ├── add.cpp ├── README.md ├── CMakeLists.txt └── hf-runner.cpp ├── hello ├── README.md ├── hello.cpp └── CMakeLists.txt ├── evloop ├── timer.js ├── registerNatives.h ├── CMakeLists.txt ├── jslib.js.inc ├── README.md └── evloop.cpp ├── CMakeLists.txt ├── LICENSE ├── README.md ├── .clang-format └── CLAUDE.md /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build*/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /host-functions/README.md: -------------------------------------------------------------------------------- 1 | Hermes/JSI demo of JSI Host Functions. 2 | -------------------------------------------------------------------------------- /runner/README.md: -------------------------------------------------------------------------------- 1 | A demo app using Hermes and JSI to run JS from a file. 2 | -------------------------------------------------------------------------------- /hf-runner/demo.js: -------------------------------------------------------------------------------- 1 | myPrint("Host function add() returned", add(10, 20, 30)); 2 | -------------------------------------------------------------------------------- /host-functions/demo.js: -------------------------------------------------------------------------------- 1 | myPrint("Host function add() returned", add(10, 20, 30)); 2 | -------------------------------------------------------------------------------- /runner/hello.js: -------------------------------------------------------------------------------- 1 | print("Hello from runner"); 2 | invalidFunction(); // for testing exceptions 3 | -------------------------------------------------------------------------------- /hello/README.md: -------------------------------------------------------------------------------- 1 | A demo app using Hermes and JSI to run JS directly embedded in the C++ source 2 | code. 3 | -------------------------------------------------------------------------------- /evloop/timer.js: -------------------------------------------------------------------------------- 1 | var sec = 0; 2 | 3 | function timer() { 4 | print(++sec, "seconds"); 5 | if (sec < 5) 6 | setTimeout(timer, 1000); 7 | else 8 | print("done"); 9 | } 10 | 11 | print("Starting timer"); 12 | setTimeout(timer, 1000); 13 | -------------------------------------------------------------------------------- /host-functions/natives.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | /// Register host functions into the given runtime. 12 | void registerNatives(facebook::jsi::Runtime &rt); 13 | -------------------------------------------------------------------------------- /evloop/registerNatives.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | /// Register host functions into the given runtime. 12 | extern "C" void registerNatives(facebook::jsi::Runtime &rt); 13 | -------------------------------------------------------------------------------- /hf-runner/registerNatives.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | /// Register host functions into the given runtime. 12 | extern "C" void registerNatives(facebook::jsi::Runtime &rt); 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | # For more information, please refer to the LICENSE file in the root directory 3 | # or at 4 | 5 | cmake_minimum_required(VERSION 3.22) 6 | project(hermes-jsi-demos) 7 | 8 | add_subdirectory(hello) 9 | add_subdirectory(runner) 10 | add_subdirectory(host-functions) 11 | add_subdirectory(hf-runner) 12 | add_subdirectory(evloop) 13 | 14 | # A target to build all demos 15 | add_custom_target(demos 16 | DEPENDS hello runner host-functions hf-runner evloop) 17 | -------------------------------------------------------------------------------- /hf-runner/myprint.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include "registerNatives.h" 8 | 9 | #include 10 | 11 | using namespace facebook; 12 | 13 | /// Print all arguments separated by spaces. 14 | static jsi::Value hostMyPrint( 15 | jsi::Runtime &rt, 16 | const jsi::Value &, 17 | const jsi::Value *args, 18 | size_t count) { 19 | for (size_t i = 0; i < count; ++i) { 20 | if (i) 21 | std::cout << ' '; 22 | std::cout << args[i].toString(rt).utf8(rt); 23 | } 24 | std::cout << std::endl; 25 | return jsi::Value::undefined(); 26 | } 27 | 28 | extern "C" void registerNatives(jsi::Runtime &rt) { 29 | rt.global().setProperty( 30 | rt, 31 | "myPrint", 32 | jsi::Function::createFromHostFunction( 33 | rt, jsi::PropNameID::forAscii(rt, "print"), 0, hostMyPrint)); 34 | } 35 | -------------------------------------------------------------------------------- /hf-runner/add.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include "registerNatives.h" 8 | 9 | using namespace facebook; 10 | 11 | /// A very complicated function that adds all its arguments, which must be 12 | /// numbers, together. 13 | static jsi::Value hostAdd( 14 | jsi::Runtime &rt, 15 | const jsi::Value &, 16 | const jsi::Value *args, 17 | size_t count) { 18 | double sum = 0; 19 | for (size_t i = 0; i < count; ++i) { 20 | if (!args[i].isNumber()) 21 | throw jsi::JSError(rt, "Argument must be a number"); 22 | sum += args[i].asNumber(); 23 | } 24 | return sum; 25 | } 26 | 27 | extern "C" void registerNatives(jsi::Runtime &rt) { 28 | rt.global().setProperty( 29 | rt, 30 | "add", 31 | jsi::Function::createFromHostFunction( 32 | rt, jsi::PropNameID::forAscii(rt, "add"), 2, hostAdd)); 33 | } 34 | -------------------------------------------------------------------------------- /hf-runner/README.md: -------------------------------------------------------------------------------- 1 | # hf-runner 2 | 3 | Hermes and JSI demo of JSI Host Functions loaded dynamically from 4 | shared libraries specified on the command line. 5 | 6 | `hf-runner` dynamically loads shared libraries specified on the command line 7 | after the input JS files, and calls them to register any native functions into 8 | the global object. Then it executes the input JS file, which can use the registered 9 | natives. 10 | 11 | ## Usage 12 | 13 | ``` 14 | hf-runner [ ...] 15 | ``` 16 | 17 | ## Example 18 | 19 | `hf-runner` comes with two example libraries: libhfadd and libhfmyprint. The register 20 | the functions `add` and `myprint` respectively. 21 | 22 | The provided file `demo.js` uses both of these functions. 23 | 24 | ```sh 25 | $ hf-runner demo.js libhfadd.so libhfmyprint.so 26 | ``` 27 | 28 | ## Shared Library API 29 | 30 | The shared libraries must export a function named `registerNatives` with the following 31 | signature: 32 | 33 | ```c 34 | void registerNatives(facebook::jsi::Runtime &rt); 35 | ``` 36 | -------------------------------------------------------------------------------- /hello/hello.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | /// JS code to be executed. 11 | static const char *code = R"( 12 | print("Hello, World!"); 13 | throw Error("Surprise!"); 14 | )"; 15 | 16 | int main() { 17 | // You can Customize the runtime config here. 18 | auto runtimeConfig = 19 | hermes::vm::RuntimeConfig::Builder().withIntl(false).build(); 20 | 21 | // Create the Hermes runtime. 22 | auto runtime = facebook::hermes::makeHermesRuntime(runtimeConfig); 23 | 24 | // Execute some JS. 25 | int status = 0; 26 | try { 27 | runtime->evaluateJavaScript( 28 | std::make_unique(code), "main.js"); 29 | } catch (facebook::jsi::JSError &e) { 30 | // Handle JS exceptions here. 31 | std::cerr << "JS Exception: " << e.getStack() << std::endl; 32 | status = 1; 33 | } catch (facebook::jsi::JSIException &e) { 34 | // Handle JSI exceptions here. 35 | std::cerr << "JSI Exception: " << e.what() << std::endl; 36 | status = 1; 37 | } 38 | 39 | return status; 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | # For more information, please refer to the LICENSE file in the root directory 3 | # or at 4 | 5 | cmake_minimum_required(VERSION 3.22) 6 | project(hello) 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | set(HERMES_SRC_DIR "" CACHE PATH "Path to Hermes source directory") 11 | set(HERMES_BUILD_DIR "" CACHE PATH "Path to Hermes build directory") 12 | 13 | if (NOT HERMES_SRC_DIR) 14 | message(FATAL_ERROR "Please specify HERMES_SRC_DIR") 15 | endif () 16 | # Validate HERMES_SRC_DIR by checking for API/jsi/jsi/jsi.h 17 | if (NOT EXISTS "${HERMES_SRC_DIR}/API/jsi/jsi/jsi.h") 18 | message(FATAL_ERROR "HERMES_SRC_DIR does not contain API/jsi/jsi/jsi.h") 19 | endif () 20 | 21 | if (NOT HERMES_BUILD_DIR) 22 | message(FATAL_ERROR "Please specify HERMES_BUILD_DIR") 23 | endif () 24 | # Validate HERMES_BUILD_DIR by checking for bin/hermes with the platform-specific extension 25 | if (NOT EXISTS "${HERMES_BUILD_DIR}/bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 26 | message(FATAL_ERROR "HERMES_BUILD_DIR does not contain bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 27 | endif () 28 | 29 | # Add Hermes include directories 30 | include_directories("${HERMES_SRC_DIR}/API") 31 | include_directories("${HERMES_SRC_DIR}/API/jsi") 32 | include_directories("${HERMES_SRC_DIR}/public") 33 | 34 | # Add Hermes library directories 35 | link_directories("${HERMES_BUILD_DIR}/API/hermes") 36 | link_directories("${HERMES_BUILD_DIR}/jsi") 37 | 38 | add_executable(hello hello.cpp) 39 | target_link_libraries(hello hermes jsi) 40 | -------------------------------------------------------------------------------- /runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | # For more information, please refer to the LICENSE file in the root directory 3 | # or at 4 | 5 | cmake_minimum_required(VERSION 3.22) 6 | project(runner) 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | set(HERMES_SRC_DIR "" CACHE PATH "Path to Hermes source directory") 11 | set(HERMES_BUILD_DIR "" CACHE PATH "Path to Hermes build directory") 12 | 13 | if (NOT HERMES_SRC_DIR) 14 | message(FATAL_ERROR "Please specify HERMES_SRC_DIR") 15 | endif () 16 | # Validate HERMES_SRC_DIR by checking for API/jsi/jsi/jsi.h 17 | if (NOT EXISTS "${HERMES_SRC_DIR}/API/jsi/jsi/jsi.h") 18 | message(FATAL_ERROR "HERMES_SRC_DIR does not contain API/jsi/jsi/jsi.h") 19 | endif () 20 | 21 | if (NOT HERMES_BUILD_DIR) 22 | message(FATAL_ERROR "Please specify HERMES_BUILD_DIR") 23 | endif () 24 | # Validate HERMES_BUILD_DIR by checking for bin/hermes with the platform-specific extension 25 | if (NOT EXISTS "${HERMES_BUILD_DIR}/bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 26 | message(FATAL_ERROR "HERMES_BUILD_DIR does not contain bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 27 | endif () 28 | 29 | # Add Hermes include directories 30 | include_directories("${HERMES_SRC_DIR}/API") 31 | include_directories("${HERMES_SRC_DIR}/API/jsi") 32 | include_directories("${HERMES_SRC_DIR}/public") 33 | 34 | # Add Hermes library directories 35 | link_directories("${HERMES_BUILD_DIR}/API/hermes") 36 | link_directories("${HERMES_BUILD_DIR}/jsi") 37 | 38 | add_executable(runner runner.cpp) 39 | target_link_libraries(runner hermes jsi) 40 | -------------------------------------------------------------------------------- /evloop/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | # For more information, please refer to the LICENSE file in the root directory 3 | # or at 4 | 5 | cmake_minimum_required(VERSION 3.22) 6 | project(evloop) 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | set(HERMES_SRC_DIR "" CACHE PATH "Path to Hermes source directory") 11 | set(HERMES_BUILD_DIR "" CACHE PATH "Path to Hermes build directory") 12 | 13 | if (NOT HERMES_SRC_DIR) 14 | message(FATAL_ERROR "Please specify HERMES_SRC_DIR") 15 | endif () 16 | # Validate HERMES_SRC_DIR by checking for API/jsi/jsi/jsi.h 17 | if (NOT EXISTS "${HERMES_SRC_DIR}/API/jsi/jsi/jsi.h") 18 | message(FATAL_ERROR "HERMES_SRC_DIR does not contain API/jsi/jsi/jsi.h") 19 | endif () 20 | 21 | if (NOT HERMES_BUILD_DIR) 22 | message(FATAL_ERROR "Please specify HERMES_BUILD_DIR") 23 | endif () 24 | # Validate HERMES_BUILD_DIR by checking for bin/hermes with the platform-specific extension 25 | if (NOT EXISTS "${HERMES_BUILD_DIR}/bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 26 | message(FATAL_ERROR "HERMES_BUILD_DIR does not contain bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 27 | endif () 28 | 29 | # Add Hermes include directories 30 | include_directories("${HERMES_SRC_DIR}/API") 31 | include_directories("${HERMES_SRC_DIR}/API/jsi") 32 | include_directories("${HERMES_SRC_DIR}/public") 33 | 34 | # Add Hermes library directories 35 | link_directories("${HERMES_BUILD_DIR}/API/hermes") 36 | link_directories("${HERMES_BUILD_DIR}/jsi") 37 | 38 | link_libraries(jsi) 39 | 40 | add_executable(evloop evloop.cpp) 41 | target_link_libraries(evloop hermes) 42 | -------------------------------------------------------------------------------- /host-functions/natives.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include "natives.h" 8 | 9 | #include 10 | 11 | using namespace facebook; 12 | 13 | /// A very complicated function that adds all its arguments, which must be 14 | /// numbers, together. 15 | static jsi::Value hostAdd( 16 | jsi::Runtime &rt, 17 | const jsi::Value &, 18 | const jsi::Value *args, 19 | size_t count) { 20 | double sum = 0; 21 | for (size_t i = 0; i < count; ++i) { 22 | if (!args[i].isNumber()) 23 | throw jsi::JSError(rt, "Argument must be a number"); 24 | sum += args[i].asNumber(); 25 | } 26 | return sum; 27 | } 28 | 29 | /// Print all arguments separated by spaces. 30 | static jsi::Value hostMyPrint( 31 | jsi::Runtime &rt, 32 | const jsi::Value &, 33 | const jsi::Value *args, 34 | size_t count) { 35 | for (size_t i = 0; i < count; ++i) { 36 | if (i) 37 | std::cout << ' '; 38 | std::cout << args[i].toString(rt).utf8(rt); 39 | } 40 | std::cout << std::endl; 41 | return jsi::Value::undefined(); 42 | } 43 | 44 | void registerNatives(jsi::Runtime &rt) { 45 | rt.global().setProperty( 46 | rt, 47 | "add", 48 | jsi::Function::createFromHostFunction( 49 | rt, jsi::PropNameID::forAscii(rt, "add"), 2, hostAdd)); 50 | rt.global().setProperty( 51 | rt, 52 | "myPrint", 53 | jsi::Function::createFromHostFunction( 54 | rt, jsi::PropNameID::forAscii(rt, "print"), 0, hostMyPrint)); 55 | } 56 | -------------------------------------------------------------------------------- /host-functions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | # For more information, please refer to the LICENSE file in the root directory 3 | # or at 4 | 5 | cmake_minimum_required(VERSION 3.22) 6 | project(host-functions) 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | set(HERMES_SRC_DIR "" CACHE PATH "Path to Hermes source directory") 11 | set(HERMES_BUILD_DIR "" CACHE PATH "Path to Hermes build directory") 12 | 13 | if (NOT HERMES_SRC_DIR) 14 | message(FATAL_ERROR "Please specify HERMES_SRC_DIR") 15 | endif () 16 | # Validate HERMES_SRC_DIR by checking for API/jsi/jsi/jsi.h 17 | if (NOT EXISTS "${HERMES_SRC_DIR}/API/jsi/jsi/jsi.h") 18 | message(FATAL_ERROR "HERMES_SRC_DIR does not contain API/jsi/jsi/jsi.h") 19 | endif () 20 | 21 | if (NOT HERMES_BUILD_DIR) 22 | message(FATAL_ERROR "Please specify HERMES_BUILD_DIR") 23 | endif () 24 | # Validate HERMES_BUILD_DIR by checking for bin/hermes with the platform-specific extension 25 | if (NOT EXISTS "${HERMES_BUILD_DIR}/bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 26 | message(FATAL_ERROR "HERMES_BUILD_DIR does not contain bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 27 | endif () 28 | 29 | # Add Hermes include directories 30 | include_directories("${HERMES_SRC_DIR}/API") 31 | include_directories("${HERMES_SRC_DIR}/API/jsi") 32 | include_directories("${HERMES_SRC_DIR}/public") 33 | 34 | # Add Hermes library directories 35 | link_directories("${HERMES_BUILD_DIR}/API/hermes") 36 | link_directories("${HERMES_BUILD_DIR}/jsi") 37 | 38 | add_executable(host-functions 39 | host-functions.cpp 40 | natives.cpp natives.h) 41 | target_link_libraries(host-functions hermes jsi) 42 | -------------------------------------------------------------------------------- /hf-runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is free and unencumbered software released into the public domain. 2 | # For more information, please refer to the LICENSE file in the root directory 3 | # or at 4 | 5 | cmake_minimum_required(VERSION 3.22) 6 | project(hf-runner) 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | set(HERMES_SRC_DIR "" CACHE PATH "Path to Hermes source directory") 11 | set(HERMES_BUILD_DIR "" CACHE PATH "Path to Hermes build directory") 12 | 13 | if (NOT HERMES_SRC_DIR) 14 | message(FATAL_ERROR "Please specify HERMES_SRC_DIR") 15 | endif () 16 | # Validate HERMES_SRC_DIR by checking for API/jsi/jsi/jsi.h 17 | if (NOT EXISTS "${HERMES_SRC_DIR}/API/jsi/jsi/jsi.h") 18 | message(FATAL_ERROR "HERMES_SRC_DIR does not contain API/jsi/jsi/jsi.h") 19 | endif () 20 | 21 | if (NOT HERMES_BUILD_DIR) 22 | message(FATAL_ERROR "Please specify HERMES_BUILD_DIR") 23 | endif () 24 | # Validate HERMES_BUILD_DIR by checking for bin/hermes with the platform-specific extension 25 | if (NOT EXISTS "${HERMES_BUILD_DIR}/bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 26 | message(FATAL_ERROR "HERMES_BUILD_DIR does not contain bin/hermes${CMAKE_EXECUTABLE_SUFFIX}") 27 | endif () 28 | 29 | # Add Hermes include directories 30 | include_directories("${HERMES_SRC_DIR}/API") 31 | include_directories("${HERMES_SRC_DIR}/API/jsi") 32 | include_directories("${HERMES_SRC_DIR}/public") 33 | 34 | # Add Hermes library directories 35 | link_directories("${HERMES_BUILD_DIR}/API/hermes") 36 | link_directories("${HERMES_BUILD_DIR}/jsi") 37 | 38 | link_libraries(jsi) 39 | 40 | add_executable(hf-runner hf-runner.cpp) 41 | target_link_libraries(hf-runner hermes) 42 | add_dependencies(hf-runner hfadd hfmyprint) 43 | 44 | add_library(hfadd SHARED add.cpp) 45 | add_library(hfmyprint SHARED myprint.cpp) 46 | -------------------------------------------------------------------------------- /evloop/jslib.js.inc: -------------------------------------------------------------------------------- 1 | R"((function() { 2 | "use strict"; 3 | 4 | var tasks = []; 5 | var nextTaskID = 0; 6 | var curTime = 0; 7 | 8 | // Return the deadline of the next task, or -1 if there is no task. 9 | function peekMacroTask() { 10 | return tasks.length ? tasks[0].deadline : -1; 11 | } 12 | 13 | // Run the next task if it's time. 14 | // `tm` is the current time. 15 | function runMacroTask(tm) { 16 | curTime = tm; 17 | if (tasks.length && tasks[0].deadline <= tm) { 18 | var task = tasks.shift(); 19 | task.fn.apply(undefined, task.args); 20 | } 21 | } 22 | 23 | function setTimeout(fn, ms = 0, ...args) { 24 | var id = nextTaskID++; 25 | var task = {id, fn, deadline: curTime + Math.max(0, ms | 0), args}; 26 | // Insert the task in the sorted list. 27 | var i = 0; 28 | for (i = 0; i < tasks.length; ++i) { 29 | if (tasks[i].deadline > task.deadline) { 30 | break; 31 | } 32 | } 33 | tasks.splice(i, 0, task); 34 | return id; 35 | } 36 | 37 | function clearTimeout(id) { 38 | for (var i = 0; i < tasks.length; i++) { 39 | if (tasks[i].id === id) { 40 | tasks.splice(i, 1); 41 | break; 42 | } 43 | } 44 | } 45 | 46 | function setImmediate(fn, ...args) { 47 | return setTimeout(fn, 0, ...args); 48 | } 49 | function clearImmediate(id) { 50 | return clearTimeout(id); 51 | } 52 | 53 | globalThis.setTimeout = setTimeout; 54 | globalThis.clearTimeout = clearTimeout; 55 | globalThis.setImmediate = setImmediate; 56 | globalThis.clearImmediate = clearImmediate; 57 | 58 | return {peek: peekMacroTask, run: runMacroTask}; 59 | })(); 60 | )" 61 | -------------------------------------------------------------------------------- /runner/runner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | /// Read the contents of a file into a string. 15 | static std::optional readFile(const char *path) { 16 | std::ifstream fileStream(path); 17 | std::stringstream stringStream; 18 | 19 | if (fileStream) { 20 | stringStream << fileStream.rdbuf(); 21 | fileStream.close(); 22 | } else { 23 | // Handle error - file opening failed 24 | std::cerr << path << ": error opening file" << std::endl; 25 | return std::nullopt; 26 | } 27 | 28 | return stringStream.str(); 29 | } 30 | 31 | int main(int argc, char **argv) { 32 | // If no argument is provided, print usage and exit. 33 | if (argc != 2) { 34 | std::cout << "Usage: " << argv[0] << " " << std::endl; 35 | return 1; 36 | } 37 | const char *jsPath = argv[1]; 38 | 39 | // Read the file. 40 | auto optCode = readFile(jsPath); 41 | if (!optCode) 42 | return 1; 43 | 44 | // You can Customize the runtime config here. 45 | auto runtimeConfig = 46 | hermes::vm::RuntimeConfig::Builder().withIntl(false).build(); 47 | 48 | // Create the Hermes runtime. 49 | auto runtime = facebook::hermes::makeHermesRuntime(runtimeConfig); 50 | 51 | // Execute some JS. 52 | int status = 0; 53 | try { 54 | runtime->evaluateJavaScript( 55 | std::make_unique(std::move(*optCode)), 56 | jsPath); 57 | } catch (facebook::jsi::JSError &e) { 58 | // Handle JS exceptions here. 59 | std::cerr << "JS Exception: " << e.getStack() << std::endl; 60 | status = 1; 61 | } catch (facebook::jsi::JSIException &e) { 62 | // Handle JSI exceptions here. 63 | std::cerr << "JSI Exception: " << e.what() << std::endl; 64 | status = 1; 65 | } 66 | 67 | return status; 68 | } 69 | -------------------------------------------------------------------------------- /host-functions/host-functions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "natives.h" 15 | 16 | /// Read the contents of a file into a string. 17 | static std::optional readFile(const char *path) { 18 | std::ifstream fileStream(path); 19 | std::stringstream stringStream; 20 | 21 | if (fileStream) { 22 | stringStream << fileStream.rdbuf(); 23 | fileStream.close(); 24 | } else { 25 | // Handle error - file opening failed 26 | std::cerr << path << ": error opening file" << std::endl; 27 | return std::nullopt; 28 | } 29 | 30 | return stringStream.str(); 31 | } 32 | 33 | int main(int argc, char **argv) { 34 | // If no argument is provided, print usage and exit. 35 | if (argc != 2) { 36 | std::cout << "Usage: " << argv[0] << " " << std::endl; 37 | return 1; 38 | } 39 | const char *jsPath = argv[1]; 40 | 41 | // Read the file. 42 | auto optCode = readFile(jsPath); 43 | if (!optCode) 44 | return 1; 45 | 46 | // You can Customize the runtime config here. 47 | auto runtimeConfig = 48 | hermes::vm::RuntimeConfig::Builder().withIntl(false).build(); 49 | 50 | // Create the Hermes runtime. 51 | auto runtime = facebook::hermes::makeHermesRuntime(runtimeConfig); 52 | 53 | // Register host functions. 54 | registerNatives(*runtime); 55 | 56 | // Execute some JS. 57 | int status = 0; 58 | try { 59 | runtime->evaluateJavaScript( 60 | std::make_unique(std::move(*optCode)), 61 | jsPath); 62 | } catch (facebook::jsi::JSError &e) { 63 | // Handle JS exceptions here. 64 | std::cerr << "JS Exception: " << e.getStack() << std::endl; 65 | status = 1; 66 | } catch (facebook::jsi::JSIException &e) { 67 | // Handle JSI exceptions here. 68 | std::cerr << "JSI Exception: " << e.what() << std::endl; 69 | status = 1; 70 | } 71 | 72 | return status; 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hermes-jsi-demos 2 | 3 | This repository contains small demos of using the Hermes JavaScript engine with JSI 4 | without React Native or any other framework. 5 | 6 | Every demo is constructed as an independent CMake project, which can be copied 7 | and customized, but they can also be built together from the top-level directory. 8 | 9 | ## Demos 10 | 11 | In order of increasing complexity: 12 | * [hello](./hello) - a simple "Hello, world!" demo, running a JS script embedded in C++. 13 | * [runner](./runner) - runs a JS script from a file. 14 | * [host-functions](./host-functions) - demonstrates registering host functions into the global object. 15 | * [hf-runner](./hf-runner) - demonstrate registering host functions from dynamically loaded shared libraries. 16 | * [evloop](./evloop) - demonstrates a simple event loop, which enables setTimeout() and setImmediate(), plus WeakRef. 17 | 18 | ## Building 19 | 20 | ### Pre-requisites 21 | 22 | * CMake 3.20 or later 23 | * Ninja 24 | * A C++ compiler 25 | * git 26 | 27 | Building and running the demos has been tested on Linux and macOS, but not on 28 | Windows. PRs adding support for Windows are welcome. 29 | 30 | On macOS, I recommend installing XCode or the XCode command line tools, and using `brew` 31 | to obtain CMake and Ninja. 32 | 33 | ### Build Hermes 34 | 35 | First you need to build hermes. Obtain a clean checkout of Hermes from https://github.com/facebook/hermes.git: 36 | ```sh 37 | $ git clone https://github.com/facebook/hermes.git 38 | ``` 39 | 40 | Create a Hermes build directory somewhere, depending on your preferences, and name it according. 41 | Then configure and build Hermes: 42 | ```sh 43 | mkdir hermes-debug 44 | cd hermes-debug 45 | cmake -G Ninja -DHERMES_BUILD_APPLE_FRAMEWORK=OFF -DCMAKE_BUILD_TYPE=Debug 46 | ninja 47 | ``` 48 | 49 | You can change the build type by specifying `-DCMAKE_BUILD_TYPE=Release`. 50 | `-DHERMES_BUILD_APPLE_FRAMEWORK=OFF` is important on MacOS, so make sure you don't forget it. 51 | 52 | ### Build the demos 53 | 54 | All demos require two CMake defines to be set: 55 | * `HERMES_SRC_DIR` - the path to the Hermes checkout. 56 | * `HERMES_BUILD_DIR` - the path to the Hermes build directory from above. 57 | 58 | Create a build directory for the demos in a location, depending on your preferences, and name it accordingly. 59 | Then configure and build the demos: 60 | ```sh 61 | mkdir demos-debug 62 | cd demos-debug 63 | cmake -G Ninja -DHERMES_SRC_DIR= \ 64 | -DHERMES_BUILD_DIR= \ 65 | 66 | ninja demos 67 | ``` 68 | ## Final Words 69 | 70 | This project is built for education purposes, and is not intended to be used in production. 71 | Questions, discussions and PRs are highly encouraged. 72 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -1 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlinesLeft: true 7 | AlignOperands: false 8 | AlignTrailingComments: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: true 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: false 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: false 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | BreakBeforeBinaryOperators: None 33 | BreakBeforeBraces: Attach 34 | BreakBeforeTernaryOperators: true 35 | BreakConstructorInitializersBeforeComma: false 36 | BreakAfterJavaFieldAnnotations: false 37 | BreakStringLiterals: false 38 | ColumnLimit: 80 39 | CommentPragmas: '^ IWYU pragma:' 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 41 | ConstructorInitializerIndentWidth: 4 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: false 45 | DisableFormat: false 46 | ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ] 47 | IncludeCategories: 48 | - Regex: '^<.*\.h(pp)?>' 49 | Priority: 1 50 | - Regex: '^<.*' 51 | Priority: 2 52 | - Regex: '.*' 53 | Priority: 3 54 | IndentCaseLabels: true 55 | IndentWidth: 2 56 | IndentWrappedFunctionNames: false 57 | KeepEmptyLinesAtTheStartOfBlocks: false 58 | MacroBlockBegin: '' 59 | MacroBlockEnd: '' 60 | MaxEmptyLinesToKeep: 1 61 | NamespaceIndentation: None 62 | ObjCBlockIndentWidth: 2 63 | ObjCSpaceAfterProperty: false 64 | ObjCSpaceBeforeProtocolList: false 65 | PenaltyBreakBeforeFirstCallParameter: 1 66 | PenaltyBreakComment: 300 67 | PenaltyBreakFirstLessLess: 120 68 | PenaltyBreakString: 1000 69 | PenaltyExcessCharacter: 1000000 70 | PenaltyReturnTypeOnItsOwnLine: 200 71 | PointerAlignment: Right 72 | ReflowComments: true 73 | SortIncludes: true 74 | SpaceAfterCStyleCast: false 75 | SpaceBeforeAssignmentOperators: true 76 | SpaceBeforeParens: ControlStatements 77 | SpaceInEmptyParentheses: false 78 | SpacesBeforeTrailingComments: 1 79 | SpacesInAngles: false 80 | SpacesInContainerLiterals: true 81 | SpacesInCStyleCastParentheses: false 82 | SpacesInParentheses: false 83 | SpacesInSquareBrackets: false 84 | Standard: Cpp11 85 | TabWidth: 8 86 | UseTab: Never 87 | ... 88 | -------------------------------------------------------------------------------- /hf-runner/hf-runner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #ifndef _WIN32 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | #include 18 | 19 | /// Read the contents of a file into a string. 20 | static std::optional readFile(const char *path) { 21 | std::ifstream fileStream(path); 22 | std::stringstream stringStream; 23 | 24 | if (fileStream) { 25 | stringStream << fileStream.rdbuf(); 26 | fileStream.close(); 27 | } else { 28 | // Handle error - file opening failed 29 | std::cerr << path << ": error opening file" << std::endl; 30 | return std::nullopt; 31 | } 32 | 33 | return stringStream.str(); 34 | } 35 | 36 | /// The signature of the function that initializes the library. 37 | typedef void (*RegisterNativesFN)(facebook::jsi::Runtime &rt); 38 | 39 | #ifndef _WIN32 40 | /// Load the library and return the "registerNatives()" function. 41 | static RegisterNativesFN loadRegisterNatives(const char *libraryPath) { 42 | // Open the library. 43 | void *handle = dlopen(libraryPath, RTLD_LAZY); 44 | if (!handle) { 45 | std::cerr << "*** Cannot open library: " << dlerror() << '\n'; 46 | return nullptr; 47 | } 48 | 49 | // Clear any existing error. 50 | dlerror(); 51 | // Load the symbol (function). 52 | auto func = (RegisterNativesFN)dlsym(handle, "registerNatives"); 53 | if (const char *dlsym_error = dlerror()) { 54 | std::cerr << "Cannot load symbol 'registerNatives': " << dlsym_error 55 | << '\n'; 56 | dlclose(handle); 57 | return nullptr; 58 | } 59 | 60 | return func; 61 | } 62 | #else 63 | /// Load the library and return the "registerNatives()" function. 64 | static RegisterNativesFN loadRegisterNatives(const char *libraryPath) { 65 | // Load the library 66 | HMODULE hModule = LoadLibraryA(libraryPath); 67 | if (!hModule) { 68 | std::cerr << "Cannot open library: " << GetLastError() << '\n'; 69 | return nullptr; 70 | } 71 | 72 | // Get the function address 73 | auto func = (RegisterNativesFN)GetProcAddress(hModule, "registerNatives"); 74 | if (!func) { 75 | std::cerr << "Cannot load symbol 'registerNatives': " << GetLastError() 76 | << '\n'; 77 | FreeLibrary(hModule); 78 | return nullptr; 79 | } 80 | 81 | return func; 82 | } 83 | #endif 84 | 85 | /// Load all the libraries and call their "registerNatives()" function. 86 | /// \return true if all libraries were loaded successfully. 87 | static bool 88 | loadNativeLibraries(facebook::jsi::Runtime &rt, int argc, char **argv) { 89 | try { 90 | for (int i = 2; i < argc; i++) { 91 | auto func = loadRegisterNatives(argv[i]); 92 | if (!func) 93 | return false; 94 | func(rt); 95 | } 96 | } catch (facebook::jsi::JSIException &e) { 97 | // Handle JSI exceptions here. 98 | std::cerr << "JSI Exception: " << e.what() << std::endl; 99 | return false; 100 | } 101 | return true; 102 | } 103 | 104 | int main(int argc, char **argv) { 105 | // If no argument is provided, print usage and exit. 106 | if (argc < 2) { 107 | std::cout << "Usage: " << argv[0] << " [...]" 108 | << std::endl; 109 | return 1; 110 | } 111 | const char *jsPath = argv[1]; 112 | 113 | // Read the file. 114 | auto optCode = readFile(jsPath); 115 | if (!optCode) 116 | return 1; 117 | 118 | // You can Customize the runtime config here. 119 | auto runtimeConfig = 120 | hermes::vm::RuntimeConfig::Builder().withIntl(false).build(); 121 | 122 | // Create the Hermes runtime. 123 | auto runtime = facebook::hermes::makeHermesRuntime(runtimeConfig); 124 | 125 | // Register host functions. 126 | if (!loadNativeLibraries(*runtime, argc, argv)) 127 | return 1; 128 | 129 | // Execute some JS. 130 | int status = 0; 131 | try { 132 | runtime->evaluateJavaScript( 133 | std::make_unique(std::move(*optCode)), 134 | jsPath); 135 | } catch (facebook::jsi::JSError &e) { 136 | // Handle JS exceptions here. 137 | std::cerr << "JS Exception: " << e.getStack() << std::endl; 138 | status = 1; 139 | } catch (facebook::jsi::JSIException &e) { 140 | // Handle JSI exceptions here. 141 | std::cerr << "JSI Exception: " << e.what() << std::endl; 142 | status = 1; 143 | } 144 | 145 | return status; 146 | } 147 | -------------------------------------------------------------------------------- /evloop/README.md: -------------------------------------------------------------------------------- 1 | # evloop 2 | 3 | This example builds on the [hf-runner](../hf-runner) example. It adds a simple event 4 | loop, which allows the JavaScript code to register callbacks using setTimeout() and 5 | setImmediate(). It also enables micro-task support, which in turn enables WeakRef 6 | support. 7 | 8 | ## Usage 9 | 10 | ``` 11 | evloop [ ...] 12 | ``` 13 | 14 | ## How the Event Loop Works 15 | 16 | The event loop implementation is split between JavaScript (jslib.js.inc) and C++ (evloop.cpp), with the task queue maintained entirely in JavaScript. 17 | 18 | ### Architecture Overview 19 | 20 | **JavaScript Side (jslib.js.inc):** 21 | - Maintains a sorted array of macro tasks (`tasks[]`) 22 | - Each task contains: `{id, fn, deadline, args}` 23 | - Tasks are kept sorted by deadline for efficient scheduling 24 | - Provides `setTimeout()`, `clearTimeout()`, `setImmediate()`, and `clearImmediate()` global functions 25 | - Exposes two helper functions to C++: 26 | - `peek()`: Returns the deadline of the next task, or -1 if queue is empty 27 | - `run(currentTime)`: Executes the next task if its deadline has passed 28 | 29 | **C++ Side (evloop.cpp):** 30 | - Implements the main event loop using `std::chrono` for timing 31 | - Manages the runtime lifecycle and microtask queue 32 | - Coordinates with JavaScript task queue via the helper functions 33 | 34 | ### Event Loop Execution Flow 35 | 36 | 1. **Initialization** (lines 138-155 in evloop.cpp): 37 | - Enable microtask queue in runtime config: `withMicrotaskQueue(true)` 38 | - Evaluate jslib.js.inc to install setTimeout/setImmediate and get helper functions 39 | - Note: jslib.js.inc is a text file containing JavaScript wrapped in a C++ raw string literal `R"(...)"` 40 | - It's included directly into the C++ source via `#include "jslib.js.inc"` and assigned to a `const char*` 41 | - This embeds the JavaScript library code directly into the compiled binary 42 | - Initialize event loop's current time with first `run()` call 43 | - Load any native libraries 44 | - Execute the main JavaScript file 45 | 46 | 2. **Main Event Loop** (lines 168-191 in evloop.cpp): 47 | ```cpp 48 | while ((nextTimeMs = peekMacroTask.call(*runtime).getNumber()) >= 0) { 49 | // Get current time 50 | // If next task is in the future, sleep until then 51 | // Run the next macro task 52 | // Drain microtasks 53 | } 54 | ``` 55 | 56 | 3. **Task Scheduling** (lines 23-35 in jslib.js.inc): 57 | - `setTimeout(fn, ms, ...args)` creates a task with deadline = currentTime + ms 58 | - Task is inserted into the sorted `tasks[]` array by deadline 59 | - Returns a unique task ID for cancellation 60 | 61 | 4. **Task Execution** (lines 15-21 in jslib.js.inc): 62 | - C++ calls `run(currentTime)` with the actual time 63 | - JavaScript updates its `curTime` variable 64 | - If the first task's deadline has passed, it's removed from the queue and executed 65 | - After macro task execution, C++ calls `runtime->drainMicrotasks()` 66 | 67 | ### Task Queue Management 68 | 69 | **Insertion (setTimeout):** 70 | Tasks are inserted in O(n) time to maintain sorted order by deadline: 71 | ```javascript 72 | for (i = 0; i < tasks.length; ++i) { 73 | if (tasks[i].deadline > task.deadline) { 74 | break; 75 | } 76 | } 77 | tasks.splice(i, 0, task); 78 | ``` 79 | 80 | **Cancellation (clearTimeout):** 81 | Searches linearly through the queue and removes the task by ID: 82 | ```javascript 83 | for (var i = 0; i < tasks.length; i++) { 84 | if (tasks[i].id === id) { 85 | tasks.splice(i, 1); 86 | break; 87 | } 88 | } 89 | ``` 90 | 91 | **Execution (runMacroTask):** 92 | Always executes from the front of the queue (earliest deadline): 93 | ```javascript 94 | if (tasks.length && tasks[0].deadline <= tm) { 95 | var task = tasks.shift(); 96 | task.fn.apply(undefined, task.args); 97 | } 98 | ``` 99 | 100 | ### Microtasks vs Macrotasks 101 | 102 | - **Macrotasks**: setTimeout/setImmediate callbacks, managed by JavaScript queue 103 | - **Microtasks**: Promise callbacks, managed by Hermes runtime 104 | - After each macrotask execution, `runtime->drainMicrotasks()` runs all pending microtasks before the next macrotask 105 | - This ensures proper Promise resolution timing and enables WeakRef support 106 | 107 | ### Timing and Sleep 108 | 109 | The C++ event loop: 110 | - Uses `std::chrono::steady_clock` for monotonic time 111 | - Calculates sleep duration: `nextTimeMs - currentTimeMs` 112 | - Sleeps using `std::this_thread::sleep_until()` for precise wakeup 113 | - Re-queries time after sleeping to account for scheduling delays 114 | 115 | ### setImmediate Implementation 116 | 117 | `setImmediate()` is implemented as `setTimeout(fn, 0, ...args)`, meaning: 118 | - Task deadline is set to current time (no delay) 119 | - Still queued as a macrotask, not executed synchronously 120 | - Will run in the next iteration of the event loop 121 | - Microtasks scheduled during current execution will run first 122 | 123 | ### Example 124 | 125 | See `timer.js` for a simple example that prints seconds 1-5 using setTimeout. 126 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | This repository contains educational demos showcasing how to use the Hermes JavaScript engine with JSI (JavaScript Interface) without React Native or any other framework. Each demo is an independent CMake project that can be built separately or together from the root. 8 | 9 | ## Build System 10 | 11 | ### Prerequisites 12 | - CMake 3.22 or later 13 | - Ninja build system 14 | - C++17 compatible compiler 15 | - A pre-built Hermes installation 16 | 17 | ### Building Hermes (Required First) 18 | Before building demos, you must have Hermes built separately: 19 | ```bash 20 | git clone https://github.com/facebook/hermes.git 21 | mkdir hermes-debug 22 | cd hermes-debug 23 | cmake -G Ninja -DHERMES_BUILD_APPLE_FRAMEWORK=OFF -DCMAKE_BUILD_TYPE=Debug 24 | ninja 25 | ``` 26 | 27 | **IMPORTANT**: On macOS, `-DHERMES_BUILD_APPLE_FRAMEWORK=OFF` must be specified. 28 | 29 | ### Building All Demos 30 | From the repository root: 31 | ```bash 32 | mkdir demos-debug 33 | cd demos-debug 34 | cmake -G Ninja -DHERMES_SRC_DIR= \ 35 | -DHERMES_BUILD_DIR= \ 36 | 37 | ninja demos 38 | ``` 39 | 40 | The `demos` target builds all demo executables. 41 | 42 | ### Building Individual Demos 43 | Each demo subdirectory (hello, runner, host-functions, hf-runner, evloop) can be built independently: 44 | ```bash 45 | mkdir build 46 | cd build 47 | cmake -G Ninja -DHERMES_SRC_DIR= \ 48 | -DHERMES_BUILD_DIR= \ 49 | 50 | ninja 51 | ``` 52 | 53 | ### CMake Variables 54 | All demos require two CMake cache variables: 55 | - `HERMES_SRC_DIR`: Path to Hermes source checkout 56 | - `HERMES_BUILD_DIR`: Path to Hermes build directory 57 | 58 | ## Demo Architecture (in increasing complexity) 59 | 60 | ### 1. hello 61 | The simplest demo - executes embedded JavaScript code and demonstrates basic error handling. 62 | - Creates Hermes runtime with custom config 63 | - Evaluates JavaScript from a C++ string literal 64 | - Shows JSError and JSIException handling pattern 65 | 66 | ### 2. runner 67 | Executes JavaScript from an external file. 68 | - Reads JS file using `readFile()` helper 69 | - Pattern: `./runner ` 70 | 71 | ### 3. host-functions 72 | Demonstrates registering C++ functions callable from JavaScript. 73 | - Uses `registerNatives(jsi::Runtime&)` pattern 74 | - Host functions are registered into the global object 75 | - Shows how to create jsi::Function and bind C++ lambdas 76 | 77 | ### 4. hf-runner 78 | Advanced host function loading from dynamically loaded shared libraries. 79 | - Loads multiple shared libraries at runtime via `dlopen`/`LoadLibraryA` 80 | - Each library exports `registerNatives(jsi::Runtime&)` function 81 | - Pattern: `./hf-runner [...]` 82 | - Builds example libraries: `hfadd.so`, `hfmyprint.so` 83 | - Uses `extern "C"` linkage for registerNatives in shared libraries 84 | 85 | ### 5. evloop 86 | Full event loop implementation enabling setTimeout, setImmediate, and WeakRef. 87 | - Includes JavaScript library (jslib.js.inc) compiled into the binary 88 | - Implements macro task queue with C++ event loop 89 | - Uses `withMicrotaskQueue(true)` runtime config 90 | - Calls `runtime->drainMicrotasks()` after each macro task 91 | - Event loop pattern: 92 | 1. Initialize with jslib.js.inc to get `peek()` and `run()` helpers 93 | 2. Execute main JavaScript 94 | 3. Loop: check next task time, sleep if needed, run task, drain microtasks 95 | 4. Exit when no pending tasks 96 | - Can also load shared libraries like hf-runner 97 | 98 | ## Code Patterns 99 | 100 | ### Runtime Creation 101 | ```cpp 102 | auto runtimeConfig = hermes::vm::RuntimeConfig::Builder() 103 | .withIntl(false) 104 | .withMicrotaskQueue(true) // Only for evloop 105 | .build(); 106 | auto runtime = facebook::hermes::makeHermesRuntime(runtimeConfig); 107 | ``` 108 | 109 | ### Exception Handling 110 | Always catch both exception types: 111 | ```cpp 112 | try { 113 | // JSI operations 114 | } catch (jsi::JSError &e) { 115 | // JavaScript exceptions (includes stack trace) 116 | std::cerr << "JS Exception: " << e.getStack() << std::endl; 117 | } catch (jsi::JSIException &e) { 118 | // JSI API exceptions 119 | std::cerr << "JSI Exception: " << e.what() << std::endl; 120 | } 121 | ``` 122 | 123 | ### Host Function Registration 124 | For static linking (host-functions): 125 | ```cpp 126 | void registerNatives(facebook::jsi::Runtime &rt); 127 | ``` 128 | 129 | For dynamic loading (hf-runner, evloop): 130 | ```cpp 131 | extern "C" void registerNatives(facebook::jsi::Runtime &rt); 132 | ``` 133 | 134 | ### CMake Structure for Each Demo 135 | All demos follow the same CMake pattern: 136 | - Validate HERMES_SRC_DIR and HERMES_BUILD_DIR 137 | - Include directories: API, API/jsi, public 138 | - Link directories: API/hermes, jsi 139 | - Link libraries: hermes, jsi 140 | - C++17 standard required 141 | 142 | ### Platform-Specific Code 143 | - Dynamic library loading uses `#ifndef _WIN32` to handle both POSIX (`dlopen`) and Windows (`LoadLibraryA`) APIs 144 | - Windows support is not actively tested but has been considered in the code 145 | 146 | ## Working with This Codebase 147 | 148 | When modifying or creating new demos: 149 | - Follow the existing demo structure and naming conventions 150 | - Maintain C++17 compatibility 151 | - Use the standard exception handling pattern 152 | - Each demo's CMakeLists.txt should validate both HERMES_SRC_DIR and HERMES_BUILD_DIR 153 | - Register new demos in the root CMakeLists.txt and add to the `demos` target 154 | - Keep demos independent and self-contained 155 | - Use `registerNatives` naming convention for host function initialization 156 | 157 | ## Testing Changes 158 | After modifying code: 159 | 1. Clean build directory: `rm -rf ` 160 | 2. Reconfigure: `cmake -G Ninja -DHERMES_SRC_DIR=... -DHERMES_BUILD_DIR=... ` 161 | 3. Build: `ninja demos` 162 | 4. Run individual demos with their respective usage patterns 163 | -------------------------------------------------------------------------------- /evloop/evloop.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is free and unencumbered software released into the public domain. 3 | * For more information, please refer to the LICENSE file in the root directory 4 | * or at 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifndef _WIN32 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | #include 20 | 21 | using namespace ::facebook; 22 | 23 | /// The JS library that implements the event loop. 24 | static const char *s_jslib = 25 | #include "jslib.js.inc" 26 | ; 27 | 28 | /// Read the contents of a file into a string. 29 | static std::optional readFile(const char *path) { 30 | std::ifstream fileStream(path); 31 | std::stringstream stringStream; 32 | 33 | if (fileStream) { 34 | stringStream << fileStream.rdbuf(); 35 | fileStream.close(); 36 | } else { 37 | // Handle error - file opening failed 38 | std::cerr << path << ": error opening file" << std::endl; 39 | return std::nullopt; 40 | } 41 | 42 | return stringStream.str(); 43 | } 44 | 45 | /// The signature of the function that initializes the library. 46 | typedef void (*RegisterNativesFN)(jsi::Runtime &rt); 47 | 48 | #ifndef _WIN32 49 | /// Load the library and return the "registerNatives()" function. 50 | static RegisterNativesFN loadRegisterNatives(const char *libraryPath) { 51 | // Open the library. 52 | void *handle = dlopen(libraryPath, RTLD_LAZY); 53 | if (!handle) { 54 | std::cerr << "*** Cannot open library: " << dlerror() << '\n'; 55 | return nullptr; 56 | } 57 | 58 | // Clear any existing error. 59 | dlerror(); 60 | // Load the symbol (function). 61 | auto func = (RegisterNativesFN)dlsym(handle, "registerNatives"); 62 | if (const char *dlsym_error = dlerror()) { 63 | std::cerr << "Cannot load symbol 'registerNatives': " << dlsym_error 64 | << '\n'; 65 | dlclose(handle); 66 | return nullptr; 67 | } 68 | 69 | return func; 70 | } 71 | #else 72 | /// Load the library and return the "registerNatives()" function. 73 | static RegisterNativesFN loadRegisterNatives(const char *libraryPath) { 74 | // Load the library 75 | HMODULE hModule = LoadLibraryA(libraryPath); 76 | if (!hModule) { 77 | std::cerr << "Cannot open library: " << GetLastError() << '\n'; 78 | return nullptr; 79 | } 80 | 81 | // Get the function address 82 | auto func = (RegisterNativesFN)GetProcAddress(hModule, "registerNatives"); 83 | if (!func) { 84 | std::cerr << "Cannot load symbol 'registerNatives': " << GetLastError() 85 | << '\n'; 86 | FreeLibrary(hModule); 87 | return nullptr; 88 | } 89 | 90 | return func; 91 | } 92 | #endif 93 | 94 | /// Load all the libraries and call their "registerNatives()" function. 95 | /// \return true if all libraries were loaded successfully. 96 | static bool loadNativeLibraries(jsi::Runtime &rt, int argc, char **argv) { 97 | try { 98 | for (int i = 2; i < argc; i++) { 99 | auto func = loadRegisterNatives(argv[i]); 100 | if (!func) 101 | return false; 102 | func(rt); 103 | } 104 | } catch (jsi::JSIException &e) { 105 | // Handle JSI exceptions here. 106 | std::cerr << "JSI Exception: " << e.what() << std::endl; 107 | return false; 108 | } 109 | return true; 110 | } 111 | 112 | int main(int argc, char **argv) { 113 | // If no argument is provided, print usage and exit. 114 | if (argc < 2) { 115 | std::cout << "Usage: " << argv[0] << " [...]" 116 | << std::endl; 117 | return 1; 118 | } 119 | const char *jsPath = argv[1]; 120 | 121 | // Read the file. 122 | auto optCode = readFile(jsPath); 123 | if (!optCode) 124 | return 1; 125 | 126 | // You can Customize the runtime config here. 127 | auto runtimeConfig = ::hermes::vm::RuntimeConfig::Builder() 128 | .withIntl(false) 129 | .withMicrotaskQueue(true) 130 | .build(); 131 | 132 | // Create the Hermes runtime. 133 | auto runtime = facebook::hermes::makeHermesRuntime(runtimeConfig); 134 | 135 | try { 136 | // Register event loop functions and obtain the runMicroTask() helper 137 | // function. 138 | jsi::Object helpers = 139 | runtime 140 | ->evaluateJavaScript( 141 | std::make_unique(s_jslib), "jslib.js.inc") 142 | .asObject(*runtime); 143 | jsi::Function peekMacroTask = 144 | helpers.getPropertyAsFunction(*runtime, "peek"); 145 | jsi::Function runMacroTask = helpers.getPropertyAsFunction(*runtime, "run"); 146 | 147 | // There are no pending tasks, but we want to initialize the event loop 148 | // current time. 149 | { 150 | double curTimeMs = 151 | (double)std::chrono::duration_cast( 152 | std::chrono::steady_clock::now().time_since_epoch()) 153 | .count(); 154 | runMacroTask.call(*runtime, curTimeMs); 155 | } 156 | 157 | // Register host functions. 158 | if (!loadNativeLibraries(*runtime, argc, argv)) 159 | return 1; 160 | 161 | runtime->evaluateJavaScript( 162 | std::make_unique(std::move(*optCode)), jsPath); 163 | runtime->drainMicrotasks(); 164 | 165 | double nextTimeMs; 166 | 167 | // This is the event loop. Loop while there are pending tasks. 168 | while ((nextTimeMs = peekMacroTask.call(*runtime).getNumber()) >= 0) { 169 | auto now = std::chrono::steady_clock::now(); 170 | double curTimeMs = 171 | (double)std::chrono::duration_cast( 172 | now.time_since_epoch()) 173 | .count(); 174 | 175 | // If we have to, sleep until the next task is ready. 176 | if (nextTimeMs > curTimeMs) { 177 | std::this_thread::sleep_until( 178 | now + 179 | std::chrono::milliseconds((int_least64_t)(nextTimeMs - curTimeMs))); 180 | 181 | // Update the current time, since we slept. 182 | curTimeMs = 183 | (double)std::chrono::duration_cast( 184 | std::chrono::steady_clock::now().time_since_epoch()) 185 | .count(); 186 | } 187 | 188 | // Run the next task. 189 | runMacroTask.call(*runtime, curTimeMs); 190 | runtime->drainMicrotasks(); 191 | } 192 | } catch (jsi::JSError &e) { 193 | // Handle JS exceptions here. 194 | std::cerr << "JS Exception: " << e.getStack() << std::endl; 195 | return 1; 196 | } catch (jsi::JSIException &e) { 197 | // Handle JSI exceptions here. 198 | std::cerr << "JSI Exception: " << e.what() << std::endl; 199 | return 1; 200 | } 201 | 202 | return 0; 203 | } 204 | --------------------------------------------------------------------------------