pkg_js_interpreter version: 0.0.3

Published 2026-10-19

ruud

sdk | dart
repository | svn
platform | generic
status | n/a
  • Readme
  • Changelog
  • Versions

pkg_js_interpreter

Source taken from js_interpreter:0.0.1.

graph

Pub package License

A comprehensive, pure Dart implementation of a JavaScript interpreter supporting ES6+ features. Perfect for embedding JavaScript execution in Dart/Flutter applications.

✨ Features

🚀 Core Capabilities

  • Full JavaScript Parsing - Complete lexer and parser supporting modern JavaScript syntax
  • Async/Await Support - Native async function execution with Promise integration
  • Module System - ES6 modules with import/export
  • Class System - Full ES6+ class support including private fields and static blocks
  • Generators - function* and yield/yield* support
  • Iterators - Full iterator protocol implementation
  • Proxy/Reflect - Complete metaprogramming support
  • TypedArrays - All typed array types (Int8Array, Uint8Array, Float32Array, etc.)
  • RegExp - Full regular expression support with ES2022 features
  • Strict Mode - Automatic and explicit strict mode handling

For detailed feature coverage and use cases, check the test suite for comprehensive examples.

🔧 Quick Start

Basic Usage

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() {
  final interpreter = JSInterpreter();
  
  // Evaluate JavaScript code
  final result = interpreter.eval('''
    const greeting = "Hello";
    const name = "World";
    greeting + ", " + name + "!";
  ''');
  
  print(result); // Hello, World!
}

Async/Await Support

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() async {
  final interpreter = JSInterpreter();
  
  final result = await interpreter.evalAsync('''
    async function fetchData() {
      const response = await Promise.resolve({ data: "Hello Async!" });
      return response.data;
    }
    
    await fetchData();
  ''');
  
  print(result); // Hello Async!
}

Convert to Dart Types

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() async {
  final interpreter = JSInterpreter();
  
  // Returns native Dart types (String, int, double, List, Map, etc.)
  final result = await interpreter.evalAsyncToDart('''
    const data = {
      name: "Alice",
      age: 30,
      hobbies: ["reading", "coding"]
    };
    JSON.stringify(data);
  ''');
  
  print(result); // {"name":"Alice","age":30,"hobbies":["reading","coding"]}
  print(result.runtimeType); // String
}

ES6 Modules

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() async {
  final interpreter = JSInterpreter();
  
  // Register a module
  interpreter.registerModule('math-utils', '''
    export function add(a, b) { return a + b; }
    export function multiply(a, b) { return a * b; }
    export const PI = 3.14159;
  ''');
  
  // Use the module
  final result = await interpreter.evalModuleAsync('''
    import { add, multiply, PI } from 'math-utils';
    
    const sum = add(5, 3);
    const product = multiply(4, PI);
    
    export default { sum, product };
  ''');
  
  print(result); // {sum: 8, product: 12.56636}
}

Dart-JS Interop

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() {
  final interpreter = JSInterpreter();
  
  // Inject Dart function into JavaScript
  interpreter.setGlobal('dartPrint', (List<JSValue> args) {
    print('From Dart: ${args.first}');
    return JSValueFactory.undefined();
  });
  
  interpreter.eval('''
    dartPrint("Hello from JavaScript!");
  ''');
  // Output: From Dart: Hello from JavaScript!
}

Load External JavaScript Files

load(path) is available by default (similar to print() and trace()). It reads the JavaScript file from disk and evaluates it in the current interpreter context.

import 'dart:io';
import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() {
  final interpreter = JSInterpreter();

  final file = File('/tmp/helpers.js');
  file.writeAsStringSync('''
    function add(a, b) { return a + b; }
  ''');

  interpreter.eval('load("/tmp/helpers.js");');
  final result = interpreter.eval('add(2, 3);');
  print(result); // 5
}

Spreadsheet Constructor Extension (new Spreadsheet(...))

When using pkg_spreadsheet, you can register a host-side constructor that is callable from JavaScript with new.

Opt-in: this extension is not enabled by default.
You must call interpreter.registerSpreadsheetClass() for each JSInterpreter instance that should expose Spreadsheet and Sheet.

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() {
  final interpreter = JSInterpreter();
  interpreter.registerSpreadsheetClass();

  interpreter.setGlobal('path', '/tmp/data.tsv');

  final result = interpreter.eval('''
    const spreadsheet = new Spreadsheet(path);
    spreadsheet.read();
    const firstSheet = spreadsheet.getFirstSheet(); // returns Sheet object
    firstSheet.getCell(0, 0);
  ''');

  print(result); // first cell as string
}

Registered globals and return types:

  • new Spreadsheet(path?)
  • new Sheet(name, rows?)
  • spreadsheet.getFirstSheet(), spreadsheet.getLastSheet(), spreadsheet.getSheet(name), spreadsheet.sheet, spreadsheet.getSheets() all return Sheet object(s)
  • spreadsheet.getSheetNames() returns string[]

Execution Control: Pause, Breakpoint Pause, Resume, Step, Step Over

You can run code in controlled mode, pause at configured source-line breakpoints, request a cooperative pause during execution, and resume from the paused state.

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() {
  final interpreter = JSInterpreter();

  interpreter.setBreakpoints({3});
  final first = interpreter.evalControlled('''
const a = 1;
const b = 2;
const c = a + b;
c;
''');

  if (first.isPaused) {
    print(first.pauseInfo?.reason); // ExecutionPauseReason.breakpoint
    print(first.pauseInfo?.line);   // 3
  }

  final resumed = interpreter.resumeExecution();
  print(resumed.value); // 3

  interpreter.setBreakpoints({4});
  final second = interpreter.evalControlled('''
function add(a, b) {
  return a + b;
}
const result = add(1, 2);
result;
''');

  if (second.isPaused) {
    final stepped = interpreter.stepExecution(); // step-over in current frame
    print(stepped.pauseInfo?.reason); // ExecutionPauseReason.step
  }
}

Host APIs:

  • evalControlled(code) starts a resumable execution session
  • pauseExecution() requests a cooperative pause of the current controlled run
  • resumeExecution() continues from the paused point
  • stepExecution() executes one step-over in the current frame
  • canStepOver() reports whether step-over is currently valid
  • canStepInto() reports whether step-into is currently valid (only when paused at a call-site whose runtime target is script-defined)
  • canStepOut() reports whether stepping out is currently possible (caller frame exists)
  • addPauseListener(...) receives all pause notifications
  • addBreakpointListener(...) receives breakpoint-only pauses
  • addResumeListener(...) notifies when execution continues from a paused state
  • addStepListener(...) / addStepOverListener(...) receive step pauses
  • addExecutionCompleteListener(...) / addExecutionErrorListener(...) notify terminal outcomes
  • executionStatus / executionPauseInfo expose current control state

Pausing is cooperative, not preemptive: the VM pauses at instruction boundaries, not by forcefully interrupting Dart code mid-operation.

Messaging System: sendMessage & sendMessageAsync

The interpreter provides a powerful bidirectional messaging system for complex Dart-JavaScript interactions.

sendMessage (Synchronous)

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() {
  final interpreter = JSInterpreter();
  
  // Register a message handler
  interpreter.onMessage('greet', (dynamic message) {
    final name = message as String;
    return 'Hello, $name!';
  });
  
  // Call from JavaScript (synchronous)
  final result = interpreter.eval('''
    const greeting = sendMessage('greet', 'Alice');
    greeting; // "Hello, Alice!"
  ''');
  
  print(result);
}

sendMessageAsync (Asynchronous)

For async operations that require waiting for results:

import 'package:pkg_js_interpreter/pkg_js_interpreter.dart';

void main() async {
  final interpreter = JSInterpreter();
  
  // Register an async message handler
  interpreter.onMessage('fetchUser', (dynamic message) async {
    final userId = message as int;
    // Simulate async operation
    await Future.delayed(Duration(milliseconds: 100));
    return {
      'id': userId,
      'name': 'Alice',
      'email': 'alice@example.com'
    };
  });
  
  // Call from JavaScript (asynchronous)
  final result = await interpreter.evalAsync('''
    async function loadUser() {
      const user = await sendMessageAsync('fetchUser', 1);
      return user.email;
    }
    
    await loadUser();
  ''');
  
  print(result); // "alice@example.com"
}

Advanced Patterns

Message Handlers with Multiple Arguments:

interpreter.onMessage('calculate', (dynamic message) {
  final args = message as List;
  final operation = args[0] as String;
  final a = args[1] as num;
  final b = args[2] as num;
  
  switch (operation) {
    case 'add': return a + b;
    case 'multiply': return a * b;
    default: return null;
  }
});

// In JavaScript:
final result = interpreter.eval('''
  sendMessage('calculate', 'add', 5, 3); // 8
  sendMessage('calculate', 'multiply', 4, 7); // 28
''');

Async Pipeline with Promise.all:

interpreter.onMessage('fetchData', (dynamic message) async {
  final source = message as String;
  await Future.delayed(Duration(milliseconds: 50));
  return {'source': source, 'data': [1, 2, 3]};
});

final result = await interpreter.evalAsync('''
  const results = await Promise.all([
    sendMessageAsync('fetchData', 'database'),
    sendMessageAsync('fetchData', 'api'),
    sendMessageAsync('fetchData', 'cache')
  ]);
  
  results.map(r => r.source);
''');
// ['database', 'api', 'cache']

Error Handling:

interpreter.onMessage('riskyOperation', (dynamic message) {
  if (message == 'fail') {
    throw Exception('Operation failed');
  }
  return 'Success';
});

// In JavaScript:
final result = await interpreter.evalAsync('''
  try {
    const result1 = await sendMessageAsync('riskyOperation', 'ok');
    const result2 = await sendMessageAsync('riskyOperation', 'fail');
  } catch (error) {
    console.log('Caught:', error.message);
  }
''');

📚 API Reference

JSInterpreter

The main class for JavaScript interpretation.

Method Description
eval(String code) Synchronously evaluate JavaScript code
evalAsync(String code) Asynchronously evaluate JavaScript code (returns Future<JSValue>)
evalAsyncToDart(String code) Asynchronously evaluate and convert result to Dart types
evalModule(String code) Evaluate as ES6 module
evalModuleAsync(String code) Asynchronously evaluate as ES6 module
registerModule(String name, String code) Register a module for import
setGlobal(String name, dynamic value) Set a global variable
registerHostCommand(String name, HostCommand handler) Register one custom global JS command backed by Dart
registerHostCommands(Map<String, HostCommand> commands) Register multiple custom global JS commands
getGlobal(String name) Get a global variable
onMessage(String channel, dynamic callback) Register a message handler for a specific channel
setConsoleCallbacks({onLog, onWarn, onError}) Capture console.log/warn/error output in Dart callbacks (suppresses default printing for registered levels)

Capturing Console Output

final interpreter = JSInterpreter();
final logs = <String>[];
final warns = <String>[];
final errors = <String>[];

interpreter.setConsoleCallbacks(
  onLog: logs.add,
  onWarn: warns.add,
  onError: errors.add,
);

interpreter.eval('''
  print("uses same log callback channel as console.log");
  console.log("hello", 123);
  console.warn("careful");
  console.error("boom");
''');

Global Functions (JavaScript Context)

Function Description
print(...args) Built-in alias of console logging; auto-registered on every JSInterpreter
sendMessage(String channel, [arg1, arg2, ...]) Send a synchronous message to Dart and get a result
sendMessageAsync(String channel, [arg1, arg2, ...]) Send an asynchronous message to Dart (returns Promise)

JSValue Types

Type Description
JSUndefined JavaScript undefined
JSNull JavaScript null
JSBoolean JavaScript boolean
JSNumber JavaScript number (int or double)
JSString JavaScript string
JSBigInt JavaScript BigInt
JSSymbol JavaScript Symbol
JSObject JavaScript object
JSArray JavaScript array
JSFunction JavaScript function
JSClass JavaScript class
JSGenerator JavaScript generator
JSAsyncGenerator JavaScript async generator
JSPromise JavaScript Promise
JSRegExp JavaScript RegExp
JSDate JavaScript Date
JSError JavaScript Error (and all error types)

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❤️ in Dart

Changelog

0.0.3 - 2026-10-19

Changed

  • Added explicit debugger stepping APIs on JSInterpreter:
    • stepOver()
    • stepInto()
    • stepOut()
  • Added step-availability APIs on JSInterpreter:
    • canStepOver()
    • canStepInto()
    • canStepOut()
  • Removed stepExecution() did alias stepOver()
  • Improved VM stepping semantics for controlled execution:
    • step-over pauses on the next meaningful source location in the current frame (or after unwind)
    • step-into is only enabled when paused on a call-site that resolves to a script-defined function target
    • step-out is only enabled when a caller frame exists
  • Added/updated execution-control regression tests for step availability and step gating behavior.

0.0.2 - 2026-10-12

Changed

  • Added execution-control APIs on JSInterpreter for controlled evaluation, cooperative pause, and resume:
    • evalControlled(...)
    • pauseExecution()
    • resumeExecution()
    • stepExecution()
    • stepOverExecution()
    • executionStatus
    • executionPauseInfo
  • Added execution-control listener callbacks on JSInterpreter:
    • addPauseListener(...), addBreakpointListener(...), addResumeListener(...)
    • addStepListener(...), addStepOverListener(...)
    • addExecutionCompleteListener(...), addExecutionErrorListener(...)
  • Added resumable pause-on-breakpoint behavior for controlled execution sessions.
  • Added debugger-like step and step-over pause reasons in controlled execution sessions.
  • Added execution-control regression tests and README usage documentation.

0.0.1 - 2026-10-12

  • Initial version. Taken from js_interpreter:0.0.1.

0.0.3

2026-10-19 download