An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          


FJS Logo

# πŸš€ FJS - Flutter JavaScript Engine

High-performance JavaScript runtime for Flutter ⚑
Built with Rust and powered by QuickJS πŸ¦€

[![pub package](https://img.shields.io/pub/v/fjs.svg)](https://pub.dev/packages/fjs)
[![GitHub stars](https://img.shields.io/github/stars/fluttercandies/fjs.svg?style=flat&logo=github&colorB=deeppink&label=stars)](https://github.com/fluttercandies/fjs)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](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.