| // 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<String> 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(); |