Installation
Requirements
- Node.js 22.18 or newer. That is everything the simulator needs. The engine ships prebuilt as WebAssembly inside the npm package, so there is no native toolchain to install.
- A firmware toolchain (ESP-IDF, the Pico SDK, …) only once you flash a board.
Create a project
- JavaScript
- TypeScript
npm create embedded-react@latest my-app
npm create embedded-react@latest my-app -- --ts
The TypeScript starter adds a tsconfig.json, type declarations for asset imports (.png,
.ttf, …) and an npm run typecheck script.
Then install and start the simulator:
cd my-app
npm install
npm run dev
npm run dev serves the simulator at http://localhost:3333. The next page,
Run it in the simulator, covers what you can do there.
Start from a demo
The starter is deliberately small: a card with a pulsing logo, a counter-button, and the panel size laid out to fit any panel. To begin from a complete app instead, pass a template:
npm create embedded-react@latest my-thermostat -- --template thermostat
npm create embedded-react@latest my-watch -- --template watch-face
| Template | What you get |
|---|---|
starter | The minimal starter (the default) |
starter-ts | The starter in TypeScript (same as --ts) |
thermostat | A thermostat with a draggable dial and a 14-day weather panel |
watch-face | A digital watch face and a bubble level, sized for a 240×280 panel |
npm create embedded-react@latest -- --list prints the current list.
Every template builds for the ahead-of-time flow. The thermostat and watch-face templates also
come with a ready-made build:aot script. See
Your first board.
What you get
my-app/
index.jsx entry point: registers <App/>
App.jsx your UI
assets/ images and fonts; import them and they are baked automatically
package.json
The project depends on two packages: embedded-react and react. Its scripts wrap the
embedded-react command-line tool:
| Script | Runs | What it does |
|---|---|---|
npm run dev | embedded-react dev | The browser simulator, with hot reload |
npm run dev:device | embedded-react dev --device | Hot reload on a connected board over USB (Flow A) |
npm run build | embedded-react build | The device artifact for Flow A: dist/app.erpkg |
npm run export | embedded-react export | A self-contained static copy of the simulator, to share or host |
npx embedded-react --help lists every command and flag.
Adding to an existing project
npm install embedded-react react@18.3.1
embedded-react pins react to 18.3.1. If your project resolves a different version, you end up
with two copies of React, and the app renders a blank screen with a useRef of null error.
Add an entry file that registers your root component, then run npx embedded-react dev:
import {AppRegistry} from 'embedded-react';
import {App} from './App.jsx';
AppRegistry.registerComponent('my-app', () => App);
The CLI looks for index.jsx, index.tsx, then App.jsx (each at the root, then under src/),
then the source or main field of package.json. build --aot looks for App.jsx or App.tsx
instead. Pass a path to use a different entry: npx embedded-react dev path/to/entry.jsx.
The engine, for firmware
The npm package is the app side. The firmware side needs the C engine, plus the Flow B runtime for Flow B. They ship at the same version number through the channels below. You only need this once you are wiring up a board; the example projects already do it for you.
- CMake
- ESP-IDF
- PlatformIO
include(FetchContent)
FetchContent_Declare(embedded-react
GIT_REPOSITORY https://github.com/TheMasterCoder007/embedded-react.git
GIT_TAG v0.20.0
SOURCE_SUBDIR engine)
FetchContent_MakeAvailable(embedded-react)
target_link_libraries(my_firmware PRIVATE embedded-react)
For Flow B, add the runtime from the same checkout:
add_subdirectory(${embedded-react_SOURCE_DIR}/runtime ${embedded-react_BINARY_DIR}/runtime)
target_link_libraries(my_firmware PRIVATE erb-runtime)
The CYD and ESP32-S3 examples are templates you can
copy out. They use CMake FetchContent, which also brings in the Flow B runtime, the display backend and, for Flow A,
the bridge and QuickJS.
The runtime is also on the ESP-IDF Component Registry, and brings the engine with it:
idf.py add-dependency "TheMasterCoder007/embedded-react-runtime^0.20.0"
The engine on its own is a separate component:
idf.py add-dependency "TheMasterCoder007/embedded-react^0.20.0"
Neither includes a display backend, so you supply your own. The engine's default pools are sized for a desktop and won't
fit in an ESP32's RAM, so set smaller ones in your project's CMakeLists.txt, after the include of project.cmake and
before project(). This example leaves the runtime's ERB_* limits at their defaults; set those the same way to change
them:
idf_build_set_property(COMPILE_DEFINITIONS "ERUI_MAX_NODES=48;ERUI_SCRATCH_WIDTH=8;ERUI_SCRATCH_HEIGHT=8;ERUI_VECTOR_EDGE_CACHE=0" APPEND)
lib_deps = https://github.com/TheMasterCoder007/embedded-react.git#v0.20.0
This installs the engine and the Flow B runtime as one library, so erb_load is ready to link. The QuickJS bridge isn't
in the library yet, so Flow A goes through the ESP-IDF examples.
The engine's default pools are sized for a desktop, so on an ESP32 set smaller ones in platformio.ini:
build_flags = -DERUI_MAX_NODES=48 -DERUI_SCRATCH_WIDTH=8 -DERUI_SCRATCH_HEIGHT=8 -DERUI_VECTOR_EDGE_CACHE=0
The engine is backend-agnostic: you supply the code that pushes pixels to your display. See Engine and backends.