├── .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 |
--------------------------------------------------------------------------------