Hermes is a JavaScript engine built for fast start-up and low memory use. Its whole design centers on compiling JavaScript ahead of time: apps ship compact bytecode, or optionally native code, and the engine does as little work as possible when they launch. Hermes was created for React Native, where it is the default engine. It also runs standalone or can be embedded as a library in other programs.
Most engines parse and compile JavaScript on the user's device, every time the app starts. Hermes moves that work to build time. The compiler parses, analyzes and optimizes the entire program once, and emits bytecode that the engine can memory-map and start executing immediately. Start-up cost, memory and binary size all benefit, and the optimizer can see the whole program instead of one function at a time.
In practice this means:
- Ship bytecode. Compile at build time and load the result. This is how React Native apps use Hermes, and it is the path that gets all the optimizations.
- Source still runs. The engine includes the compiler, so
.jsfiles can be run directly during development. It is not the intended production path. eval()andnew Function()are supported for small things. They run the compiler at runtime. Code passed toevalsees the global scope only; giving it access to the enclosing function's locals would force the compiler to pessimize every function, so that is deliberately left out.
Hermes can execute JavaScript in four ways, and they can be mixed freely within one runtime.
- From source. The engine can also be given JavaScript source, which it compiles to bytecode before running. By default, large sources are compiled lazily, one function at a time when it is first called. Good for development and for code generated at runtime.
- From bytecode. The production path. Code is compiled ahead of time, the bytecode file is memory-mapped, and the interpreter runs it. This is how React Native apps run.
- JIT. An optional baseline JIT compiles frequently executed functions to machine code while the app runs. It is deliberately simple; the heavy optimization already happened at build time, so the JIT stays small and predictable.
- Native code. The
shermestool compiles JavaScript ahead of time all the way to native code, with no interpreter involved.
An app can ship some code as native, some as bytecode, and generate some at runtime, and the JIT will pick up hot functions from the latter two.
Hermes is gaining the ability to use type annotations to compile JavaScript to much faster code. This is under active development and not ready for use. Both Flow and TypeScript syntax are being supported, to different degrees. Flow is the more static-typing-friendly of the two, so TypeScript is currently converted to Flow by an AST pass contributed by Amazon. That is an implementation detail and may change; TypeScript support is a priority.
Neither the Flow checker nor the TypeScript compiler guarantees that the annotations are true at runtime. Both accept this program, for example:
const a: number[] = [];
const n: number = a[0]; // the type says number, the value is undefinedA typed compiler has to strengthen the language semantics until the types
are actually true. In this example, typed Hermes range-checks array indexing
and throws on out-of-bounds access, so a[0] can never produce a value that
contradicts its type. Where a value of unknown type flows into a typed one,
for example from any or from untyped code, the compiler inserts a checked
cast, which throws at runtime if the value is not of the expected type. With
those guarantees in place, the generated code can rely on the types.
TypeScript is less friendly to static compilation than Flow because its typing is structural, and it is unsound in more places. This program type-checks in TypeScript and fails at runtime; Flow rejects it because object properties are invariant:
class Animal {}
class Dog extends Animal { bark() {} }
class Cat extends Animal {}
const d: { pet: Dog } = { pet: new Dog() };
const a: { pet: Animal } = d; // TypeScript accepts, Flow rejects
a.pet = new Cat();
d.pet.bark(); // d.pet is a Cat: "bark is not a function"In TypeScript a value of type Dog need not be an instance of Dog at all;
any object with the same shape qualifies. A compiler can therefore not derive
an object layout from a class, which is exactly what makes typed code fast.
See doc/TypedLanguage.md for the current state.
Independently from this, TypeScript can already be run in untyped, unsound mode: the type annotations are stripped and the result is treated as plain JavaScript. See doc/typescript-stripping.md.
Hermes can also compile JavaScript ahead of time to native code, using the
shermes tool. It works both on ordinary untyped JavaScript and on typed
JavaScript, where the latter produces much better native code.
"Static Hermes" was the code name for an umbrella project, developed on the
static_h branch, to add static typing and optional native compilation to
Hermes. The branch then became the main development line and gained the JIT,
modern ECMAScript features, and a large amount of unrelated work. It is not a
separate engine; it is Hermes.
Hermes releases had always been numbered 0.x. When the static_h line was
judged ready, its releases moved to a new scheme built from the branch date and
bytecode version (for example 260318099.0.0), and React Native calls it
Hermes V1 to distinguish it from the legacy line on the main branch.
See doc/Branches.md.
You need a C++17 compiler, CMake, Ninja and Python. Clang is strongly recommended; gcc is supported but produces noticeably worse code. On Ubuntu:
apt install clang build-essential cmake git ninja-build python3 tzdata libreadline-devClone and build the two command line tools, hermes (compile to bytecode, run
bytecode or source) and shermes (compile to native):
git clone https://github.com/facebook/hermes.git
cmake -S hermes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build --target hermes shermesRun a script from source, then the way apps do it, from bytecode:
echo "print('Hello from Hermes');" > hello.js
build/bin/hermes hello.js
build/bin/hermes -emit-binary -out hello.hbc hello.js
build/bin/hermes hello.hbcCompile the same script to a native executable:
build/bin/shermes -O -o hello hello.js
./helloEverything else, including macOS, Windows, release builds and running the test suites, is in doc/BuildingAndRunning.md.
Integration documentation is thin today and will grow. These are the starting points:
- Embedding through JSI. JSI is the stable C++ API for hosting the engine and exchanging values and functions with JavaScript. Start with hermes-jsi-demos, a set of small standalone CMake projects, from hello world to a working event loop. The API itself is API/jsi/jsi/jsi.h and the Hermes entry point is API/hermes/hermes.h.
- Node-API. Native addons written against the Node-API ABI run on Hermes without modification. See API/napi/README.md.
- React Native. Hermes is the default engine. See doc/ReactNative.md.
- Running in a browser. Hermes itself can be compiled to Wasm with
Emscripten, so it runs in the browser and in other Wasm hosts. JavaScript
compiled with
shermescan be built to Wasm the same way. See doc/Emscripten.md.
- Design, VM, IR, Optimizer, Hades GC
- Language features and known spec incompatibilities
- Lazy and eval compilation, Strings, RegExp, Intl
- Performance profiling and memory profilers
- Cross compilation
- The Hermes blog: compilation and runtime modes, memory modes, JSON performance, JSI additions and release notes.
Contributions are welcome. The preferred way to add functionality is a JSI extension: extensions are written against the stable JSI API rather than engine internals, so they are safer to write and do not break when the engine changes. Read CONTRIBUTING.md for the development process and how to propose changes.
This project follows the Meta Code of Conduct. Hermes is MIT licensed.