// Copyright 2020 The Dart Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart'; import 'package:frontend_server_client/frontend_server_client.dart'; import 'package:path/path.dart' as p; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_packages_handler/shelf_packages_handler.dart'; import 'package:shelf_static/shelf_static.dart'; void main(List args) async { // Change to package root so relative paths work in CI final scriptDir = p.dirname(p.fromUri(Platform.script)); final packageRoot = p.dirname(scriptDir); Directory.current = packageRoot; try { watch.start(); if (args.isNotEmpty) { throw ArgumentError('No command line args are supported'); } _print('compiling the dart sdk'); final sdkCompileResult = await Process.run(Platform.resolvedExecutable, [ 'compile', 'js-dev', '--multi-root-scheme=org-dartlang-sdk', '--modules=amd', '--module-name=dart_sdk', '-o', dartSdkJs, p.url.join(sdkDir, sdkKernelPath), ]); if (sdkCompileResult.exitCode != 0) { _print( 'Failed to compile the dart sdk to JS:\n' '${sdkCompileResult.stdout}\n' '${sdkCompileResult.stderr}', ); exit(sdkCompileResult.exitCode); } _print('starting frontend server'); final packagesPath = findNearestPackageConfigPath(); final client = await DartDevcFrontendServerClient.start( 'org-dartlang-root:///$app', outputDill, // Use an absolute filesystem root so org-dartlang-root:/// URIs resolve reliably in CI fileSystemRoots: [Directory.current.path], fileSystemScheme: 'org-dartlang-root', platformKernel: p.toUri(sdkKernelPath).toString(), packagesJson: packagesPath ?? '.dart_tool/package_config.json', verbose: true, ); _print('compiling $app'); await client.compile([]); client.accept(); _print('done compiling $app'); _print('starting shelf server'); final cascade = Cascade() .add(_clientHandler(client)) .add(createStaticHandler(Directory.current.path)) .add(createFileHandler(dartSdkJs, url: 'example/app/dart_sdk.js')) .add( createFileHandler( p.join( sdkDir, 'lib', 'dev_compiler', 'web', 'dart_stack_trace_mapper.js', ), url: 'example/app/dart_stack_trace_mapper.js', ), ) .add( createFileHandler( p.join(sdkDir, 'lib', 'dev_compiler', 'amd', 'require.js'), url: 'example/app/require.js', ), ) .add(packagesDirHandler()); final server = await shelf_io.serve(cascade.handler, 'localhost', 8080); _print('server ready'); // The file we will be editing in the repl final appFile = File(app); final originalContent = await appFile.readAsString(); final appLines = const LineSplitter().convert(originalContent); final getterText = 'String get message =>'; final messageLine = appLines.indexWhere( (line) => line.startsWith(getterText), ); final stdinQueue = StreamQueue( stdin.transform(utf8.decoder).transform(const LineSplitter()), ); _prompt(); while (await stdinQueue.hasNext) { final newMessage = await stdinQueue.next; if (newMessage == 'quit') { await server.close(); await stdinQueue.cancel(); break; } else if (newMessage == 'reset') { print('resetting'); client.reset(); _print('restoring $app'); await appFile.writeAsString(originalContent); } else { _print('editing $app'); appLines[messageLine] = '$getterText "$newMessage";'; final newContent = appLines.join('\n'); await appFile.writeAsString(newContent); _print('recompiling $app with edits'); final result = await client.compile([ Uri.parse('org-dartlang-root:///$app'), ]); if (result.errorCount > 0) { print('Compile errors: \n${result.compilerOutputLines.join('\n')}'); await client.reject(); } else { _print('Recompile succeeded for $app'); client.accept(); // TODO: support hot restart print('reload app to see the new message'); } } _prompt(); } _print('restoring $app'); await appFile.writeAsString(originalContent); _print('exiting'); await client.shutdown(); } finally { Directory(p.join('.dart_tool', 'out')).deleteSync(recursive: true); } } Handler _clientHandler(DartDevcFrontendServerClient client) { return (Request request) { var path = request.url.path; final packagesIndex = path.indexOf('/packages/'); if (packagesIndex > 0) { path = request.url.path.substring(packagesIndex); } else { path = request.url.path; } if (!path.startsWith('/')) path = '/$path'; if (path.endsWith('.dart.js') && path != '/example/app/main.dart.js') { path = path.replaceFirst('.dart.js', '.dart.lib.js', path.length - 8); } final assetBytes = client.assetBytes(path); if (assetBytes == null) return Response.notFound('path not found'); return Response.ok( assetBytes, headers: {HttpHeaders.contentTypeHeader: 'application/javascript'}, ); }; } void _print(String message) { print('${watch.elapsed}: $message'); } void _prompt() => stdout.write( 'Enter a new message to print and recompile, or type `quit` to exit:', ); final app = 'example/app/main.dart'; final dartSdkJs = p.join('.dart_tool', 'out', 'dart_sdk.js'); final outputDill = p.join('.dart_tool', 'out', 'example_app.dill'); final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable)); final sdkKernelPath = p.join(sdkDir, 'lib', '_internal', 'ddc_platform.dill'); final watch = Stopwatch();