Building JerryScript Arduino Library for ESP32
This tutorial explains how to cross-compile JerryScript v3.0.0 for ESP32 and package it as an Arduino-compatible library.
Overview
JerryScript is a lightweight JavaScript engine designed for microcontrollers. This guide produces a ready-to-use Arduino library that enables ECMAScript support for SCXML state machines on ESP32 devices.
What you'll create:
- Pre-compiled JerryScript static libraries for ESP32 (Xtensa architecture)
- Arduino library with proper structure and metadata
- Port implementation for Arduino/ESP32 platform
Requirements:
- Linux, macOS, or Windows with WSL2
- CMake 3.16+
- Git
- ESP-IDF toolchain (for xtensa-esp32-elf-gcc)
Step 1: Install ESP-IDF Toolchain
The ESP32 uses the Xtensa LX6 processor, which requires a specific cross-compiler.
Option A: Full ESP-IDF Installation (Recommended)
# Clone ESP-IDF
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
# Install tools for ESP32
./install.sh esp32
# Activate the environment (run this in each new terminal)
source export.sh
# Verify installation
xtensa-esp32-elf-gcc --version
Option B: Standalone Toolchain
Download from Espressif's toolchain releases:
# Linux example
wget https://github.com/espressif/crosstool-NG/releases/download/esp-12.2.0_20230208/xtensa-esp32-elf-12.2.0_20230208-x86_64-linux-gnu.tar.xz
tar -xf xtensa-esp32-elf-*.tar.xz
export PATH=$PATH:$(pwd)/xtensa-esp32-elf/bin
Step 2: Clone and Configure JerryScript
# Create working directory
mkdir -p ~/jerryscript-arduino
cd ~/jerryscript-arduino
# Clone JerryScript v3.0.0
git clone https://github.com/jerryscript-project/jerryscript.git
cd jerryscript
git checkout v3.0.0
Create ESP32 Arduino Toolchain File
cat > cmake/toolchain-esp32-arduino.cmake << 'EOF'
# CMake toolchain file for ESP32 Arduino
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR xtensa)
# Compilers from ESP-IDF toolchain
set(CMAKE_C_COMPILER xtensa-esp32-elf-gcc)
set(CMAKE_CXX_COMPILER xtensa-esp32-elf-g++)
set(CMAKE_ASM_COMPILER xtensa-esp32-elf-gcc)
# Compiler flags for ESP32
# Added -Wno-unterminated-string-initialization to bypass GCC 15 strictness
set(CMAKE_C_FLAGS_INIT "-mlongcalls -Wno-frame-address -ffunction-sections -fdata-sections -fno-exceptions -Wno-unterminated-string-initialization")
set(CMAKE_CXX_FLAGS_INIT "-mlongcalls -Wno-frame-address -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -Wno-unterminated-string-initialization")
# Don't try to run test executables on host
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Search paths
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
EOF
Step 3: Build JerryScript for ESP32
Configure Build Options
Choose heap size based on your needs:
| Heap Size | Use Case | RAM Usage |
|---|---|---|
| 32 KB | Simple expressions only | ~35 KB |
| 64 KB | Moderate complexity (recommended) | ~67 KB |
| 128 KB | Complex JS with objects/arrays | ~131 KB |
Build Commands
# Create build directory
mkdir -p build-esp32-arduino
cd build-esp32-arduino
# Configure with CMake
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-esp32-arduino.cmake \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DJERRY_CMDLINE=OFF \
-DJERRY_PORT=ON \
-DJERRY_EXT=OFF \
-DJERRY_LIBM=OFF \
-DJERRY_MATH=ON \
-DJERRY_GLOBAL_HEAP_SIZE=64 \
-DJERRY_GC_LIMIT=0 \
-DJERRY_STACK_LIMIT=4 \
-DJERRY_CPOINTER_32_BIT=ON \
-DJERRY_SYSTEM_ALLOCATOR=OFF \
-DJERRY_BUILTIN_ANNEXB=OFF \
-DJERRY_BUILTIN_BIGINT=OFF \
-DJERRY_BUILTIN_CONTAINER=OFF \
-DJERRY_BUILTIN_DATAVIEW=OFF \
-DJERRY_BUILTIN_PROXY=OFF \
-DJERRY_BUILTIN_REALMS=OFF \
-DJERRY_BUILTIN_REFLECT=OFF \
-DJERRY_BUILTIN_TYPEDARRAY=OFF \
-DJERRY_BUILTIN_WEAKREF=OFF \
-DJERRY_MODULE_SYSTEM=OFF \
-DJERRY_SNAPSHOT_EXEC=OFF \
-DJERRY_SNAPSHOT_SAVE=OFF \
-DJERRY_PARSER=ON \
-DJERRY_LINE_INFO=OFF \
-DJERRY_LOGGING=OFF \
-DJERRY_ERROR_MESSAGES=ON \
-DJERRY_VALGRIND=OFF \
-DJERRY_MEM_STATS=OFF \
-DJERRY_DEBUGGER=OFF
# Build
cmake --build . --target jerry-core jerry-port-default -- -j$(nproc)
# Verify output
ls -la lib/
# Should show:
# libjerry-core.a (~120-180 KB)
# libjerry-port-default.a (~3-5 KB)
Step 4: Create Arduino Library Structure
# Go back to working directory
cd ~/jerryscript-arduino
# Create library structure
mkdir -p JerryScript/src/esp32
mkdir -p JerryScript/examples/basic
# Copy compiled libraries
cp jerryscript/build-esp32-arduino/lib/libjerry-core.a JerryScript/src/esp32/
cp jerryscript/build-esp32-arduino/lib/libjerry-port-default.a JerryScript/src/esp32/
# Copy headers
cp jerryscript/jerry-core/include/*.h JerryScript/src/
cp jerryscript/jerry-port/include/*.h JerryScript/src/
Create library.properties
cat > JerryScript/library.properties << 'EOF'
name=JerryScript
version=3.0.0
author=JerryScript Project
maintainer=VSCXML Project
sentence=JerryScript JavaScript engine for ESP32
paragraph=Lightweight JavaScript engine (<64KB RAM) for running ECMAScript on microcontrollers. Supports ES5.1 with selected ES2015+ features.
category=Data Processing
url=https://jerryscript.net/
architectures=esp32
includes=jerryscript.h
precompiled=true
ldflags=-L{build.library_discovery_phase_flag_path}/JerryScript/src/esp32 -ljerry-core -ljerry-port-default
EOF
Create Arduino Port Implementation
cat > JerryScript/src/jerry_port_arduino.cpp << 'EOF'
/**
* JerryScript Port Implementation for Arduino/ESP32
*
* This file provides the platform-specific functions required by JerryScript
* to run on Arduino/ESP32 hardware.
*/
#include <Arduino.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include "jerryscript-port.h"
}
/* ============================================================================
* Fatal Error Handler
* ============================================================================ */
extern "C" void jerry_port_fatal(jerry_fatal_code_t code) {
Serial.print(F("[JERRY FATAL] Code: "));
Serial.println((int)code);
Serial.flush();
// Blink LED rapidly to indicate fatal error
pinMode(LED_BUILTIN, OUTPUT);
while (1) {
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
}
/* ============================================================================
* Logging
* ============================================================================ */
extern "C" void jerry_port_log(const char *message_p) {
if (message_p) {
Serial.print(message_p);
}
}
/* ============================================================================
* Date/Time Support
* ============================================================================ */
extern "C" double jerry_port_current_time(void) {
// Return milliseconds since boot
// For real date/time, integrate with NTP or RTC
return (double)millis();
}
extern "C" int32_t jerry_port_local_tza(double unix_ms) {
(void)unix_ms;
// Return timezone offset in milliseconds (0 = UTC)
// Adjust for your timezone if needed
return 0;
}
/* ============================================================================
* Sleep (for async operations)
* ============================================================================ */
extern "C" void jerry_port_sleep(uint32_t sleep_time_ms) {
delay(sleep_time_ms);
}
/* ============================================================================
* Path Operations (stubs - not used on Arduino)
* ============================================================================ */
extern "C" jerry_size_t jerry_port_path_normalize(const jerry_char_t *in_path_p,
jerry_char_t *out_buf_p,
jerry_size_t out_buf_size) {
(void)in_path_p;
(void)out_buf_p;
(void)out_buf_size;
return 0;
}
extern "C" jerry_size_t jerry_port_path_base(const jerry_char_t *path_p) {
(void)path_p;
return 0;
}
/* ============================================================================
* Context Allocation
* ============================================================================ */
extern "C" void *jerry_port_context_alloc(size_t size) {
return malloc(size);
}
extern "C" void jerry_port_context_free(void *context_p) {
free(context_p);
}
EOF
Create Basic Example
cat > JerryScript/examples/basic/basic.ino << 'EOF'
/**
* JerryScript Basic Example
*
* Demonstrates running JavaScript code on ESP32 using JerryScript.
*/
#include <jerryscript.h>
void setup() {
Serial.begin(115200);
while (!Serial) delay(10);
Serial.println("\n=== JerryScript Basic Example ===");
// Initialize JerryScript engine
jerry_init(JERRY_INIT_EMPTY);
// Print version
Serial.print("JerryScript version: ");
Serial.print(JERRY_API_MAJOR_VERSION);
Serial.print(".");
Serial.print(JERRY_API_MINOR_VERSION);
Serial.print(".");
Serial.println(JERRY_API_PATCH_VERSION);
// Run simple JavaScript
const char* script = "var x = 40 + 2; x;";
jerry_value_t parsed = jerry_parse(
(const jerry_char_t*)script,
strlen(script),
NULL
);
if (jerry_value_is_exception(parsed)) {
Serial.println("Parse error!");
} else {
jerry_value_t result = jerry_run(parsed);
if (jerry_value_is_exception(result)) {
Serial.println("Runtime error!");
} else {
double num = jerry_value_as_number(result);
Serial.print("Result of '40 + 2': ");
Serial.println(num);
}
jerry_value_free(result);
}
jerry_value_free(parsed);
// Test string operations
const char* str_script = "'Hello ' + 'ESP32!'";
parsed = jerry_parse(
(const jerry_char_t*)str_script,
strlen(str_script),
NULL
);
if (!jerry_value_is_exception(parsed)) {
jerry_value_t result = jerry_run(parsed);
if (!jerry_value_is_exception(result)) {
jerry_value_t str_val = jerry_value_to_string(result);
jerry_size_t str_size = jerry_string_size(str_val, JERRY_ENCODING_UTF8);
char* buffer = (char*)malloc(str_size + 1);
jerry_string_to_buffer(str_val, JERRY_ENCODING_UTF8,
(jerry_char_t*)buffer, str_size);
buffer[str_size] = '\0';
Serial.print("String result: ");
Serial.println(buffer);
free(buffer);
jerry_value_free(str_val);
}
jerry_value_free(result);
}
jerry_value_free(parsed);
// Cleanup
jerry_cleanup();
Serial.println("\nJerryScript test complete!");
}
void loop() {
delay(1000);
}
EOF
Step 5: Create Distribution ZIP
cd ~/jerryscript-arduino
# Create ZIP for distribution
zip -r JerryScript-ESP32-v3.0.0.zip JerryScript/
# Verify contents
unzip -l JerryScript-ESP32-v3.0.0.zip
Expected output:
Archive: JerryScript-ESP32-v3.0.0.zip
Length Date Time Name
--------- ---------- ----- ----
0 2024-XX-XX XX:XX JerryScript/
XXX 2024-XX-XX XX:XX JerryScript/library.properties
0 2024-XX-XX XX:XX JerryScript/src/
XXXXXX 2024-XX-XX XX:XX JerryScript/src/esp32/libjerry-core.a
XXXX 2024-XX-XX XX:XX JerryScript/src/esp32/libjerry-port-default.a
XXXX 2024-XX-XX XX:XX JerryScript/src/jerryscript.h
XXXX 2024-XX-XX XX:XX JerryScript/src/jerryscript-core.h
...
Step 6: Install in Arduino IDE
Method 1: ZIP Import (Recommended)
- Open Arduino IDE
- Go to Sketch → Include Library → Add .ZIP Library...
- Select
JerryScript-ESP32-v3.0.0.zip - Restart Arduino IDE
Method 2: Manual Installation
Copy the JerryScript folder to your Arduino libraries directory:
| Platform | Location |
|---|---|
| Windows | C:\Users\<username>\Documents\Arduino\libraries\ |
| macOS | ~/Documents/Arduino/libraries/ |
| Linux | ~/Arduino/libraries/ |
Verification
- Open Arduino IDE
- Go to File → Examples → JerryScript → basic
- Select your ESP32 board
- Upload and open Serial Monitor (115200 baud)
Expected output:
=== JerryScript Basic Example ===
JerryScript version: 3.0.0
Result of '40 + 2': 42.00
String result: Hello ESP32!
JerryScript test complete!
Build Configuration Reference
Heap Size Options
| CMake Flag | Effect |
|---|---|
-DJERRY_GLOBAL_HEAP_SIZE=32 |
32 KB heap, minimal JS |
-DJERRY_GLOBAL_HEAP_SIZE=64 |
64 KB heap, moderate JS |
-DJERRY_GLOBAL_HEAP_SIZE=128 |
128 KB heap, complex JS |
Feature Flags
| Flag | Default | Description |
|---|---|---|
JERRY_BUILTIN_ARRAY |
ON | Array methods |
JERRY_BUILTIN_DATE |
ON | Date object |
JERRY_BUILTIN_JSON |
ON | JSON.parse/stringify |
JERRY_BUILTIN_MATH |
ON | Math functions |
JERRY_BUILTIN_REGEXP |
ON | Regular expressions |
JERRY_ERROR_MESSAGES |
ON | Detailed error messages |
JERRY_PARSER |
ON | Runtime JS parsing |
To reduce binary size, disable unused features:
-DJERRY_BUILTIN_REGEXP=OFF # Saves ~20KB if not using regex
-DJERRY_ERROR_MESSAGES=OFF # Saves ~10KB
Troubleshooting
"xtensa-esp32-elf-gcc: command not found"
Ensure ESP-IDF environment is activated:
source ~/esp/esp-idf/export.sh
Build fails with "cannot find -lgcc"
The toolchain path is incorrect. Verify with:
which xtensa-esp32-elf-gcc
xtensa-esp32-elf-gcc -print-libgcc-file-name
Arduino IDE doesn't find the library
- Check
library.propertiesexists and has correct syntax - Ensure
architectures=esp32is set - Restart Arduino IDE completely
"undefined reference to jerry_init"
The precompiled library isn't being linked. Check:
.afiles are insrc/esp32/folderprecompiled=truein library.propertiesldflagspath is correct
Next Steps
- See Using JerryScript with SCXML on ESP32 Nano for integration with SCXML state machines
- See SCXML ECMAScript Datamodel Reference for API details