runFlutsimPreview function
Future<void>
runFlutsimPreview(
)
Implementation
Future<void> runFlutsimPreview() async {
final port = FlutsimConfig.port;
final buildDir = Directory('build/web');
final unifiedServer = UnifiedWebSocketServer();
await unifiedServer.start();
// Check if we should use fast development mode
if (FlutsimConfig.useFastMode) {
print('$ansiBlue🚀 Starting Flutter in fast development mode...$ansiReset');
await _startFastDevelopmentMode(port, unifiedServer);
return;
}
// Step 1: Build Flutter Web with development optimizations
if (!buildDir.existsSync()) {
print('🔧 Building Flutter web (development mode)...');
// Get the Flutter path to use full path instead of relying on PATH
final flutterPath = getFlutterPath();
final flutterCommand = flutterPath ?? 'flutter';
// Build flags based on configuration
final buildFlags = ['build', 'web', '--release'];
if (FlutsimConfig.useHtmlRenderer) {
buildFlags.addAll(['--web-renderer', 'html']);
}
if (FlutsimConfig.disableSkia) {
buildFlags.add('--dart-define=FLUTTER_WEB_USE_SKIA=false');
}
if (FlutsimConfig.disableIconTreeShaking) {
buildFlags.add('--no-tree-shake-icons');
}
// Use development-optimized build flags
final buildProcess = await Process.start(
flutterCommand,
buildFlags,
mode: ProcessStartMode.inheritStdio,
);
final exitCode = await buildProcess.exitCode;
if (exitCode != 0) {
print('❌ Web build failed.');
return;
}
} else {
print('✅ Web build already exists.');
}
// Step 2: Get IP address
final ip = await getLocalIp();
final url = 'http://$ip:$port';
// Step 3: Serve Web Folder with live reload injection
final handler = createStaticHandler(
path.absolute(buildDir.path),
defaultDocument: 'index.html',
);
// Create a middleware to inject live reload script
Future<Response> liveReloadHandler(Request request) async {
final response = await handler(request);
// Only inject script for HTML files
if (request.url.path.endsWith('.html') || request.url.path.isEmpty) {
final body = await response.readAsString();
// Inject unified FlutSim client script
String modifiedBody = FlutSimClient.injectScript(body, unifiedServer.port);
return Response.ok(
modifiedBody,
headers: response.headers,
);
}
return response;
}
await shelf_io.serve(liveReloadHandler, InternetAddress.anyIPv4, port);
print('\n$ansiGreen🚀 Flutter Web Server: $url$ansiReset\n');
// Auto-open browser for standard preview mode
openBrowser(url);
// Generate and display QR code
await generateAndDisplayQRCode(url);
// Get the Flutter path
final flutterPath = getFlutterPath();
final flutterCommand = flutterPath ?? 'flutter';
final flutterProcess = await Process.start(
flutterCommand,
[
'run',
'-d', 'web-server',
'--web-port', port.toString(),
'--web-hostname', '0.0.0.0',
'--hot', // Enable hot reload
'--debug', // Use debug mode for faster hot reload
],
mode: ProcessStartMode.normal,
);
String? devToolsUrl;
// Pipe stdout and stderr to this process and intercept errors
flutterProcess.stdout.transform(utf8.decoder).listen((data) {
stdout.write(data);
// Capture DevTools URL
if (data.contains('DevTools debugger')) {
final match = RegExp(r'http://127\.0\.0\.1:[0-9]+[^ \n\r]*').firstMatch(data);
if (match != null) {
devToolsUrl = match.group(0);
}
}
// Check for compilation or generic errors to overlay in browser
if (data.contains('Error: ') || data.contains('Exception: ') || data.contains('Failed to compile')) {
unifiedServer.sendError(data);
} else if (data.contains('Reloaded ') || data.contains('Restarted ')) {
unifiedServer.clearError(); // Wipe the overlay on successful reload
}
});
flutterProcess.stderr.transform(utf8.decoder).listen((data) {
stderr.write(data);
if (data.contains('Error: ') || data.contains('Exception: ')) {
unifiedServer.sendError(data);
}
});
// Start Terminal UI (TUI) Listener
if (stdin.hasTerminal) {
stdin.echoMode = false;
stdin.lineMode = false;
print('\n$ansiBlue🎮 Terminal UI active:$ansiReset');
print(' [r] Hot Reload | [R] Hot Restart');
print(' [d] DevTools | [c] Clear');
print(' [q] Quit | [h] Help / QR');
stdin.listen((List<int> codes) {
final key = utf8.decode(codes);
switch (key) {
case 'r':
print('\n$ansiYellow🔥 Manual Hot Reload...$ansiReset');
flutterProcess.stdin.write('r\n');
unifiedServer.triggerHotReload();
break;
case 'R':
print('\n$ansiYellow💥 Manual Hot Restart...$ansiReset');
flutterProcess.stdin.write('R\n');
unifiedServer.triggerAutoReload();
break;
case 'd':
if (devToolsUrl != null) {
print('\n$ansiBlue🛠 Opening Dart DevTools...$ansiReset');
openBrowser(devToolsUrl!);
} else {
print('\n$ansiYellow⚠️ DevTools URL not yet available.$ansiReset');
}
break;
case 'c':
print('\x1B[2J\x1B[0;0H'); // Clear terminal
break;
case 'q':
case '\x03': // Ctrl+C
print('\n$ansiGreen👋 Exiting FlutSim...$ansiReset');
flutterProcess.kill();
exit(0);
case 'h':
generateAndDisplayQRCode(url);
break;
}
});
}
// Start file watcher for automatic hot reload
final watcher = DirectoryWatcher('lib');
print('👀 Watching lib/ for changes...');
Timer? debounceTimer;
bool isHotReloading = false;
watcher.events.listen((event) async {
// Skip if already hot reloading
if (isHotReloading) {
print('⏳ Hot reload already in progress, skipping...');
return;
}
// Debounce rapid changes
debounceTimer?.cancel();
debounceTimer =
Timer(Duration(milliseconds: FlutsimConfig.debounceDelay), () async {
print('🔃 Change detected in ${event.path}');
print('🔥 Triggering automatic hot reload...');
isHotReloading = true;
try {
// Trigger instant hot reload if enabled
if (FlutsimConfig.enableInstantHotReload) {
unifiedServer.triggerHotReload();
}
// Trigger auto reload if enabled and instant hot reload is disabled
if (FlutsimConfig.enableAutoReload && !FlutsimConfig.enableInstantHotReload) {
unifiedServer.triggerAutoReload();
}
// Trigger instant UI updates if enabled
if (FlutsimConfig.enableInstantUIUpdates) {
unifiedServer.sendInstantUIUpdate([
{
'action': 'update_text',
'selector': 'body',
'text': 'Updated at ${DateTime.now().toString()}',
}
]);
}
// Send 'r' to flutter process stdin
flutterProcess.stdin.write('r\n');
print('✅ Hot reload triggered automatically!');
// Cooldown to ensure flutter finishes hot reloading before allowing another
await Future.delayed(const Duration(milliseconds: 1500));
} catch (e) {
print('❌ Failed to trigger hot reload: $e');
} finally {
isHotReloading = false;
}
});
});
// Wait for the process to complete
await flutterProcess.exitCode;
}