The FlutterJS Runtime (@flutterjs/runtime) is the JavaScript engine that executes your application in the browser. It mimics the behavior of the Flutter framework but is optimized for the DOM.
The runtime bridges the gap between the static Widget definition and the dynamic DOM updates.
graph TD
Widget[Widget<br>Immutable Config] --> Element[Element<br>Mutable Agent]
Element --> State[State<br>Local Data]
Element --> Render[Render Object<br>VNode]
Render --> DOM[Browser DOM<br>HTML/CSS]
Just like in Flutter, there are three trees:
- Widget Tree: Immutable configuration (blueprint). Rebuilt frequently.
- Element Tree: Mutable instances that manage the lifecycle. Persistent.
- Render Tree: The visual description. In FlutterJS, this is the VNode Tree.
The base class for elements that compose other widgets.
StatelessElement: ForStatelessWidget. It simply callsbuild().StatefulElement: ForStatefulWidget. It creates and holds aStateobject. It callsstate.build().
(Conceptual in FlutterJS) These are elements that actually create VNodes (like RawVNode or leaf widgets).
The State class works exactly like in Flutter.
class MyState extends State {
initState() {
// Called once upon mounting
}
didUpdateWidget(oldWidget) {
// Called when parent rebuilds and sends new configuration
}
setState(fn) {
// 1. Run the update function
// 2. Mark this element as 'dirty'
// 3. Schedule a frame
}
dispose() {
// Called when unmounting
}
}BuildContext is actually the Element itself. It allows widgets to look up the tree.
context.findAncestorWidgetOfExactType(Theme): Used forTheme.of(context).
The runtime uses a batching scheduler to ensure efficient updates, similar to Flutter's "frame" concept.
- Event: User interaction or network event calls
setState(). - Mark Dirty: The element key is added to a
dirtyElementsset. - Schedule: A microtask (Promise) is scheduled if not already pending.
- Flush:
- The runtime iterates through
dirtyElements. - It calls
rebuild()on each. rebuild()callsbuild()implementation (widget's build method).- The new Widget tree is compared to the old one.
- If widget type/key matches -> Update element.
- If different -> Unmount old, Mount new.
- The runtime iterates through
- Render: The resulting VNode tree is patched to the DOM.
To avoid "layout thrashing" where the browser recalculates layout multiple times:
// User code
setState(() => a++);
setState(() => b++);
setState(() => c++);
// Runtime
// 1. Adds 'a' to dirty list
// 2. Adds 'b' to dirty list
// 3. Adds 'c' to dirty list
// 4. End of event loop -> FLUSH ONCEThis ensures O(1) rendering cost per frame regardless of how many setState calls were made.