- π High Performance: Built with Rust for optimal performance
- π¦ Module Support: ES6 modules with import/export syntax
- π Built-in APIs: Fetch, Console, Buffer, Timers, Crypto, and more
- π Async/Await: Full support for asynchronous JavaScript
- π Bridge Calls: Seamless communication between Dart and JavaScript
- π± Cross Platform: Supports Android, iOS, Linux, macOS, and Windows
- π― Type Safe: Strongly typed APIs with Dart integration
- π§ Memory Management: Built-in garbage collection and memory limits
Add this to your pubspec.yaml
:
dependencies:
fjs: any
Then run:
flutter pub get
import 'package:fjs/fjs.dart';
Future<void> main() async {
await LibFjs.init();
runApp(MyApp());
}
// Create runtime and context
final runtime = JsAsyncRuntime();
final context = await JsAsyncContext.from(rt: runtime);
// Create engine with bridge support
final engine = JsEngine(context);
await engine.init(bridgeCall: (jsValue) {
// Handle bridge calls from JavaScript
print('Bridge call from JS: ${jsValue.value}');
return JsValue.string('Response from Dart');
});
// Simple evaluation
final result = await engine.eval(JsCode.code('1 + 2'));
print(result.value); // 3
// Async JavaScript
final asyncResult = await engine.eval(JsCode.code('''
(async () => {
const response = await fetch('https://api.example.com/data');
return await response.json();
})()
'''));
// Enable fetch and console APIs
await engine.enableBuiltinModule(const JsBuiltinOptions(
fetch: true,
console: true,
timers: true,
));
// Now you can use fetch, console.log, setTimeout, etc.
await engine.eval(JsCode.code('''
console.log('Hello from JavaScript!');
setTimeout(() => console.log('Delayed message'), 1000);
'''));
// Declare a module
const moduleCode = '''
export function greet(name) {
return `Hello, ${name}!`;
}
export const version = '1.0.0';
''';
await engine.declareModule(
JsModule.code(module: 'greeting', code: moduleCode)
);
// Use the module
await engine.eval(JsCode.code('''
import { greet, version } from 'greeting';
console.log(greet('Flutter'));
console.log('Version:', version);
'''));
Create bidirectional communication between Dart and JavaScript:
// Dart side
final engine = JsEngine(context);
await engine.init(bridgeCall: (jsValue) async {
final data = jsValue.value;
// Process data in Dart
if (data is Map && data['action'] == 'fetchUserData') {
final userId = data['userId'];
final userData = await fetchUserFromDatabase(userId);
return JsValue.from(userData);
}
return const JsValue.none();
});
// JavaScript side
await engine.eval(JsCode.code('''
const userData = await fjs.bridge_call({
action: 'fetchUserData',
userId: 12345
});
console.log('User data:', userData);
'''));
// Set memory limits
final runtime = JsAsyncRuntime();
await runtime.setMemoryLimit(50 * 1024 * 1024); // 50MB
await runtime.setGcThreshold(10 * 1024 * 1024); // 10MB
// Monitor memory usage
final usage = await runtime.memoryUsage();
print('Memory used: ${usage.memoryUsedSize} bytes');
// Force garbage collection
await runtime.runGc();
try {
final result = await engine.eval(JsCode.code('invalid.syntax()'));
} on JsError catch (e) {
print('JavaScript Error: ${e.message}');
} catch (e) {
print('Other Error: $e');
}
// Load from file
final result = await engine.eval(JsCode.path('/path/to/script.js'));
// Or use evalFile method on context
final context = await JsAsyncContext.from(rt: runtime);
final result = await context.evalFile(path: '/path/to/script.js');
FJS provides several built-in modules that can be enabled as needed:
Module | Description | Enable Option |
---|---|---|
fetch |
HTTP client for making network requests | fetch: true |
console |
Console logging (log, debug, warn, error) | console: true |
buffer |
Buffer manipulation utilities | buffer: true |
timers |
setTimeout, setInterval, clearTimeout, etc. | timers: true |
crypto |
Cryptographic functions | crypto: true |
stream |
Stream processing utilities | stream: true |
url |
URL parsing and manipulation | url: true |
events |
Event emitter implementation | events: true |
The main interface for JavaScript execution:
class JsEngine {
// Initialize the engine
Future<void> init({FutureOr<JsValue?> Function(JsValue)? bridgeCall});
// Execute JavaScript code
Future<JsValue> eval(JsCode source, {JsEvalOptions? options, Duration? timeout});
// Enable built-in modules
Future<JsValue> enableBuiltinModule(JsBuiltinOptions options, {Duration? timeout});
// Module operations
Future<JsValue> declareModule(JsModule module, {Duration? timeout});
Future<JsValue> evaluateModule(JsModule module, {Duration? timeout});
Future<JsValue> importModule(String specifier, {Duration? timeout});
// Cleanup
Future<void> dispose();
// Status
bool get disposed;
bool get running;
bool get initialized;
}
Type-safe representation of JavaScript values:
sealed class JsValue {
// Constructors
const factory JsValue.none();
const factory JsValue.boolean(bool value);
const factory JsValue.integer(int value);
const factory JsValue.float(double value);
const factory JsValue.string(String value);
const factory JsValue.array(List<JsValue> value);
const factory JsValue.object(Map<String, JsValue> value);
// Convert from Dart object
static JsValue from(Object? any);
// Get Dart value
dynamic get value;
// Type checking
bool get isNone;
bool get isBoolean;
bool get isInteger;
// ... other type checkers
}
- Reuse Engines: Create one engine instance and reuse it for multiple evaluations
- Set Memory Limits: Configure appropriate memory limits for your use case
- Use Timeouts: Always set reasonable timeouts for JavaScript execution
- Enable Only Needed Modules: Only enable built-in modules you actually use
- Batch Operations: Group related JavaScript operations together
Check out the example directory for more comprehensive examples including:
- Basic JavaScript evaluation
- Module system usage
- Bridge communication
- Built-in API usage
- Error handling
- Performance testing
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.