diff --git a/pkgs/dart_services/lib/src/common.dart b/pkgs/dart_services/lib/src/common.dart index ecc4457ee..e49c74726 100644 --- a/pkgs/dart_services/lib/src/common.dart +++ b/pkgs/dart_services/lib/src/common.dart @@ -29,13 +29,22 @@ void main() { // https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/web/bootstrap.dart#L236. const kBootstrapFlutterCode = r''' import 'dart:ui_web' as ui_web; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'generated_plugin_registrant.dart' as pluginRegistrant; import 'main.dart' as entrypoint; +@JS('window') +external JSObject get _window; + Future main() async { + // Mock DWDS indicators to allow Flutter to register hot reload 'reassemble' + // extension. + _window[r'$dwdsVersion'] = true.toJS; + _window[r'$emitRegisterEvent'] = ((String _) {}).toJS; await ui_web.bootstrapEngine( runApp: () { entrypoint.main(); diff --git a/pkgs/dart_services/lib/src/common_server.dart b/pkgs/dart_services/lib/src/common_server.dart index 0dfc136f4..4a03f5049 100644 --- a/pkgs/dart_services/lib/src/common_server.dart +++ b/pkgs/dart_services/lib/src/common_server.dart @@ -77,6 +77,9 @@ class CommonServerApi { router.post(r'/api//analyze', handleAnalyze); router.post(r'/api//compile', handleCompile); router.post(r'/api//compileDDC', handleCompileDDC); + router.post(r'/api//compileNewDDC', handleCompileNewDDC); + router.post( + r'/api//compileNewDDCReload', handleCompileNewDDCReload); router.post(r'/api//complete', handleComplete); router.post(r'/api//fixes', handleFixes); router.post(r'/api//format', handleFormat); @@ -127,14 +130,18 @@ class CommonServerApi { } } - Future handleCompileDDC(Request request, String apiVersion) async { + Future _handleCompileDDC( + Request request, + String apiVersion, + Future Function(api.CompileRequest) + compile) async { if (apiVersion != api3) return unhandledVersion(apiVersion); - final sourceRequest = - api.SourceRequest.fromJson(await request.readAsJson()); + final compileRequest = + api.CompileRequest.fromJson(await request.readAsJson()); final results = await serialize(() { - return impl.compiler.compileDDC(sourceRequest.source); + return compile(compileRequest); }); if (results.hasOutput) { @@ -144,6 +151,7 @@ class CommonServerApi { } return ok(api.CompileDDCResponse( result: results.compiledJS!, + deltaDill: results.deltaDill, modulesBaseUrl: modulesBaseUrl, ).toJson()); } else { @@ -151,6 +159,26 @@ class CommonServerApi { } } + Future handleCompileDDC(Request request, String apiVersion) async { + return await _handleCompileDDC(request, apiVersion, + (request) => impl.compiler.compileDDC(request.source)); + } + + Future handleCompileNewDDC( + Request request, String apiVersion) async { + return await _handleCompileDDC(request, apiVersion, + (request) => impl.compiler.compileNewDDC(request.source)); + } + + Future handleCompileNewDDCReload( + Request request, String apiVersion) async { + return await _handleCompileDDC( + request, + apiVersion, + (request) => impl.compiler + .compileNewDDCReload(request.source, request.deltaDill!)); + } + Future handleComplete(Request request, String apiVersion) async { if (apiVersion != api3) return unhandledVersion(apiVersion); diff --git a/pkgs/dart_services/lib/src/compiling.dart b/pkgs/dart_services/lib/src/compiling.dart index 8f3f689e7..a57717d01 100644 --- a/pkgs/dart_services/lib/src/compiling.dart +++ b/pkgs/dart_services/lib/src/compiling.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:bazel_worker/driver.dart'; @@ -32,13 +33,15 @@ class Compiler { }) : this._(sdk, path.join(sdk.dartSdkPath, 'bin', 'dart'), storageBucket); Compiler._(this._sdk, this._dartPath, this._storageBucket) - : _ddcDriver = BazelWorkerDriver( - () => Process.start(_dartPath, [ - path.join(_sdk.dartSdkPath, 'bin', 'snapshots', - 'dartdevc.dart.snapshot'), - '--persistent_worker' - ]), - maxWorkers: 1), + : _ddcDriver = BazelWorkerDriver(() async { + final p = await Process.start(_dartPath, [ + path.join( + _sdk.dartSdkPath, 'bin', 'snapshots', 'dartdevc.dart.snapshot'), + '--persistent_worker' + ]); + p.stderr.listen((e) => print(utf8.decode(e))); + return p; + }, maxWorkers: 1), _projectTemplates = ProjectTemplates.projectTemplates; /// Compile the given string and return the resulting [CompilationResults]. @@ -104,7 +107,8 @@ class Compiler { } /// Compile the given string and return the resulting [DDCCompilationResults]. - Future compileDDC(String source) async { + Future _compileDDC(String source, + {String? deltaDill, required bool useNew}) async { final imports = getAllImportsFor(source); final temp = Directory.systemTemp.createTempSync('dartpad'); @@ -126,9 +130,29 @@ class Compiler { File(bootstrapPath).writeAsStringSync(bootstrapContents); File(path.join(temp.path, 'lib', kMainDart)).writeAsStringSync(source); + final newDeltaKernelPath = path.join(temp.path, 'new_kernel.dill'); + String? oldDillPath; + if (deltaDill != null) { + final oldDillBytes = base64Decode(deltaDill); + oldDillPath = path.join(temp.path, 'old_kernel.dill'); + File(oldDillPath) + ..createSync() + ..writeAsBytesSync(oldDillBytes); + } + + final mainJsPath = path.join(temp.path, '$kMainDart.js'); final arguments = [ - '--modules=amd', + if (useNew) ...[ + '--modules=ddc', + '--canary', + '--new-reload-delta-kernel=$newDeltaKernelPath', + if (oldDillPath != null) '--old-reload-delta-kernel=$oldDillPath', + ], + if (!useNew) ...[ + '--modules=amd', + '--module-name=dartpad_main', + ], '--no-summarize', if (usingFlutter) ...[ '-s', @@ -136,8 +160,7 @@ class Compiler { '-s', '${_sdk.flutterWebSdkPath}/ddc_outline_sound.dill', ], - ...['-o', path.join(temp.path, '$kMainDart.js')], - ...['--module-name', 'dartpad_main'], + ...['-o', mainJsPath], '--enable-asserts', if (_sdk.experiments.isNotEmpty) '--enable-experiment=${_sdk.experiments.join(",")}', @@ -145,13 +168,10 @@ class Compiler { '--packages=${path.join(temp.path, '.dart_tool', 'package_config.json')}', ]; - final mainJs = File(path.join(temp.path, '$kMainDart.js')); - _logger.fine('About to exec dartdevc worker: ${arguments.join(' ')}"'); final response = await _ddcDriver.doWork(WorkRequest(arguments: arguments)); - if (response.exitCode != 0) { if (response.output.contains("Undefined name 'main'")) { return DDCCompilationResults._missingMain; @@ -160,18 +180,26 @@ class Compiler { CompilationProblem._(_rewritePaths(response.output)), ]); } else { - // The `--single-out-file` option for dartdevc was removed in v2.7.0. As - // a result, the JS code produced above does *not* provide a name for - // the module it contains. That's a problem for DartPad, since it's - // adding the code to a script tag in an iframe rather than loading it - // as an individual file from baseURL. As a workaround, this replace - // statement injects a name into the module definition. - final processedJs = mainJs - .readAsStringSync() - .replaceFirst('define([', "define('dartpad_main', ["); + final mainJs = File(mainJsPath); + final newDeltaDill = File(newDeltaKernelPath); + + var compiledJs = mainJs.readAsStringSync(); + + if (!useNew) { + // The `--single-out-file` option for dartdevc was removed in v2.7.0. As + // a result, the JS code produced above does *not* provide a name for + // the module it contains. That's a problem for DartPad, since it's + // adding the code to a script tag in an iframe rather than loading it + // as an individual file from baseURL. As a workaround, this replace + // statement injects a name into the module definition. + compiledJs = + compiledJs.replaceFirst('define([', "define('dartpad_main', ["); + } final results = DDCCompilationResults( - compiledJS: processedJs, + compiledJS: compiledJs, + deltaDill: + useNew ? base64Encode(newDeltaDill.readAsBytesSync()) : null, modulesBaseUrl: 'https://storage.googleapis.com/$_storageBucket' '/${_sdk.dartVersion}/', ); @@ -186,6 +214,19 @@ class Compiler { } } + Future compileDDC(String source) async { + return await _compileDDC(source, useNew: false); + } + + Future compileNewDDC(String source) async { + return await _compileDDC(source, useNew: true); + } + + Future compileNewDDCReload( + String source, String deltaDill) async { + return await _compileDDC(source, deltaDill: deltaDill, useNew: true); + } + Future dispose() async { return _ddcDriver.terminateWorkers(); } @@ -225,14 +266,16 @@ class DDCCompilationResults { ]); final String? compiledJS; + final String? deltaDill; final String? modulesBaseUrl; final List problems; - DDCCompilationResults({this.compiledJS, this.modulesBaseUrl}) + DDCCompilationResults({this.compiledJS, this.deltaDill, this.modulesBaseUrl}) : problems = const []; const DDCCompilationResults.failed(this.problems) : compiledJS = null, + deltaDill = null, modulesBaseUrl = null; bool get hasOutput => compiledJS != null && compiledJS!.isNotEmpty; diff --git a/pkgs/dart_services/test/compiling_test.dart b/pkgs/dart_services/test/compiling_test.dart index 2f05df00c..c7bb9ed34 100644 --- a/pkgs/dart_services/test/compiling_test.dart +++ b/pkgs/dart_services/test/compiling_test.dart @@ -23,18 +23,6 @@ void defineTests() { await compiler.dispose(); }); - Future Function() generateCompilerDDCTest(String sample) { - return () async { - final result = await compiler.compileDDC(sample); - expect(result.problems, isEmpty); - expect(result.success, true); - expect(result.compiledJS, isNotEmpty); - expect(result.modulesBaseUrl, isNotEmpty); - - expect(result.compiledJS, contains("define('dartpad_main', [")); - }; - } - test('simple', () async { final result = await compiler.compile(sampleCode); @@ -44,75 +32,132 @@ void defineTests() { expect(result.sourceMap, isNull); }); - test( - 'compileDDC simple', - generateCompilerDDCTest(sampleCode), - ); - - test( - 'compileDDC with web', - generateCompilerDDCTest(sampleCodeWeb), - ); - - test( - 'compileDDC with Flutter', - generateCompilerDDCTest(sampleCodeFlutter), - ); - - test( - 'compileDDC with Flutter Counter', - generateCompilerDDCTest(sampleCodeFlutterCounter), - ); - - test( - 'compileDDC with Flutter Sunflower', - generateCompilerDDCTest(sampleCodeFlutterSunflower), - ); - - test( - 'compileDDC with Flutter Draggable Card', - generateCompilerDDCTest(sampleCodeFlutterDraggableCard), - ); - - test( - 'compileDDC with Flutter Implicit Animations', - generateCompilerDDCTest(sampleCodeFlutterImplicitAnimations), - ); - - test( - 'compileDDC with async', - generateCompilerDDCTest(sampleCodeAsync), - ); - - test('compileDDC with single error', () async { - final result = await compiler.compileDDC(sampleCodeError); - expect(result.success, false); - expect(result.problems.length, 1); - expect(result.problems[0].toString(), - contains('Error: Expected \';\' after this.')); - }); - - test('compileDDC with no main', () async { - final result = await compiler.compileDDC(sampleCodeNoMain); - expect(result.success, false); - expect(result.problems.length, 1); - expect( - result.problems.first.message, - contains("Invoked Dart programs must have a 'main' function defined"), - ); - }); + void testDDCEndpoint(String endpointName, + {Future Function(String source)? endpoint, + Future Function(String source, String deltaDill)? + reloadEndpoint, + required bool expectDeltaDill, + required String compiledIndicator}) { + group(endpointName, () { + Future Function() generateEndpointTest(String sample) { + return () async { + DDCCompilationResults result; + if (endpoint != null) { + result = await endpoint(sample); + } else { + result = await reloadEndpoint!(sample, sampleDillFile); + } + expect(result.problems, isEmpty); + expect(result.success, true); + expect(result.compiledJS, isNotEmpty); + expect(result.deltaDill, expectDeltaDill ? isNotEmpty : isNull); + expect(result.modulesBaseUrl, isNotEmpty); + + expect(result.compiledJS, contains(compiledIndicator)); + }; + } + + test( + 'simple', + generateEndpointTest(sampleCode), + ); + + test( + 'with web', + generateEndpointTest(sampleCodeWeb), + ); + + test( + 'with Flutter', + generateEndpointTest(sampleCodeFlutter), + ); + + test( + 'with Flutter Counter', + generateEndpointTest(sampleCodeFlutterCounter), + ); + + test( + 'with Flutter Sunflower', + generateEndpointTest(sampleCodeFlutterSunflower), + ); + + test( + 'with Flutter Draggable Card', + generateEndpointTest(sampleCodeFlutterDraggableCard), + ); + + test( + 'with Flutter Implicit Animations', + generateEndpointTest(sampleCodeFlutterImplicitAnimations), + ); + + test( + 'with async', + generateEndpointTest(sampleCodeAsync), + ); + + test('with single error', () async { + DDCCompilationResults result; + if (endpoint != null) { + result = await endpoint(sampleCodeError); + } else { + result = await reloadEndpoint!(sampleCodeError, ''); + } + expect(result.success, false); + expect(result.problems.length, 1); + expect(result.problems[0].toString(), + contains('Error: Expected \';\' after this.')); + }); + + test('with no main', () async { + DDCCompilationResults result; + if (endpoint != null) { + result = await endpoint(sampleCodeNoMain); + } else { + result = await reloadEndpoint!(sampleCodeNoMain, sampleDillFile); + } + expect(result.success, false); + expect(result.problems.length, 1); + expect( + result.problems.first.message, + contains( + "Invoked Dart programs must have a 'main' function defined"), + ); + }); + + test('with multiple errors', () async { + DDCCompilationResults result; + if (endpoint != null) { + result = await endpoint(sampleCodeErrors); + } else { + result = await reloadEndpoint!(sampleCodeErrors, sampleDillFile); + } + expect(result.success, false); + expect(result.problems.length, 1); + expect(result.problems[0].toString(), + contains('Error: Method not found: \'print1\'.')); + expect(result.problems[0].toString(), + contains('Error: Method not found: \'print2\'.')); + expect(result.problems[0].toString(), + contains('Error: Method not found: \'print3\'.')); + }); + }); + } - test('compileDDC with multiple errors', () async { - final result = await compiler.compileDDC(sampleCodeErrors); - expect(result.success, false); - expect(result.problems.length, 1); - expect(result.problems[0].toString(), - contains('Error: Method not found: \'print1\'.')); - expect(result.problems[0].toString(), - contains('Error: Method not found: \'print2\'.')); - expect(result.problems[0].toString(), - contains('Error: Method not found: \'print3\'.')); - }); + testDDCEndpoint('compileDDC', + endpoint: (source) => compiler.compileDDC(source), + expectDeltaDill: false, + compiledIndicator: "define('dartpad_main', ["); + testDDCEndpoint('compileNewDDC', + endpoint: (source) => compiler.compileNewDDC(source), + expectDeltaDill: true, + compiledIndicator: 'defineLibrary("package:dartpad_sample/main.dart"'); + testDDCEndpoint('compileNewDDC', + reloadEndpoint: (source, deltaDill) => + compiler.compileNewDDCReload(source, deltaDill), + expectDeltaDill: true, + compiledIndicator: 'defineLibrary("package:dartpad_sample/main.dart"'); test('sourcemap', () async { final result = await compiler.compile(sampleCode, returnSourceMap: true); diff --git a/pkgs/dart_services/test/server_test.dart b/pkgs/dart_services/test/server_test.dart index 26cac5a4a..98d82a008 100644 --- a/pkgs/dart_services/test/server_test.dart +++ b/pkgs/dart_services/test/server_test.dart @@ -216,7 +216,7 @@ void main() { }); test('compileDDC', () async { - final result = await client.compileDDC(CompileRequest(source: ''' + final result = await client.compileNewDDC(CompileRequest(source: ''' void main() { print('hello world'); } @@ -227,7 +227,7 @@ void main() { }); test('compileDDC flutter', () async { - final result = await client.compileDDC(CompileRequest(source: ''' + final result = await client.compileNewDDC(CompileRequest(source: ''' import 'package:flutter/material.dart'; void main() { @@ -252,7 +252,7 @@ class MyApp extends StatelessWidget { test('compileDDC with error', () async { try { - await client.compileDDC(CompileRequest(source: ''' + await client.compileNewDDC(CompileRequest(source: ''' void main() { print('hello world') } diff --git a/pkgs/dart_services/test/src/sample_code.dart b/pkgs/dart_services/test/src/sample_code.dart index 329196999..1907720cc 100644 --- a/pkgs/dart_services/test/src/sample_code.dart +++ b/pkgs/dart_services/test/src/sample_code.dart @@ -730,3 +730,64 @@ const sampleCode3PartVariousPartOfTestAnim = ''' part of testanim; $sampleCode3PartFlutterImplicitAnimationsVarious '''; + +const sampleDillFile = 'kKvN7wAAAHowMDAwMDAwMDAwAAADBwEAAAAABgEAAA0BACEAAA4AADs' + 'AAA8AAF0AABAAAICdAAAHAgCA3AAACgMAAAAAAAAAAAIGBACA/4EjgSoBAAUEAQFqgQBgAhEBA' + 'AADgSqBKgAAAAAAAABnAhIAYAIUAAAABgYAgS2BOoKqAAABBQAAAAOBPoKqAgIAAAAAAF0CFgF' + 'cAVwAAT6BR4KqAz0egbEYAwADHoGqGwIBZwISAGACFAIaAB4nAAYAJ4GyBx6ByiABAAEpgcUAA' + 'D0egdkYAwADHoHSGwIBZwISAGACFAIaAB4nAAYAJ4HaCB6CAyIBAAE0gfQDgfSCAQAAAAEBAYH' + '8AAAACWACJAAAmAAAAT6B/4IAAAAAPTOCCx6CGCYCAAACCjSCNQOCNYJYAAAAAAAAAFwAAAE+g' + 'jiCVwE9HoJLDAAAAAALNIJvA4JvgqMAAAAAAAAAXAAAAT6CcoKiAT0egosJAAAAAAAAAAABcAA' + 'AAEYAAAAAAAAASgAAAHgAAAFwAAAAAgACDQcAAQAABH4AACcAAIDDAAAoAACA/AAAKQAAgTUAA' + 'BAAAAAAAAAAAAABBgkBgXWBeoKoAAABCwAAAAOBiYKoAAAAAQABgZwAAAEMYAErAWoAmAIAXAA' + 'AAT6BroKoBU6BwoHMAAUNYAIrATWB3oHOAACCBQBgASsB4IHOgdQjgd4Pgc7hgc6B/BqB4S0Ug' + 'c6B/AEBYAIrAWACKz0egg8wAQAB4YIcge4APR6CPDMBAAHhgkmB7gA9HoJpNgEAAeGCdoHuAD1' + '4AACCjuGChIHuDgAAAABhAgBcOAABAQAAAmkAAAG1AAAAAAAAAbkAAAJpAAAAAQADBwoAAgAAA' + 'QEAADkAAAAAAAAAAAABBgwCKi+BZgAAAQUAAAADM4FmAgIAAAAAAFwBXAABPjyBZgE9HkA8AQA' + 'BH0xAAgAAAg8qexAfgI5EAgAAAhEfgKhIAQAAARJqgMdgAkoEE2qA/WACTAcAAQIAAALxAAACl' + 'gAAAAAAAAKaAAAC8QAAAAEAAAADLGZpbGU6Ly8vdG1wL2RhcnRwYWRDWFlaR1EvbGliL2Jvb3R' + 'zdHJhcC5kYXJ0AAAlcGFja2FnZTpkYXJ0cGFkX3NhbXBsZS9ib290c3RyYXAuZGFydAA+ZmlsZ' + 'TovLy90bXAvZGFydHBhZENYWVpHUS9saWIvZ2VuZXJhdGVkX3BsdWdpbl9yZWdpc3RyYW50LmR' + 'hcnQAADdwYWNrYWdlOmRhcnRwYWRfc2FtcGxlL2dlbmVyYXRlZF9wbHVnaW5fcmVnaXN0cmFud' + 'C5kYXJ0ACdmaWxlOi8vL3RtcC9kYXJ0cGFkQ1hZWkdRL2xpYi9tYWluLmRhcnQAACBwYWNrYWd' + 'lOmRhcnRwYWRfc2FtcGxlL21haW4uZGFydAAAAAMPAAADZQAAA98IBAYIEQABTgAABBQISgARU' + 'ANRAlICUwJUAlUCVgJXAlgCWQJaAlsCXAJdAl4CXwJjAgMAAAAAAAAAAAhlAAJnBWgFCEwABWs' + 'GbAJtAnAEYwIAAAABAAAAAwAAAAkAAAAKAAAADAAAADIAAAA7AAAAQwAAAAhwABUBFgIVAwQBF' + 'wUFABgHFwgLABkKFwsFABoAGwAcAB0OHg4fACATHwAhFSIPFxcjACQZFxolACYcFh0nDhcfKBo' + 'kISkAKiMrDRclLAAtAC4ALwAwKjEqFiwyJzMuFy80KDUxFzI0KTY0FzU0Kxc3DgA3ADg6FzsKA' + 'Dk9Oj47PwAAPEE9QjtDAAA+RT9GO0cAAEBJQQBCS0MRRE1FSkRPRk9HT0hPSU9KT0tPTE9NT05' + 'PT09QT1FPUk9TT1RPVQBWYFdhRGJYAFlkWmVEZltmXEtdaURqXmpfamBgYW5Eb2IAAAAAAAAAY' + 'wAGFiAnKzE9T1thcH+AiICegLiAvIDCgMeAy4DYgP2BBYENgUSBZIFvgX6BlIHIgcqB0oHkge6' + 'B9IITgh+CKoI5glOCa4J5goKCiIKXgtGC/4Mtg2GDaoN8g5ODn4Owg8GD3oQGhCuENoRDhG2Ed' + 'YSehKSEyYTNhPOE+YUAhQSFCIUQhRWFH4UohTWFO4VDhUuFWoVkhWyFeoWIhZqFqIXShdiF24Y' + 'Ghg+GEIYRhhaGH4YqhjaGU4ZYdWlfd2VicGx1Z2luUmVnaXN0cmFudGVudHJ5cG9pbnRfd2luZ' + 'G93bWFpbndpbmRvdyRkd2RzVmVyc2lvbiRlbWl0UmVnaXN0ZXJFdmVudF8jd2MwI2Zvcm1hbHJ' + '1bkFwcHJlZ2lzdGVyUGx1Z2luc3BsdWdpblJlZ2lzdHJhcnJlZ2lzdHJhcnJlZ2lzdGVyTWVzc' + '2FnZUhhbmRsZXJkZWJ1Z1Nob3dDaGVja2VkTW9kZUJhbm5lcmhvbWVhcHBCYXJ0aXRsZWJvZHl' + 'IZWxsbywgV29ybGQhcGFja2FnZTpkYXJ0cGFkX3NhbXBsZS9ib290c3RyYXAuZGFydEBnZXR0Z' + 'XJzQG1ldGhvZHNwYWNrYWdlOmRhcnRwYWRfc2FtcGxlL2dlbmVyYXRlZF9wbHVnaW5fcmVnaXN' + '0cmFudC5kYXJ0cGFja2FnZTpkYXJ0cGFkX3NhbXBsZS9tYWluLmRhcnRkYXJ0OnVpX3dlYmRhc' + 'nQ6anNfaW50ZXJvcGRhcnQ6anNfaW50ZXJvcF91bnNhZmVwYWNrYWdlOmZsdXR0ZXJfd2ViX3B' + 'sdWdpbnMvZmx1dHRlcl93ZWJfcGx1Z2lucy5kYXJ0SlNKU09iamVjdGRhcnQ6X2ludGVyY2Vwd' + 'G9yc2RhcnQ6YXN5bmNGdXR1cmVKU09iamVjdFVuc2FmZVV0aWxFeHRlbnNpb258W109ZGFydDp' + 'qc191dGlsZ2V0UHJvcGVydHlkYXJ0Ol9qc19oZWxwZXJzdGF0aWNJbnRlcm9wR2xvYmFsQ29ud' + 'GV4dEJvb2xUb0pTQm9vbGVhbnxnZXQjdG9KU19mdW5jdGlvblRvSlMxZGFydDpjb3JlU3RyaW5' + 'nYm9vdHN0cmFwRW5naW5lcGFja2FnZTpzaGFyZWRfcHJlZmVyZW5jZXNfd2ViL3NoYXJlZF9wc' + 'mVmZXJlbmNlc193ZWIuZGFydHBhY2thZ2U6dXJsX2xhdW5jaGVyX3dlYi91cmxfbGF1bmNoZXJ' + 'fd2ViLmRhcnRwYWNrYWdlOnZpZGVvX3BsYXllcl93ZWIvdmlkZW9fcGxheWVyX3dlYi5kYXJ0c' + 'GFja2FnZTpmbHV0dGVyX3dlYl9wbHVnaW5zL3NyYy9wbHVnaW5fcmVnaXN0cnkuZGFydFJlZ2l' + 'zdHJhcndlYlBsdWdpblJlZ2lzdHJhclNoYXJlZFByZWZlcmVuY2VzUGx1Z2lucmVnaXN0ZXJXa' + 'XRoVXJsTGF1bmNoZXJQbHVnaW5WaWRlb1BsYXllclBsdWdpbnBhY2thZ2U6Zmx1dHRlci9tYXR' + 'lcmlhbC5kYXJ0cGFja2FnZTpmbHV0dGVyL3NyYy93aWRnZXRzL2JpbmRpbmcuZGFydHBhY2thZ' + '2U6Zmx1dHRlci9zcmMvbWF0ZXJpYWwvYXBwLmRhcnRNYXRlcmlhbEFwcEBjb25zdHJ1Y3RvcnN' + 'wYWNrYWdlOmZsdXR0ZXIvc3JjL21hdGVyaWFsL3NjYWZmb2xkLmRhcnRTY2FmZm9sZHBhY2thZ' + '2U6Zmx1dHRlci9zcmMvbWF0ZXJpYWwvYXBwX2Jhci5kYXJ0QXBwQmFycGFja2FnZTpmbHV0dGV' + 'yL3NyYy93aWRnZXRzL3RleHQuZGFydFRleHRwYWNrYWdlOmZsdXR0ZXIvc3JjL3dpZGdldHMvY' + 'mFzaWMuZGFydENlbnRlckBmaWVsZHNuYW1lZGF0YXRleHRTcGFuc3R5bGVzdHJ1dFN0eWxldGV' + '4dEFsaWdudGV4dERpcmVjdGlvbmxvY2FsZXNvZnRXcmFwb3ZlcmZsb3d0ZXh0U2NhbGVGYWN0b' + '3J0ZXh0U2NhbGVybWF4TGluZXNzZW1hbnRpY3NMYWJlbHRleHRXaWR0aEJhc2lzdGV4dEhlaWd' + 'odEJlaGF2aW9yc2VsZWN0aW9uQ29sb3JwYWNrYWdlOmZsdXR0ZXIvc3JjL3dpZGdldHMvZnJhb' + 'WV3b3JrLmRhcnRXaWRnZXRrZXlwYWNrYWdlOmZsdXR0ZXIvc3JjL3BhaW50aW5nL2FsaWdubWV' + 'udC5kYXJ0QWxpZ25tZW50eHlBbGlnbmFsaWdubWVudHdpZHRoRmFjdG9yaGVpZ2h0RmFjdG9yU' + '2luZ2xlQ2hpbGRSZW5kZXJPYmplY3RXaWRnZXRjaGlsZAAAAAADCwAABDcAAASIAAAErAAABZA' + 'AAAWQAAAFlAAADKYAAAAAAAAAAAAAABMAAAGNAAACgwAAAwsAAAADAAAM6A=='; diff --git a/pkgs/dart_services/tool/grind.dart b/pkgs/dart_services/tool/grind.dart index f80e5e2df..5074de469 100644 --- a/pkgs/dart_services/tool/grind.dart +++ b/pkgs/dart_services/tool/grind.dart @@ -24,7 +24,10 @@ Future main(List args) async { final List compilationArtifacts = [ 'dart_sdk.js', + 'dart_sdk_new.js', 'flutter_web.js', + 'flutter_web_new.js', + 'ddc_module_loader.js', ]; @Task('validate that we have the correct compilation artifacts available in ' @@ -91,7 +94,8 @@ void buildStorageArtifacts() async { delete(getDir('artifacts')); final instructions = []; - // build and copy dart_sdk.js, flutter_web.js, and flutter_web.dill + // build and copy ddc_module_loader.js, dart_sdk.js, flutter_web.js, and + // flutter_web.dill final temp = Directory.systemTemp.createTempSync('flutter_web_sample'); try { @@ -107,6 +111,20 @@ void buildStorageArtifacts() async { } } +// Packages to include in flutter_web.js. These are implicitly imported by all +// flutter apps. Since DDC doesn't do tree-shaking these would be included in +// every compilation. +const _flutterPackages = [ + 'flutter', + 'flutter_test', + 'url_launcher_web', + 'shared_preferences_web', + 'video_player_web', + 'shared_preferences_platform_interface', + 'video_player_platform_interface', + 'web', +]; + Future _buildStorageArtifacts( Directory dir, Sdk sdk, { @@ -153,16 +171,13 @@ Future _buildStorageArtifacts( path.join(dir.path, 'lib', 'generated_plugin_registrant.dart')); } - // locate the artifacts - final flutterPackages = ['flutter', 'flutter_test']; - final flutterLibraries = []; final config = await findPackageConfig(dir); if (config == null) { throw FileSystemException('package config not found', dir.toString()); } for (final package in config.packages) { - if (flutterPackages.contains(package.name)) { + if (_flutterPackages.contains(package.name)) { // This is a package we're interested in - add all the public libraries to // the list. final libPath = package.packageUriRoot.toFilePath(); @@ -209,17 +224,50 @@ Future _buildStorageArtifacts( workingDirectory: dir.path, ); - // Copy both to the project directory. + final argumentsNew = [ + path.join(sdk.dartSdkPath, 'bin', 'snapshots', 'dartdevc.dart.snapshot'), + '-s', + dillPath, + '--modules=ddc', + '--canary', + '--source-map', + // The dill file will be the same between legacy and new compilation. + '--no-summarize', + '-o', + 'flutter_web_new.js', + ...flutterLibraries + ]; + + await _run( + compilerPath, + arguments: argumentsNew, + workingDirectory: dir.path, + ); + + // Copy all to the project directory. final artifactsDir = getDir(path.join('artifacts')); artifactsDir.createSync(recursive: true); final sdkJsPath = path.join(sdk.flutterWebSdkPath, 'amd-canvaskit-html-sound/dart_sdk.js'); - + final newSdkJsPath = path.join(sdk.flutterWebSdkPath, + 'ddcLibraryBundle-canvaskit-html-sound/dart_sdk.js'); + final ddcModuleLoaderPath = + path.join(sdk.dartSdkPath, 'lib/dev_compiler/ddc/ddc_module_loader.js'); + + copy(getFile(ddcModuleLoaderPath), artifactsDir); + copy(getFile(newSdkJsPath), artifactsDir); + copy(getFile('$newSdkJsPath.map'), artifactsDir); + joinFile(artifactsDir, ['dart_sdk.js']) + .copySync(path.join('artifacts', 'dart_sdk_new.js')); + joinFile(artifactsDir, ['dart_sdk.js.map']) + .copySync(path.join('artifacts', 'dart_sdk_new.js.map')); copy(getFile(sdkJsPath), artifactsDir); copy(getFile('$sdkJsPath.map'), artifactsDir); copy(joinFile(dir, ['flutter_web.js']), artifactsDir); copy(joinFile(dir, ['flutter_web.js.map']), artifactsDir); + copy(joinFile(dir, ['flutter_web_new.js']), artifactsDir); + copy(joinFile(dir, ['flutter_web_new.js.map']), artifactsDir); copy(joinFile(dir, ['flutter_web.dill']), artifactsDir); final args = context.invocation.arguments; diff --git a/pkgs/dartpad_shared/lib/model.dart b/pkgs/dartpad_shared/lib/model.dart index d70957e67..779a7bad0 100644 --- a/pkgs/dartpad_shared/lib/model.dart +++ b/pkgs/dartpad_shared/lib/model.dart @@ -119,8 +119,9 @@ class DiagnosticMessage { @JsonSerializable() class CompileRequest { final String source; + final String? deltaDill; - CompileRequest({required this.source}); + CompileRequest({required this.source, this.deltaDill}); factory CompileRequest.fromJson(Map json) => _$CompileRequestFromJson(json); @@ -143,10 +144,12 @@ class CompileResponse { @JsonSerializable() class CompileDDCResponse { final String result; + final String? deltaDill; final String? modulesBaseUrl; CompileDDCResponse({ required this.result, + required this.deltaDill, required this.modulesBaseUrl, }); diff --git a/pkgs/dartpad_shared/lib/model.g.dart b/pkgs/dartpad_shared/lib/model.g.dart index 5e8dfacfb..31d3494b9 100644 --- a/pkgs/dartpad_shared/lib/model.g.dart +++ b/pkgs/dartpad_shared/lib/model.g.dart @@ -85,11 +85,13 @@ Map _$DiagnosticMessageToJson(DiagnosticMessage instance) => CompileRequest _$CompileRequestFromJson(Map json) => CompileRequest( source: json['source'] as String, + deltaDill: json['deltaDill'] as String?, ); Map _$CompileRequestToJson(CompileRequest instance) => { 'source': instance.source, + 'deltaDill': instance.deltaDill, }; CompileResponse _$CompileResponseFromJson(Map json) => @@ -105,12 +107,14 @@ Map _$CompileResponseToJson(CompileResponse instance) => CompileDDCResponse _$CompileDDCResponseFromJson(Map json) => CompileDDCResponse( result: json['result'] as String, + deltaDill: json['deltaDill'] as String?, modulesBaseUrl: json['modulesBaseUrl'] as String?, ); Map _$CompileDDCResponseToJson(CompileDDCResponse instance) => { 'result': instance.result, + 'deltaDill': instance.deltaDill, 'modulesBaseUrl': instance.modulesBaseUrl, }; diff --git a/pkgs/dartpad_shared/lib/services.dart b/pkgs/dartpad_shared/lib/services.dart index 091c38279..05c79da71 100644 --- a/pkgs/dartpad_shared/lib/services.dart +++ b/pkgs/dartpad_shared/lib/services.dart @@ -40,6 +40,14 @@ class ServicesClient { Future compileDDC(CompileRequest request) => _requestPost('compileDDC', request.toJson(), CompileDDCResponse.fromJson); + Future compileNewDDC(CompileRequest request) => + _requestPost( + 'compileNewDDC', request.toJson(), CompileDDCResponse.fromJson); + + Future compileNewDDCReload(CompileRequest request) => + _requestPost( + 'compileNewDDCReload', request.toJson(), CompileDDCResponse.fromJson); + Future openInIdx(OpenInIdxRequest request) => _requestPost('openInIDX', request.toJson(), OpenInIdxResponse.fromJson); diff --git a/pkgs/dartpad_ui/lib/execution/frame.dart b/pkgs/dartpad_ui/lib/execution/frame.dart index b535bbfa8..f06806743 100644 --- a/pkgs/dartpad_ui/lib/execution/frame.dart +++ b/pkgs/dartpad_ui/lib/execution/frame.dart @@ -28,11 +28,20 @@ class ExecutionServiceImpl implements ExecutionService { String javaScript, { String? modulesBaseUrl, String? engineVersion, + required bool reload, + required bool isNewDDC, + required bool isFlutter, }) async { - await _reset(); + if (!reload) { + await _reset(); + } - return _send('execute', { - 'js': _decorateJavaScript(javaScript, modulesBaseUrl: modulesBaseUrl), + return _send(reload ? 'executeReload' : 'execute', { + 'js': _decorateJavaScript(javaScript, + modulesBaseUrl: modulesBaseUrl, + isNewDDC: isNewDDC, + reload: reload, + isFlutter: isFlutter), if (engineVersion != null) 'canvasKitBaseUrl': _canvasKitUrl(engineVersion), }); @@ -52,7 +61,13 @@ class ExecutionServiceImpl implements ExecutionService { @override Future tearDown() => _reset(); - String _decorateJavaScript(String javaScript, {String? modulesBaseUrl}) { + String _decorateJavaScript(String javaScript, + {String? modulesBaseUrl, + required bool isNewDDC, + required bool reload, + required bool isFlutter}) { + if (reload) return javaScript; + final script = StringBuffer(); // Redirect print messages to the host. @@ -66,10 +81,12 @@ function dartPrint(message) { } '''); - script.writeln(''' + if (!isNewDDC) { + script.writeln(''' // Unload any previous version. require.undef('dartpad_main'); '''); + } // The JavaScript exception handling for DartPad catches both errors // directly raised by `main()` (in which case we might have useful Dart @@ -99,9 +116,27 @@ require.config({ '''); } - script.writeln(javaScript); + if (isNewDDC) { + // The code depends on ddc_module_loader already being loaded in the page. + // Wrap in a function that we'll call after the module loader is loaded. + script.writeln('let __ddcInitCode = function() {$javaScript}'); - script.writeln(''' + script.writeln(''' +function contextLoaded() { + __ddcInitCode(); + dartDevEmbedder.runMain('package:dartpad_sample/bootstrap.dart', {}); +}'''); + if (isFlutter) { + script.writeln( + 'require(["dart_sdk_new", "flutter_web_new", "ddc_module_loader"], contextLoaded);'); + } else { + script.writeln( + 'require(["dart_sdk_new", "ddc_module_loader"], contextLoaded);'); + } + } else { + script.writeln(javaScript); + + script.writeln(''' require(['dart_sdk'], function(sdk) { 'use strict'; @@ -128,6 +163,7 @@ require(["dartpad_main", "dart_sdk"], function(dartpad_main, dart_sdk) { } }); '''); + } return script.toString(); } diff --git a/pkgs/dartpad_ui/lib/main.dart b/pkgs/dartpad_ui/lib/main.dart index 9929c119f..be6406e07 100644 --- a/pkgs/dartpad_ui/lib/main.dart +++ b/pkgs/dartpad_ui/lib/main.dart @@ -323,6 +323,7 @@ class _DartPadMainPageState extends State appServices: appServices, onFormat: _handleFormatting, onCompileAndRun: appServices.performCompileAndRun, + onCompileAndReload: appServices.performCompileAndReload, key: _editorKey, ); @@ -646,12 +647,14 @@ class EditorWithButtons extends StatelessWidget { required this.appServices, required this.onFormat, required this.onCompileAndRun, + required this.onCompileAndReload, }); final AppModel appModel; final AppServices appServices; final VoidCallback onFormat; final VoidCallback onCompileAndRun; + final VoidCallback onCompileAndReload; @override Widget build(BuildContext context) { @@ -710,6 +713,24 @@ class EditorWithButtons extends StatelessWidget { ), const SizedBox(width: defaultSpacing), // Run action + ValueListenableBuilder( + valueListenable: appModel.showReload, + builder: (_, bool value, __) { + if (!value) return const SizedBox(); + return ValueListenableBuilder( + valueListenable: appModel.canReload, + builder: (_, bool value, __) { + return PointerInterceptor( + child: ReloadButton( + onPressed: + value ? onCompileAndReload : null, + ), + ); + }, + ); + }), + const SizedBox(width: defaultSpacing), + // Run action ValueListenableBuilder( valueListenable: appModel.compilingBusy, builder: (_, bool value, __) { diff --git a/pkgs/dartpad_ui/lib/model.dart b/pkgs/dartpad_ui/lib/model.dart index efb915fc0..2420bd399 100644 --- a/pkgs/dartpad_ui/lib/model.dart +++ b/pkgs/dartpad_ui/lib/model.dart @@ -22,6 +22,9 @@ abstract class ExecutionService { String javaScript, { String? modulesBaseUrl, String? engineVersion, + required bool isNewDDC, + required bool reload, + required bool isFlutter, }); Stream get onStdout; Future reset(); @@ -38,7 +41,7 @@ abstract class EditorService { } class AppModel { - bool? _appIsFlutter; + final ValueNotifier _appIsFlutter = ValueNotifier(null); bool? _usesPackageWeb; final ValueNotifier appReady = ValueNotifier(false); @@ -54,6 +57,9 @@ class AppModel { final ValueNotifier compilingBusy = ValueNotifier(false); final ValueNotifier docHelpBusy = ValueNotifier(false); + final ValueNotifier hasRun = ValueNotifier(false); + final ValueNotifier canReload = ValueNotifier(false); + final StatusController editorStatus = StatusController(); final ValueNotifier runtimeVersions = ValueNotifier(null); @@ -69,8 +75,25 @@ class AppModel { final ValueNotifier vimKeymapsEnabled = ValueNotifier(false); + bool _consoleShowingError = false; + final ValueNotifier showReload = ValueNotifier(false); + final ValueNotifier _useNewDDC = ValueNotifier(false); + final ValueNotifier currentDeltaDill = ValueNotifier(null); + AppModel() { consoleOutput.addListener(_recalcLayout); + void updateCanReload() => canReload.value = + hasRun.value && !compilingBusy.value && currentDeltaDill.value != null; + hasRun.addListener(updateCanReload); + compilingBusy.addListener(updateCanReload); + currentDeltaDill.addListener(updateCanReload); + + void updateShowReload() { + showReload.value = _useNewDDC.value && (_appIsFlutter.value ?? false); + } + + _useNewDDC.addListener(updateShowReload); + _appIsFlutter.addListener(updateShowReload); _splitSubscription = splitDragStateManager.onSplitDragUpdated.listen((SplitDragState value) { @@ -82,7 +105,10 @@ class AppModel { consoleOutput.value += '$str\n'; } - void clearConsole() => consoleOutput.value = ''; + void clearConsole() { + consoleOutput.value = ''; + _consoleShowingError = false; + } void dispose() { consoleOutput.removeListener(_recalcLayout); @@ -91,7 +117,7 @@ class AppModel { void _recalcLayout() { final hasConsoleText = consoleOutput.value.isNotEmpty; - final isFlutter = _appIsFlutter; + final isFlutter = _appIsFlutter.value; final usesPackageWeb = _usesPackageWeb; if (isFlutter == null || usesPackageWeb == null) { @@ -148,12 +174,25 @@ class AppServices { // TODO: Consider using DebounceStreamTransformer from package:rxdart. Timer? reanalysisDebouncer; + static const Set _hotReloadableChannels = { + Channel.localhost, + Channel.main, + }; + AppServices(this.appModel, Channel channel) { _channel.value = channel; services = ServicesClient(_httpClient, rootUrl: channel.url); appModel.sourceCodeController.addListener(_handleCodeChanged); appModel.analysisIssues.addListener(_updateEditorProblemsStatus); + + void updateUseNewDDC() { + appModel._useNewDDC.value = + _hotReloadableChannels.contains(_channel.value); + } + + updateUseNewDDC(); + _channel.addListener(updateUseNewDDC); } EditorService? get editorService => _editorService; @@ -304,20 +343,34 @@ class AppServices { appModel.appReady.value = true; } - Future performCompileAndRun() async { + Future _performCompileAndAction({required bool reload}) async { final source = appModel.sourceCodeController.text; final progress = appModel.editorStatus.showMessage(initialText: 'Compiling…'); try { - final response = await _compileDDC(CompileRequest(source: source)); - appModel.clearConsole(); + CompileDDCResponse response; + if (!appModel._useNewDDC.value) { + response = await _compileDDC(CompileRequest(source: source)); + } else if (reload) { + response = await _compileNewDDCReload(CompileRequest( + source: source, deltaDill: appModel.currentDeltaDill.value!)); + } else { + response = await _compileNewDDC(CompileRequest(source: source)); + } + if (!reload || appModel._consoleShowingError) { + appModel.clearConsole(); + } _executeJavaScript( response.result, modulesBaseUrl: response.modulesBaseUrl, engineVersion: appModel.runtimeVersions.value?.engineVersion, dartSource: source, + isNewDDC: appModel._useNewDDC.value, + reload: reload, ); + appModel.currentDeltaDill.value = response.deltaDill; + appModel.hasRun.value = true; } catch (error) { appModel.clearConsole(); @@ -329,11 +382,20 @@ class AppServices { } else { appModel.appendLineToConsole('$error'); } + appModel._consoleShowingError = true; } finally { progress.close(); } } + Future performCompileAndReload() async { + _performCompileAndAction(reload: true); + } + + Future performCompileAndRun() async { + _performCompileAndAction(reload: false); + } + Future format(SourceRequest request) async { try { appModel.formattingBusy.value = true; @@ -365,6 +427,25 @@ class AppServices { } } + Future _compileNewDDC(CompileRequest request) async { + try { + appModel.compilingBusy.value = true; + return await services.compileNewDDC(request); + } finally { + appModel.compilingBusy.value = false; + } + } + + Future _compileNewDDCReload( + CompileRequest request) async { + try { + appModel.compilingBusy.value = true; + return await services.compileNewDDCReload(request); + } finally { + appModel.compilingBusy.value = false; + } + } + void registerExecutionService(ExecutionService? executionService) { // unregister the old stdoutSub?.cancel(); @@ -388,16 +469,20 @@ class AppServices { required String dartSource, String? modulesBaseUrl, String? engineVersion, + required bool isNewDDC, + required bool reload, }) { - appModel._appIsFlutter = hasFlutterWebMarker(javaScript); + final appIsFlutter = hasFlutterWebMarker(javaScript, isNewDDC: isNewDDC); + appModel._appIsFlutter.value = appIsFlutter; appModel._usesPackageWeb = hasPackageWebImport(dartSource); appModel._recalcLayout(); - _executionService?.execute( - javaScript, - modulesBaseUrl: modulesBaseUrl, - engineVersion: engineVersion, - ); + _executionService?.execute(javaScript, + modulesBaseUrl: modulesBaseUrl, + engineVersion: engineVersion, + reload: reload, + isNewDDC: isNewDDC, + isFlutter: appIsFlutter); } void dispose() { diff --git a/pkgs/dartpad_ui/lib/utils.dart b/pkgs/dartpad_ui/lib/utils.dart index c008f888e..ca6b991b6 100644 --- a/pkgs/dartpad_ui/lib/utils.dart +++ b/pkgs/dartpad_ui/lib/utils.dart @@ -35,14 +35,19 @@ RelativeRect calculatePopupMenuPosition( ); } -bool hasFlutterWebMarker(String javaScript) { +bool hasFlutterWebMarker(String javaScript, {required bool isNewDDC}) { const marker1 = 'window.flutterConfiguration'; if (javaScript.contains(marker1)) { return true; } - - // define('dartpad_main', ['dart_sdk', 'flutter_web'] - if (javaScript.contains("define('") && javaScript.contains("'flutter_web'")) { + if (isNewDDC && + javaScript.contains('defineLibrary(') && + javaScript.contains('importLibrary("package:flutter/')) { + return true; + // define('dartpad_main', ['dart_sdk', 'flutter_web'] + } else if (!isNewDDC && + javaScript.contains("define('") && + javaScript.contains("'flutter_web'")) { return true; } diff --git a/pkgs/dartpad_ui/lib/widgets.dart b/pkgs/dartpad_ui/lib/widgets.dart index 2b86324eb..deb9304a5 100644 --- a/pkgs/dartpad_ui/lib/widgets.dart +++ b/pkgs/dartpad_ui/lib/widgets.dart @@ -93,15 +93,42 @@ class MiniIconButton extends StatelessWidget { } } -class RunButton extends StatelessWidget { +class RunButton extends ActionButton { + const RunButton({super.key, super.onPressed}) + : super( + text: 'Run', + icon: const Icon( + Icons.play_arrow, + color: Colors.black, + size: 20, + ), + ); +} + +class ReloadButton extends ActionButton { + const ReloadButton({super.key, super.onPressed}) + : super( + text: 'Reload', + icon: const Icon( + Icons.refresh, + color: Colors.black, + size: 20, + ), + ); +} + +abstract class ActionButton extends StatelessWidget { final VoidCallback? onPressed; + final String text; + final Icon icon; - const RunButton({this.onPressed, super.key}); + const ActionButton( + {this.onPressed, super.key, required this.text, required this.icon}); @override Widget build(BuildContext context) { return Tooltip( - message: 'Run', + message: text, waitDuration: tooltipDelay, child: TextButton( style: ButtonStyle( @@ -118,17 +145,13 @@ class RunButton extends StatelessWidget { ), ), onPressed: onPressed, - child: const Row( + child: Row( children: [ - Icon( - Icons.play_arrow, - color: Colors.black, - size: 20, - ), - SizedBox(width: 8), + icon, + const SizedBox(width: 8), Text( - 'Run', - style: TextStyle(color: Colors.black), + text, + style: const TextStyle(color: Colors.black), ), ], ), diff --git a/pkgs/dartpad_ui/web/frame.js b/pkgs/dartpad_ui/web/frame.js index 9ae404571..d88ce0ac5 100644 --- a/pkgs/dartpad_ui/web/frame.js +++ b/pkgs/dartpad_ui/web/frame.js @@ -22,16 +22,26 @@ function messageHandler(e) { var obj = e.data; if (window.origin !== 'null' || e.source !== window.parent) return; if (obj.command === 'execute') { - runFlutterApp(obj.js, obj.canvasKitBaseUrl); + runFlutterApp(obj.js, obj.canvasKitBaseUrl, false); + } else if (obj.command === 'executeReload') { + runFlutterApp(obj.js, obj.canvasKitBaseUrl, true); } }; -function runFlutterApp(compiledScript, canvasKitBaseUrl) { - var blob = new Blob([compiledScript], {type: 'text/javascript'}); +function runFlutterApp(compiledScript, canvasKitBaseUrl, reload) { + var blob = new Blob([compiledScript], { type: 'text/javascript' }); var url = URL.createObjectURL(blob); + if (reload) { + dartDevEmbedder.hotReload([url], ['package:dartpad_sample/main.dart']).then(function () { + if (dartDevEmbedder.debugger.extensionNames.includes('ext.flutter.reassemble')) { + dartDevEmbedder.debugger.invokeExtension('ext.flutter.reassemble', '{}'); + } + }); + return; + } _flutter.loader.loadEntrypoint({ entrypointUrl: url, - onEntrypointLoaded: async function(engineInitializer) { + onEntrypointLoaded: async function (engineInitializer) { let appRunner = await engineInitializer.initializeEngine({ canvasKitBaseUrl: canvasKitBaseUrl, assetBase: 'frame/',