https://github.com/fluttercandies/fjs
A high-performance JavaScript runtime for Flutter applications, built with Rust and powered by QuickJS.
https://github.com/fluttercandies/fjs
flutter flutter-js js-engine js-runtime quickjs rust
Last synced: about 1 month ago
JSON representation
A high-performance JavaScript runtime for Flutter applications, built with Rust and powered by QuickJS.
- Host: GitHub
- URL: https://github.com/fluttercandies/fjs
- Owner: fluttercandies
- License: mit
- Created: 2025-02-14T08:40:29.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-01-21T07:57:11.000Z (3 months ago)
- Last Synced: 2026-01-21T18:58:33.688Z (3 months ago)
- Topics: flutter, flutter-js, js-engine, js-runtime, quickjs, rust
- Language: Dart
- Homepage: https://pub.dev/packages/fjs
- Size: 3.15 MB
- Stars: 61
- Watchers: 1
- Forks: 6
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# π FJS - Flutter JavaScript Engine
High-performance JavaScript runtime for Flutter β‘
Built with Rust and powered by QuickJS π¦
[](https://pub.dev/packages/fjs)
[](https://github.com/fluttercandies/fjs)
[](https://github.com/fluttercandies/fjs/blob/main/LICENSE)
*[π δΈζζζ‘£](README_zh.md)*
## β¨ Why FJS?
- **High Performance** - Rust-powered, optimized for mobile platforms
- **ES6 Modules** - Full support for import/export syntax
- **Async/Await** - Native async JavaScript execution
- **Type Safe** - Strongly typed Dart API with sealed classes
- **Bridge Communication** - Bidirectional Dart-JS communication
- **Cross Platform** - Android, iOS, Linux, macOS, Windows
- **Memory Safe** - Built-in GC with configurable limits
## π― Real-world Usage
**[Mikan Flutter](https://github.com/iota9star/mikan_flutter)** - A Flutter client for [Mikan Project](https://mikanani.me), an anime subscription and management platform. FJS powers its core JavaScript execution engine.
## π¦ Installation
```yaml
dependencies:
fjs: any
```
## π Quick Start
```dart
import 'package:fjs/fjs.dart';
void main() async {
await LibFjs.init();
// Create runtime with builtin modules
final runtime = await JsAsyncRuntime.withOptions(
builtin: JsBuiltinOptions(
console: true,
fetch: true,
timers: true,
),
);
// Create context
final context = await JsAsyncContext.from(runtime: runtime);
// Create engine
final engine = JsEngine(context: context);
await engine.init(bridge: (jsValue) {
return JsResult.ok(JsValue.string('Hello from Dart'));
});
// Execute JavaScript
final result = await engine.eval(source: JsCode.code('''
console.log('Hello from FJS!');
1 + 2
'''));
print(result.value); // 3
await engine.dispose();
}
```
## ποΈ Runtime & Context APIs
```dart
// Create an async runtime with web-style builtins and one extra ES module.
final runtime = await JsAsyncRuntime.withOptions(
builtin: JsBuiltinOptions.web(),
additional: [
JsModule.code(
module: 'app/math',
code: 'export function add(a, b) { return a + b; }',
),
],
);
// Apply runtime-level safety and diagnostic limits.
await runtime.setInfo(info: 'main-runtime');
await runtime.setMemoryLimit(limit: BigInt.from(64 * 1024 * 1024));
await runtime.setGcThreshold(threshold: BigInt.from(8 * 1024 * 1024));
await runtime.setMaxStackSize(limit: BigInt.from(512 * 1024));
// Create a context from that runtime.
final context = await JsAsyncContext.from(runtime: runtime);
// Evaluate a simple expression and read the structured JsResult.
final evalResult = await context.eval(code: '21 + 21');
print(evalResult.ok.value); // 42
// Enable top-level await / Promise handling explicitly.
final asyncResult = await context.evalWithOptions(
code: 'await Promise.resolve(40 + 2)',
options: JsEvalOptions.withPromise(),
);
print(asyncResult.ok.value); // 42
// Load code from disk.
final fileResult = await context.evalFile(path: '/absolute/path/to/script.js');
final strictFileResult = await context.evalFileWithOptions(
path: '/absolute/path/to/script.js',
options: JsEvalOptions.defaults(),
);
// Call an exported function from a module that is already available in the runtime.
final functionResult = await context.evalFunction(
module: 'app/math',
method: 'add',
params: [JsValue.integer(2), JsValue.integer(3)],
);
print(functionResult.ok.value); // 5
// Inspect which modules the context can currently import.
final availableModules = await context.getAvailableModules();
print(availableModules);
// Advance or fully drain pending async work when you need explicit control.
if (await runtime.isJobPending()) {
await runtime.executePendingJob();
}
await runtime.idle();
```
Low-level context APIs return `JsResult`, which is useful when you want structured success or error handling instead of exceptions.
### Synchronous Runtime & Context
```dart
// Build a synchronous runtime when you do not need async JavaScript execution.
final runtime = await JsRuntime.withOptions(
builtin: JsBuiltinOptions.essential(),
);
final context = JsContext.from(runtime: runtime);
// Sync contexts return JsResult directly.
final result = context.eval(code: '6 * 7');
print(result.ok.value); // 42
// Apply eval flags such as strict mode.
final strictResult = context.evalWithOptions(
code: '"use strict"; 8 * 8',
options: JsEvalOptions.defaults(),
);
print(strictResult.ok.value); // 64
// File-based sync evaluation uses the same JsResult shape.
final fileResult = context.evalFile(path: '/absolute/path/to/script.js');
final fileWithOptions = context.evalFileWithOptions(
path: '/absolute/path/to/script.js',
options: JsEvalOptions.defaults(),
);
// Introspect the modules visible to this context.
final modules = context.getAvailableModules();
print(modules);
// Pump the QuickJS job queue manually in sync mode.
while (runtime.isJobPending()) {
runtime.executePendingJob();
}
// Configure runtime limits and collect memory statistics.
runtime.setMemoryLimit(limit: BigInt.from(32 * 1024 * 1024));
runtime.setGcThreshold(threshold: BigInt.from(4 * 1024 * 1024));
runtime.setMaxStackSize(limit: BigInt.from(256 * 1024));
runtime.setInfo(info: 'sync-runtime');
print(runtime.memoryUsage().summary());
runtime.runGc();
```
## π§± Source Inputs & Eval Options
```dart
import 'dart:convert';
import 'dart:typed_data';
// Source code can come from a string, a file path, or UTF-8 bytes.
final inlineCode = JsCode.code('1 + 1');
final fileCode = JsCode.path('/absolute/path/to/script.js');
final bytesCode = JsCode.bytes(Uint8List.fromList(utf8.encode('2 + 2')));
// Modules support the same three source forms.
final inlineModule = JsModule.code(
module: 'feature/inline',
code: 'export const enabled = true;',
);
final fileModule = JsModule.path(
module: 'feature/file',
path: '/absolute/path/to/feature.js',
);
final bytesModule = JsModule.bytes(
module: 'feature/bytes',
bytes: utf8.encode('export const answer = 42;'),
);
// Eval options control whether code runs as global script, async code, or module-style code.
final defaultEval = JsEvalOptions.defaults();
final asyncEval = JsEvalOptions.withPromise();
final moduleEval = JsEvalOptions.module();
```
## π¦ ES6 Modules
```dart
// Declare modules
await engine.declareNewModule(
module: JsModule.code(module: 'math', code: '''
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
'''),
);
// Use modules
await engine.eval(source: JsCode.code('''
const { add, multiply } = await import('math');
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
'''));
// Or call an exported function directly without writing an import wrapper yourself.
final sum = await engine.call(
module: 'math',
method: 'add',
params: [JsValue.integer(2), JsValue.integer(3)],
);
print(sum.value); // 5
// Batch-register multiple modules in one request.
await engine.declareNewModules(modules: [
JsModule.code(
module: 'numbers/double',
code: 'export const double = (value) => value * 2;',
),
JsModule.code(
module: 'numbers/triple',
code: 'export const triple = (value) => value * 3;',
),
]);
// Execute a module immediately and leave it cached in the current context.
await engine.evaluateModule(
module: JsModule.code(
module: 'startup',
code: 'globalThis.started = true; export default "ready";',
),
);
// Inspect the dynamic modules declared on this engine.
final declaredModules = await engine.getDeclaredModules();
final hasMath = await engine.isModuleDeclared(moduleName: 'math');
print(declaredModules);
print(hasMath); // true
```
Dynamic modules can be cleared only before they are loaded. After a module has been imported or evaluated in a context, recreate the context to replace it.
## π Module Inventory
```dart
final modules = await engine.getAvailableModules();
print(modules);
final hasConsole = await engine.isModuleAvailable(moduleName: 'console');
final hasXml = await engine.isModuleAvailable(moduleName: 'llrt:xml');
print('console: $hasConsole, llrt:xml: $hasXml');
```
## π Engine Lifecycle Notes
- `JsEngine` wraps an existing `JsAsyncContext`; disposing the engine does not dispose the underlying context or runtime
- `dispose()` detaches the `fjs` bridge object, drains pending runtime work, and then runs GC before the engine becomes unusable
- `clearPendingModules()` only removes dynamic modules that have not been loaded into the current context yet
- `declareNewModules()` and `declareNewBytecodeModules()` reject duplicate module names in a single request
## π¦ Module Bytecode
```dart
// Compile an ES module into QuickJS bytecode without touching the current engine.
final bytecode = await JsBytecode.compile(
module: JsModule.code(
module: 'plugin/main.js',
code: 'export function run() { return "ready"; }',
),
options: JsModuleBytecodeOptions.defaults(),
);
// Validate the bytecode payload before declaring it.
await JsBytecode.validate(module: bytecode);
// Register the precompiled module on the engine.
await engine.declareNewBytecodeModule(module: bytecode);
// Import and execute the declared module like any other ES module.
final result = await engine.eval(source: JsCode.code('''
const { run } = await import('plugin/main.js');
run();
'''));
// Reconstruct bytecode from persisted bytes when loading from storage.
final restored = JsModuleBytecode(
name: bytecode.name,
bytes: bytecode.bytes,
);
JsBytecode.validateSync(module: restored);
```
`JsBytecode.compile()` runs in an isolated QuickJS context, so compiling does not declare or cache the module inside the current engine. `JsBytecode.validate()` only checks structural validity and embedded module name; it does not execute the module. `compileSync()` / `validateSync()` are also available for synchronous callers, but the async variants are safer on the main isolate.
QuickJS bytecode is version-specific and must be treated as trusted input. Recompile bytecode whenever the embedded QuickJS version changes.
### Bytecode Bundles
```dart
// Compile a full module graph into one distributable bundle.
final bundle = await JsBytecode.compileModuleBundle(
entry: 'plugins/main.js',
modules: [
JsModule.code(
module: 'plugins/deps/math.js',
code: 'export const double = (value) => value * 2;',
),
JsModule.code(
module: 'plugins/main.js',
code: '''
import { double } from './deps/math.js';
export default { ready: true, answer: double(21) };
''',
),
],
);
// Validate the bundle structure before loading it.
await JsBytecode.validateBundle(bundle: bundle);
// Execute the bundle entry and cache the involved modules.
await engine.evaluateBytecodeBundle(bundle: bundle);
// Read exports by importing the entry module afterwards.
final result = await engine.eval(source: JsCode.code('''
const { default: plugin } = await import('plugins/main.js');
plugin
'''));
print(result.value); // { ready: true, answer: 42 }
```
Bundles are useful when a plugin ships as a module graph instead of a single file. Relative imports are preserved inside the compiled bundle, and `declareNewBytecodeBundle()` is available when you want to register the bundle without executing its entry yet. `validateBundle()` is structural: it checks entry presence, duplicate names, and that each payload is readable by the embedded QuickJS version. `evaluateBytecodeBundle()` executes the entry and populates the module cache; import the entry afterwards to read its exports.
### Classic Script Bytecode
```dart
// Compile classic script source into non-module bytecode.
final script = await JsBytecode.compileScript(
name: 'startup.js',
source: JsCode.code('''
await Promise.resolve();
globalThis.launchCount = (globalThis.launchCount ?? 0) + 1;
({ mode: 'script', launchCount: globalThis.launchCount })
'''),
options: const JsScriptBytecodeOptions(
promise: true,
strict: true,
stripSource: true,
stripDebug: true,
endianness: JsBytecodeEndianness.little,
),
);
// Validate before evaluation.
await JsBytecode.validateScript(script: script);
// Execute the script bytecode and read its completion value.
final result = await engine.evaluateScriptBytecode(script: script);
print(result.value); // { mode: 'script', launchCount: 1 }
```
Script bytecode is the non-module counterpart to ES module bytecode. `validateScript()` is structural only: it ensures the bytes decode as executable non-module bytecode under the embedded QuickJS version. QuickJS does not expose an embedded script name to verify at load time, so the `name` acts as compile-time metadata and the source filename shown in stack traces.
## π§Ύ Values, Results, and Errors
```dart
import 'dart:typed_data';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// Convert common Dart values into structured JsValue instances automatically.
final payload = JsValue.from({
'enabled': true,
'count': 3,
'tags': ['a', 'b'],
'buffer': Uint8List.fromList([1, 2, 3]),
});
print(payload.typeName()); // Object
print(payload.value); // Dart Map
// Or build typed JsValue trees yourself when you need exact control.
final typed = JsValue.object({
'big': JsValue.bigint('9007199254740993'),
'createdAt': JsValue.date(DateTime.now().millisecondsSinceEpoch),
});
print(typed.value);
// Low-level context APIs return JsResult instead of throwing.
final result = await context.eval(code: '40 + 2');
if (result.isOk) {
print(result.ok.value); // 42
} else {
print('${result.err.code()}: ${result.err}');
}
// JsError values are useful for structured error handling and retry decisions.
const syntaxError = JsError.syntax(
message: 'Unexpected token',
line: 1,
column: 10,
);
print(syntaxError.code());
print(syntaxError.isRecoverable());
// High-level execution APIs still throw AnyhowException on failure.
try {
await engine.eval(source: JsCode.code('invalid.code()'));
} on AnyhowException catch (e) {
print('Execution failed: ${e.message}');
}
```
`JsError` is returned inside `JsResult.err(...)` for structured bridge and low-level context results. Public execution APIs like `eval()` and `call()` currently surface Rust-side failures as `AnyhowException`.
## π Bridge Communication
```dart
// The bridge receives a JsValue and returns a JsResult back to JavaScript.
await engine.init(bridge: (jsValue) async {
final data = jsValue.value;
if (data is Map && data['action'] == 'fetchUser') {
final user = await fetchUser(data['id']);
return JsResult.ok(JsValue.from(user));
}
return JsResult.ok(JsValue.none());
});
// In JavaScript, call back into Dart through the injected fjs object.
await engine.eval(source: JsCode.code('''
const user = await fjs.bridge_call({ action: 'fetchUser', id: 123 });
console.log(user);
'''));
```
## π§ Memory Management
```dart
// Set runtime safety limits.
await runtime.setMemoryLimit(limit: BigInt.from(50 * 1024 * 1024)); // 50MB
await runtime.setGcThreshold(threshold: BigInt.from(10 * 1024 * 1024)); // 10MB
await runtime.setMaxStackSize(limit: BigInt.from(512 * 1024)); // 512KB
// Inspect current memory usage.
final usage = await runtime.memoryUsage();
print(usage.summary());
// Force a garbage collection pass when you need immediate cleanup.
await runtime.runGc();
```
## π Core API
### JsAsyncRuntime & JsAsyncContext
```dart
abstract class JsAsyncRuntime {
factory JsAsyncRuntime();
static Future withOptions({
JsBuiltinOptions? builtin,
List? additional,
});
Future isJobPending();
Future executePendingJob();
Future idle();
Future memoryUsage();
Future setMemoryLimit({required BigInt limit});
Future setGcThreshold({required BigInt threshold});
Future setMaxStackSize({required BigInt limit});
Future setInfo({required String info});
Future runGc();
}
abstract class JsAsyncContext {
static Future from({required JsAsyncRuntime runtime});
Future eval({required String code});
Future evalWithOptions({required String code, required JsEvalOptions options});
Future evalFile({required String path});
Future evalFileWithOptions({required String path, required JsEvalOptions options});
Future evalFunction({
required String module,
required String method,
List? params,
});
Future> getAvailableModules();
}
```
### JsRuntime & JsContext
```dart
abstract class JsRuntime {
factory JsRuntime();
static Future withOptions({
JsBuiltinOptions? builtin,
List? additional,
});
bool isJobPending();
bool executePendingJob();
MemoryUsage memoryUsage();
void setMemoryLimit({required BigInt limit});
void setGcThreshold({required BigInt threshold});
void setMaxStackSize({required BigInt limit});
void setInfo({required String info});
void runGc();
}
abstract class JsContext {
static JsContext from({required JsRuntime runtime});
JsResult eval({required String code});
JsResult evalWithOptions({required String code, required JsEvalOptions options});
JsResult evalFile({required String path});
JsResult evalFileWithOptions({required String path, required JsEvalOptions options});
List getAvailableModules();
}
```
### JsEngine
```dart
class JsEngine {
factory JsEngine({required JsAsyncContext context});
Future init({required FutureOr Function(JsValue) bridge});
Future initWithoutBridge();
Future eval({required JsCode source, JsEvalOptions? options});
Future call({required String module, required String method, List? params});
Future declareNewModule({required JsModule module});
Future declareNewModules({required List modules}); // rejects duplicate names in one request
Future declareNewBytecodeBundle({required JsModuleBytecodeBundle bundle});
Future declareNewBytecodeModule({required JsModuleBytecode module});
Future declareNewBytecodeModules({required List modules}); // rejects duplicate names in one request
Future clearPendingModules();
Future> getAvailableModules();
Future isModuleDeclared({required String moduleName});
Future isModuleAvailable({required String moduleName});
Future> getDeclaredModules();
Future evaluateBytecodeBundle({required JsModuleBytecodeBundle bundle});
Future evaluateModule({required JsModule module});
Future evaluateBytecodeModule({required JsModuleBytecode module});
Future evaluateScriptBytecode({required JsScriptBytecode script});
Future dispose(); // drains pending runtime work, then runs GC
bool get running;
bool get disposed;
JsAsyncContext get context; // engine does not own the returned context
}
```
### MemoryUsage
```dart
abstract class MemoryUsage {
int get totalMemory;
int get totalAllocations;
int get mallocSize;
int get objCount;
int get strCount;
String summary();
}
```
### JsValue
```dart
sealed class JsValue {
const factory JsValue.none();
const factory JsValue.boolean(bool value);
const factory JsValue.integer(PlatformInt64 value);
const factory JsValue.float(double value);
const factory JsValue.bigint(String value);
const factory JsValue.string(String value);
const factory JsValue.bytes(Uint8List value);
const factory JsValue.array(List value);
const factory JsValue.object(Map value);
const factory JsValue.date(PlatformInt64 value);
const factory JsValue.symbol(String value);
const factory JsValue.function(String value);
static JsValue from(Object? any);
String typeName();
dynamic get value;
}
```
### JsBuiltinOptions & JsEvalOptions
```dart
sealed class JsBuiltinOptions {
const factory JsBuiltinOptions({
bool? console,
bool? fetch,
bool? timers,
bool? crypto,
bool? fs,
bool? url,
bool? process,
bool? path,
bool? util,
bool? intl,
bool? temporal,
// ... other builtin toggles
});
static JsBuiltinOptions none();
static JsBuiltinOptions essential();
static JsBuiltinOptions web();
static JsBuiltinOptions node();
static JsBuiltinOptions all();
}
sealed class JsEvalOptions {
factory JsEvalOptions({
bool? global,
bool? strict,
bool? backtraceBarrier,
bool? promise,
});
static JsEvalOptions defaults();
static JsEvalOptions withPromise();
static JsEvalOptions module();
}
```
### JsCode, JsModule, and Bytecode
```dart
sealed class JsCode {
const factory JsCode.code(String value); // Inline code
const factory JsCode.path(String value); // File path
const factory JsCode.bytes(Uint8List value); // Raw UTF-8 source bytes
}
sealed class JsModule {
static JsModule code({required String module, required String code});
static JsModule path({required String module, required String path});
static JsModule bytes({required String module, required List bytes}); // UTF-8 source bytes
}
sealed class JsModuleBytecode {
factory JsModuleBytecode({required String name, required List bytes});
}
sealed class JsModuleBytecodeBundle {
factory JsModuleBytecodeBundle({
String? entry,
required List modules,
});
}
sealed class JsScriptBytecode {
factory JsScriptBytecode({required String name, required List bytes});
}
class JsBytecode {
static JsModuleBytecode compileSync({
required JsModule module,
JsModuleBytecodeOptions? options,
});
static Future compile({
required JsModule module,
JsModuleBytecodeOptions? options,
});
static JsModuleBytecodeBundle compileModuleBundleSync({
required List modules,
String? entry,
JsModuleBytecodeOptions? options,
});
static Future compileModuleBundle({
required List modules,
String? entry,
JsModuleBytecodeOptions? options,
});
static JsScriptBytecode compileScriptSync({
required String name,
required JsCode source,
JsScriptBytecodeOptions? options,
});
static Future compileScript({
required String name,
required JsCode source,
JsScriptBytecodeOptions? options,
});
static void validateSync({required JsModuleBytecode module});
static Future validate({required JsModuleBytecode module});
static void validateBundleSync({required JsModuleBytecodeBundle bundle});
static Future validateBundle({required JsModuleBytecodeBundle bundle});
static void validateScriptSync({required JsScriptBytecode script});
static Future validateScript({required JsScriptBytecode script});
}
sealed class JsModuleBytecodeOptions {
const factory JsModuleBytecodeOptions({
JsBytecodeEndianness? endianness,
bool? stripSource,
bool? stripDebug,
});
static JsModuleBytecodeOptions defaults();
}
sealed class JsScriptBytecodeOptions {
const factory JsScriptBytecodeOptions({
JsBytecodeEndianness? endianness,
bool? stripSource,
bool? stripDebug,
bool? strict,
bool? backtraceBarrier,
bool? promise,
});
static JsScriptBytecodeOptions defaults();
}
```
### JsResult & JsError
```dart
sealed class JsResult {
const factory JsResult.ok(JsValue value);
const factory JsResult.err(JsError error);
bool get isOk;
bool get isErr;
JsValue get ok;
JsError get err;
}
sealed class JsError {
const factory JsError.promise(String message);
const factory JsError.module({String? module, String? method, required String message});
const factory JsError.context(String message);
const factory JsError.storage(String message);
const factory JsError.io({String? path, required String message});
const factory JsError.runtime(String message);
const factory JsError.generic(String message);
const factory JsError.engine(String message);
const factory JsError.bridge(String message);
const factory JsError.conversion({
required String from,
required String to,
required String message,
});
const factory JsError.timeout({
required String operation,
required BigInt timeoutMs,
});
const factory JsError.memoryLimit({
required BigInt current,
required BigInt limit,
});
const factory JsError.stackOverflow(String message);
const factory JsError.syntax({int? line, int? column, required String message});
const factory JsError.reference(String message);
const factory JsError.type(String message);
const factory JsError.cancelled(String message);
String code();
bool isRecoverable();
}
```
## π§© Built-in Runtime Features
Some builtin options expose importable modules, and some install globals directly on the runtime.
| Option | Description |
|--------|-------------|
| `abort` | `AbortController` and abort-related globals |
| `assert` | Assertion helpers |
| `asyncHooks` | Async lifecycle tracking |
| `buffer` | Buffer utilities for binary data |
| `childProcess` | Child process spawning |
| `console` | Console logging (`console.log`, `console.error`, etc.) |
| `crypto` | Cryptographic functions and Web Crypto globals |
| `dgram` | UDP sockets |
| `dns` | DNS resolution |
| `events` | `EventEmitter` support |
| `exceptions` | Exception helpers installed globally |
| `fetch` | Fetch API globals |
| `fs` | File system operations |
| `https` | HTTPS client module |
| `intl` | Lightweight `Intl.DateTimeFormat` timezone support |
| `navigator` | Navigator globals |
| `net` | TCP sockets |
| `os` | Operating system utilities (`not available on iOS`) |
| `path` | Path manipulation (POSIX/Windows) |
| `perfHooks` | Performance measurement APIs |
| `process` | Process information and environment |
| `streamWeb` | Web Streams API |
| `stringDecoder` | String decoding from buffers |
| `temporal` | `Temporal` global |
| `timers` | Timer functions (`setTimeout`, `setInterval`, `setImmediate`) |
| `tty` | Terminal utilities |
| `url` | URL parsing and formatting |
| `util` | Utility functions |
| `zlib` | Compression/decompression (gzip, deflate) |
| `json` | JSON static method compatibility helpers |
### Quick Presets
```dart
// Essential: console, timers, buffer, util, json
JsBuiltinOptions.essential()
// Web: console, timers, fetch, url, crypto, streamWeb, navigator, exceptions, intl, json
JsBuiltinOptions.web()
// Node.js: Most Node-compatible modules, plus https and intl
JsBuiltinOptions.node()
// All modules
JsBuiltinOptions.all()
// Custom selection
JsBuiltinOptions(
console: true,
fetch: true,
timers: true,
// ... other options
)
```
## β‘ Performance Tips
1. **Reuse Engines** - Create once, use many times
2. **Set Memory Limits** - Configure appropriate limits
3. **Use Source Bytes** - Prefer `JsCode.bytes()` / `JsModule.bytes()` when your JavaScript source is already in UTF-8 bytes
4. **Batch Operations** - Group related operations
## π License
MIT License - see [LICENSE](LICENSE) file.