Skip to main content

js_widget_runtime 0.4.191 copy "js_widget_runtime: ^0.4.191" to clipboard
js_widget_runtime: ^0.4.191 copied to clipboard

Run JavaScript widgets as Flutter widgets using QuickJS (VM) or Web Workers (web).

js_widget_runtime #

js_widget_runtime icon — a JavaScript tile driving a stack of Flutter widget bars

CI pub package pub points platforms coverage ≥ 85% CRAP

A Flutter package that runs JavaScript widgets and renders them as native Flutter UI.

  • VM/desktop/mobile: uses flutter_js (QuickJS / JavaScriptCore).
  • Web: uses a dedicated web.Worker spawned from an inline Blob URL.
  • Opt-in QuickJS FFI backend (VM only): swap flutter_js for the quickjs_runtime package to get fully synchronous host calls — build the native library with bash <quickjs_runtime>/tool/build_quickjs.sh, then pass QuickjsWidgetEngineBackend via JsRuntimeConfig.backend.

The JS side communicates with Flutter through a declarative JSON UI tree and a small async bridge (render, fetchJson, exec, storage, secrets, timers, requestAnimationFrame, etc.).

▶ Live demos — click through the widget gallery in your browser: each demo runs in the real engine (Web Worker backend) on GitHub Pages.

Example widgets #

Real JavaScript, rendered by this package and captured by golden tests — the images below are generated straight from those test runs. Live, interactive versions ship in the example app and run in the browser on the live demos page.

yolo-hello calculator
weather stocks
crypto animation-showcase
map showcase: bounce
showcase: card stack showcase: colors
3D showcase 3D game: dodge
GLB showcase fitness trainer
M3 showcase M3 showcase: controls
M3: bottom sheet M3: dialog
charts showcase M3: date picker
pomodoro audio player
video player webview
adaptive dashboard 3D model viewer
THREE Minecraft

yolo-hello (animated gradient + bounce) · calculator (full keyboard logic in JS) · weather (wttr.in fetch) · stocks (live quotes) · crypto (price tickers) · map (OpenStreetMap landmarks) · animation-showcase (menu + per-scene captures from the interactive golden: fade / morph / bounce / cards / drag / pulse / colors) · 3d-showcase (procedural primitives, flutter_cube — shape variants under doc/widgets/3d-showcase-.png) · 3d-game-dodge (dodge-the-blocks game) · 3d-glb-showcase (GLB model with PBR) · fitness-trainer (skeletal-coach workout, flame_3d) · m3-showcase (Material 3 nodes: drawer, appBar, banner, searchBar, segmentedButton, radio, navigationRail, carousel, bottomAppBar, tabBar, fab, popupMenu, navigationBar + overlays: bottomSheet, dialog, snackBar, datePicker, timePicker — extra state frames under doc/widgets/m3-.png) · charts-showcase (fl_chart via the flChart node: line, bar, pie, radar, scatter) · pomodoro (25/5 focus/break cycles, donut ring via flChart pie, completed counter persisted in jsr.storage) · audio-player (playlist transport via the zero-size audio_player node + host JsMediaHost) · video-player (video node, cinematic hero card + SVG source/BoxFit switcher) · 3d-viewer (GLB viewer with control-room UI, SVG control bar) · webview-showcase (webView node, host JsWebViewHost, page-to-widget message bridge) · adaptive-dashboard (adaptive node + gridView.maxCrossAxisExtent + jsr.breakpoint/jsr.adaptive/jsr.onViewport) · three-minecraft (official upstream r186 module on the opt-in THREE host; dedicated three_example.dart runner)

Web preview runner #

example/lib/preview.dart is a web-only entry point that renders a single widget full-bleed through the real engine (Web Worker backend) — built for hosting live widget previews behind an iframe:

cd example && flutter build web -t lib/preview.dart --base-href /widgets/preview/

URL contract: ?widget=<id>&theme=dark|light, with widget files fetched over HTTP (?base=<url> to override the source). fetch is enabled only for widgets whose manifest opts into "network": true. CI (.github/workflows/preview-web.yml) publishes the build as the jsr-preview-web artifact.

The runner also powers the live demo site on GitHub Pages — https://istin.github.io/flutter_js_widget_runtime/ — deployed by .github/workflows/pages.yml: a landing page (site/index.html, widget cards built from the fa_widgets catalog) at the root plus the runner under /preview/ (base-href /flutter_js_widget_runtime/preview/).

Quick start #

flutter pub add js_widget_runtime
import 'package:flutter/material.dart';
import 'package:js_widget_runtime/js_widget_runtime.dart';

class DemoPage extends StatelessWidget {
  const DemoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return JsWidgetRuntime(
      jsSource: r'''
(function() {
  jsr.render({
    type: 'center',
    child: {
      type: 'column',
      mainAxisSize: 'min',
      children: [
        {type: 'text', data: 'Hello from JS!', style: {fontSize: 24}},
        {type: 'sizedBox', height: 12},
        {
          type: 'textButton',
          text: 'Tap me',
          onTap: 'tap',
        },
      ],
    },
  });
  jsr.onEvent(function(actionId, payload) {
    if (actionId === 'tap') {
      jsr.setTitle('Tapped!');
    }
  });
})();
''',
      runtimeConfig: JsRuntimeConfig(
        onRender: (tree) => print('render: $tree'),
        onSetTitle: (title) => print('title: $title'),
        onStorageUpdate: (storage) => print('storage: $storage'),
      ),
    );
  }
}

See the example/ folder for a runnable menu of sample JS apps.

3D scenes and GLB models #

js_widget_runtime does not bundle a 3D engine. Instead it exposes a Js3dHost interface so the host app can plug in any engine (Flame 3D, three_dart, flutter_3d_controller, etc.).

JsRuntimeConfig(
  js3dHost: const My3dHost(),
  // ... other handlers
);

From JS, create a scene and manipulate it:

jsr.scene3d.create('main', {
  camera: { position: [0, 2, 5], target: [0, 0, 0] },
  lights: [{ type: 'ambient', color: '#ffffff', intensity: 0.5 }]
});

jsr.render({
  type: 'scene3d',
  id: 'main',
  width: 320,
  height: 320
});

jsr.scene3d.addModel('main', {
  id: 'player',
  src: 'assets/player.glb'
});

jsr.scene3d.playAnimation('main', 'player', 'run');

jsr.scene3d.setTransform('main', 'player', {
  position: [1, 0, 0],
  rotation: [0, 45, 0],
  scale: [1.5, 1.5, 1.5]
});

The example/ app includes a 3d-viewer widget powered by flutter_3d_controller.

Upstream three.js r186 (opt-in) #

Upstream THREE is independent of the older scene3d API. Enable its pinned bundle explicitly and inject the platform host; ordinary widgets do not load or evaluate three.js:

final threeHost = kIsWeb
    ? createWebThreeHost()
    : createFlutterGpuThreeHost();

final config = JsRuntimeConfig(
  threeEnabled: true,
  threeHost: threeHost,
  // ... required render/title/storage callbacks
);
const canvas = jsr.three.createCanvas('main');
const renderer = new THREE.WebGLRenderer({canvas: canvas});
// Use the real upstream scene/geometry/material objects, then mount it:
jsr.render({type: 'three', canvasId: 'main', width: 640, height: 360});
  • Native (1A): upstream JS object model on QuickJS/JSC, dirty versioned resources over the binary JSR3D-T1/R1-v2 protocol (the decoder also accepts legacy R1-v1), and Flutter GPU shaders behind JsThreeHost. WebGLRenderer is an explicit R1 alias; getContext(), raw WebGL, ShaderMaterial and WebGPU report three.unsupported. No WebView is involved.
  • Web (1B): the real upstream WebGLRenderer acquires WebGL2 on a Worker-owned OffscreenCanvas. Transferable ImageBitmap frames (bounded to two pending) are presented through HtmlElementView canvases; canvas/GL handles never cross the JSON bridge.
  • Native THREE.InstancedMesh uses R1 DRAW_BATCH: real instance matrices are uploaded once and drawn with GPU instancing, including indexed multi-material parts. Transparent instancing and per-instance colors are outside the initial R1 profile. Diagnostics count triangles across all instances, not just the base geometry.
  • Large indexed single-material meshes use a cached BVH for native raycasts; upstream THREE still performs the final triangle/UV/normal/material tests. Attribute/array identity and versions invalidate the cache, while morph, skinned, non-indexed and bounded-draw-range cases retain the stock path.
  • Native R1 includes Blinn-Phong materials and unique-edge mesh wireframes. Its bounded Flutter Canvas2D path rasterizes concentric radial gradients and fillRect into versioned CanvasTexture RGBA uploads; unsupported canvas operations remain explicit three.unsupported failures. Web keeps using the real OffscreenCanvas implementation.
  • Lighting includes ambient, directional, point and real hemisphere diffuse irradiance (independent sky/ground colors); Normal and emissive materials have their own native GPU paths rather than Lambert aliases.
  • Resources are isolated by (engineId, surfaceId, resourceId). Mount, resize/DPR, pointer/key/cancel, capture and disposal are host-managed. Prepared local assets use jsr.three.registerAsset; host asset failures are named three.assetFailure.
  • The pin is r186, peeled commit 9b4a2ac29c63ccb43fd51c5661f2f873ac2c39b8 (tag object 48dd7be01263658aad497fd72e09d1e4c6213461). Build manifests and the MIT notice ship under assets/three/.

Rebuild and run an official example from the pinned local source:

dart run bin/jsr_three_example.dart prepare webgl_geometry_minecraft
dart run bin/jsr_three_example.dart prepare webgl_lines_colors
dart run bin/jsr_three_example.dart prepare webgl_geometry_colors
dart run bin/jsr_three_example.dart prepare webgl_buffergeometry_uint
dart run bin/jsr_three_example.dart prepare webgl_buffergeometry_indexed
dart run bin/jsr_three_example.dart prepare webgl_buffergeometry_lines_indexed
dart run bin/jsr_three_example.dart prepare webgl_buffergeometry_points
dart run bin/jsr_three_example.dart prepare webgl_geometry_terrain_raycast
dart run bin/jsr_three_example.dart prepare webgl_math_orientation_transform
dart run bin/jsr_three_example.dart prepare webgl_interactive_raycasting_points
dart run bin/jsr_three_example.dart prepare webgl_interactive_cubes
dart run bin/jsr_three_example.dart prepare webgl_interactive_cubes_ortho
dart run bin/jsr_three_example.dart prepare webgl_math_obb
dart run bin/jsr_three_example.dart prepare webgl_buffergeometry_points_interleaved
dart run bin/jsr_three_example.dart prepare webgl_interactive_voxelpainter
dart run bin/jsr_three_example.dart prepare webgl_panorama_equirectangular
cd example
flutter run -d macos -t lib/three_example.dart
# Or select the initial module before using the sample sidebar:
flutter run -t lib/three_example.dart \
  --dart-define=JSR_THREE_EXAMPLE=webgl_lines_colors

For meaningful FPS numbers, use the standalone release build:

cd example
flutter build macos --release -t lib/three_example.dart
open build/macos/Build/Products/Release/js_widget_runtime_example.app

The native sample runner opts into createQuickjsIsolateWidgetEngineBackend through JsRuntimeConfig.backendFactory: QuickJS and JS-side R1 encoding run in a persistent background isolate, while the bridge, RAF/timers, assets and Flutter GPU hosts stay on the main isolate. The factory receives the final wrapped config (unlike a preconstructed backend, which can capture an old onRender). The direct QuickJS backend remains available for synchronous host timing. quickjs_runtime >=0.4.8 is required for isolate OS-thread migration safety.

The prepare step preserves the exact official HTML/module bytes and resolves only each module's pinned addons/local assets. Minecraft, Line Colors, Geometry Colors, Integer BufferGeometry, Indexed BufferGeometry, Indexed BufferGeometry Lines, BufferGeometry Points, Terrain Raycast, Orientation Transform, Raycasting Points, Interactive Cubes (perspective and orthographic), Oriented Bounding Boxes, Interleaved BufferGeometry Points, Voxel Painter and Equirectangular Panorama run on native R1. The two texture-backed additions use pinned PNG/JPEG assets with byte, hash and dimension validation. Indexed BufferGeometry uses real hemisphere lighting and a functional wireframe checkbox through the bounded JSR lil-gui facade. Terrain uses the pinned OrbitControls addon with host pointer, wheel, capture and cancellation events plus the bounded generated-texture Canvas2D adapter. Orientation Transform uses a bounded native lil-gui boolean facade backed by a real JSR checkbox; a real browser document keeps the official lil-gui addon, while the Web Worker uses the functional JSR facade. Unsupported GUI controls and general DOM/CSS remain explicit failures. The official webgl_buffergeometry_lines module remains excluded because R1 deliberately rejects morph targets; Extrude Shapes and Interactive Lines are likewise excluded for unsupported non-indexed material groups and nested DOM containers. None is approximated or rewritten. Native runs require a Flutter GPU/Impeller platform; the Web path requires Worker OffscreenCanvas + WebGL2 (verify on real Chrome/Edge). The sample browser sidebar switches all sixteen integrated modules without restarting; its overlay reports presented-frame FPS, decode/frame time, calls, triangles and uploads.

Official three.js Minecraft example on Flutter GPU

Actual macOS Impeller capture of the unmodified official module; not a browser/WebView screenshot.

Local Minecraft stress profile #

tool/three_minecraft_stress/ prepares a separate heavy local target from the unmodified minecraft-threejs-clone application graph. Its default drawDistance = 0 profile keeps one chunk, batches the real blocks with InstancedMesh, and exercises QuickJS FFI → native R1 → Flutter GPU without Vite or a WebView:

node tool/three_minecraft_stress/prepare.mjs --offline
node tool/three_minecraft_stress/test/stress_test.mjs
cd example && flutter run -d macos -t lib/three_minecraft_stress.dart

See the stress-target README for source pins, verification, diagnostics and bounded controls. Generated output stays under /tmp; the reference has no license and must not be redistributed.

Renderer nodes #

The JSON tree supports these node types:

Category Nodes
Layout column, row, stack, center, padding, sizedBox, expanded, flexible, wrap, align, absoluteFill
Display text, icon, markdown, divider, spacer, image, svg, avatar, chip, badge, path, progress indicators
Container container, card, inkWell, safeArea, scroll, blur
List listView, gridView, listTile. Scrolling is configurable: shrinkWrap (default true) and physics ('never' / 'always' / 'platform'; default 'always' for listView, 'never' for gridView). Use shrinkWrap: false in a bounded parent to let the view scroll
Input button, textButton, outlinedButton, iconButton, textField, textArea (multiline input: value, hint, minLines default 3, maxLines default 8, onChange per keystroke, optional onSubmit done action), switch, checkbox, slider, dropdown
Animation animatedContainer, animatedOpacity, animatedPositioned (implicit change animations); entrance — one-shot mount animation (animation: fade/slideUp/slideDown/slideLeft/slideRight/scale/fadeScale, delay ms for staggered lists, duration, curve); animatedSwitcher — view transition when switchKey changes (animation: fade/slideLeft/slideRight/slideUp/scale/fadeScale)
Map map — OpenStreetMap via flutter_map (no API key). Props: center {lat, lng}, zoom, markers [{id, lat, lng, label?, color?}], polylines [{points, color?, width?}], fitBounds; events: onTap ({lat, lng}), onMarkerTap ({id})
Chart chart — sparkline or bar chart. Props: data (list of numbers; points accepted as alias), chartType (line default, bar), color, fillColor (hex with alpha, e.g. #22c55e33), strokeWidth, height
3D scene3d — host-provided 3D engine. Props: id, width, height, camera, lights; controlled via jsr.scene3d.*. three — upstream r186 canvas (canvasId, width, height) via opt-in jsr.three; independent of scene3d
Media video, audio (host-provided via JsMediaHost)
Gestures gestureDetector

Features #

  • Cross-platform JS execution (VM + web).
  • Declarative JSON-to-Flutter renderer with layout, input, animation and gesture nodes.
  • Effects ported from YoClip: radial gradients, box shadows, blur, 3D transforms, clip, text shadows.
  • manifest.json based app registry with example widgets.
  • Injected I/O handlers so the host controls permissions (network, CLI, secure storage).
  • Host-specific JS APIs via JsRuntimeConfig.hostBootstrapJs (e.g., jsr.yoloit = {...}).
  • Default in-memory handlers for demo/development use.

License #

MIT