Unpacking the internal hermesc pipeline: frontend, IR, optimizer, and code generation, how bytecode is produced then serialized, plus the custom build flags and optimization tiers you can control.

Episode 15 taught interpreter-friendly code patterns. Now we reverse direction: not how code is executed, but how it gets compiled. hermesc is a name you've typed often, but what actually happens inside it? Episode 16 opens the compiler's hood.
This episode's roadmap: the hermesc pipeline from frontend to code generation, how bytecode is produced then serialized, then custom build flags and optimization tiers.
hermesc works in a sequence of stages. Each stage turns the code's representation into a form closer to the machine:
JavaScript source
-> parser -> AST
-> semantik (validasi scope & aturan bahasa)
-> HBC lowering -> IR
-> optimizer passes
-> bytecode writer
-> serialisasi -> file .hbcEach stage can be unpacked and studied — that's why the Hermes compiler feels approachable to dig into, unlike a very large runtime compiler. Let's walk through each one.
No JIT slips into this pipeline — the result is always deterministic bytecode. That means two compilations from the same source and flags produce identical bytecode. This property is what makes reproducible builds, as discussed in episode 11, actually feasible in the real world.
The first step turns JavaScript text into an abstract syntax tree, aka the AST. The Hermes parser translates every token — function, arrow, async, const — into AST nodes. After parsing, the compiler does semantic validation: checking scopes, strict mode writing, and rules that syntax alone can't capture.
You can see the parse result with the -dump-ast flag:
hermesc -dump-ast -out /dev/null app.jsThe output is a tree representation of your code. It's useful when you're curious how a particular syntax construct is understood by the compiler — for example, how async/await gets lowered into a state machine.
The Hermes parser applies the same rules as the ECMAScript standard, so parsing behavior doesn't diverge from other engines. What deserves attention is the layer below, where optimization differences start to show.
If the parser finds invalid syntax, compilation stops at this stage — no bytecode is produced. Errors at this stage are the easiest to read because they point directly at the original source line.
The AST is still too high-level to compile directly. Hermes lowers the code into a lower-level Intermediate Representation (IR) — roughly like three-address code in a classic compiler. This is the level where the optimizer works. See the result with -dump-ir:
hermesc -dump-ir -O -out /dev/null app.jsThe optimizer runs a series of passes: removing dead code, folding constants, and discarding expressions with no side effects. The -O flag activates optimization tiers — the higher the level, the more aggressive the passes run, and usually the longer the compilation takes. That's the trade-off to think about when choosing build options in CI.
Interestingly, the IR is also where Hermes performs optimizations related to the interpreter's character, for example rearranging instruction order so register access is friendlier. This part is what makes hermesc different from a plain transpiler.
From the optimized IR, the code generator writes Hermes bytecode: the sequence of opcodes the interpreter will execute. Each function is compiled into an instruction segment with an information table — argument count, register count, variable locations. Then serialization merges all the segments into a single .hbc file with:
The stored structure can be inspected with hbcdump. Its summary is shown via hbcdump -summary app.hbc:
hbcdump -summary app.hbcThe -c flag shows opcode-by-opcode disassembly. Watching how a small function gets translated into instructions is the fastest way to understand why certain code shapes are faster on the interpreter.
Besides disassembly, hbcdump can show the string table and function statistics. Reading all three together gives the full picture: which instructions are used, which literals are stored, and how much each function contributes to the bundle size.
Info
Because serialization produces a documented binary format, Hermes bytecode is an auditable artifact — but remember, it's not encryption, as emphasized in episodes 12 and 13.
hermesc gives fairly granular control through flags. Some frequently used ones:
-O through -O3: optimization levels; without a flag, the compiler runs without optimizer passes.-emit-binary: writes bytecode, not just validates syntax.-output-source-map: produces the bytecode-to-source mapping, as in episode 10.-dump-ast and -dump-ir: unpack intermediate representations for compiler debugging.All these flags can be combined in a single invocation, and the order you write them doesn't affect the result. What matters is the hermesc version and the set of flags used — that's why you should store your chosen combination as committed configuration.
hermesc -O -emit-binary -out app.hbc app.js
hermesc -O2 -emit-binary -out app.hbc app.jsRecommended practice: keep using -O or higher for release, standardize it through committed configuration, as in episode 11, and document the tier used so bytecode across builds can be compared fairly. If you suspect a compiler bug, the -dump-ir and hbcdump -c pair is your first forensic tool.
One note: a higher tier doesn't always mean smaller bytecode. Some passes sacrifice size for execution speed, and vice versa. Measure both on a real app before deciding which tier becomes the team standard.
You now see hermesc from the inside: the AST from the parser, the IR where the optimizer works, code generation that writes opcodes, and serialization that produces a .hbc file with a structured header and tables. With an understanding of flags and tiers, you no longer treat the compiler as a black box.
The essentials to take home:
hermesc pipeline consists of a parser, semantic validation, lowering to IR, an optimizer, and a bytecode writer.-dump-ast and -dump-ir unpack intermediate representations for analysis..hbc serialization stores a header, version number, string table, and function table locked to the engine version.hbcdump is the bytecode inspection tool: summary for an overview, the -c flag for disassembly.In episode 17 we dive deeper into memory: Advanced Memory & GC Tuning — benchmarking heap behavior and GC pauses, GC configuration for low-memory devices, plus profiling memory leaks and fragmentation. See you there!