diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 8a6ba0c5a..aa97c636b 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,5 +1,8 @@ ## 24.4.1-wip +- Fixed an issue where we didn't wait until all scripts were parsed before + recomputing metadata on a hot reload. + ## 24.4.0 - Added support for breakpoint registering on a hot reload with the DDC library bundle format using PausePostRequests. diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart index c96c2bdbe..e68e5d315 100644 --- a/dwds/lib/src/debugging/debugger.dart +++ b/dwds/lib/src/debugging/debugger.dart @@ -56,6 +56,9 @@ class Debugger extends Domain { int _frameErrorCount = 0; + final StreamController parsedScriptsController = + StreamController.broadcast(); + Debugger._( this._remoteDebugger, this._streamNotify, @@ -489,6 +492,9 @@ class Debugger extends Domain { final scriptPath = _pathForChromeScript(e.script.url); if (scriptPath != null) { chromePathToRuntimeScriptId[scriptPath] = e.script.scriptId; + if (parsedScriptsController.hasListener) { + parsedScriptsController.sink.add(e.script.url); + } } } diff --git a/dwds/lib/src/debugging/modules.dart b/dwds/lib/src/debugging/modules.dart index 15f10b06a..a57bfb5b2 100644 --- a/dwds/lib/src/debugging/modules.dart +++ b/dwds/lib/src/debugging/modules.dart @@ -98,9 +98,6 @@ class Modules { ) async { final serverPath = await globalToolConfiguration.loadStrategy .serverPathForModule(entrypoint, module); - // TODO(srujzs): We should wait until all scripts are parsed before - // accessing after a hot reload. See - // https://github.com/dart-lang/webdev/issues/2640. return chromePathToRuntimeScriptId[serverPath]; } diff --git a/dwds/lib/src/injected/client.js b/dwds/lib/src/injected/client.js index e85ffd662..85c394005 100644 --- a/dwds/lib/src/injected/client.js +++ b/dwds/lib/src/injected/client.js @@ -26879,7 +26879,7 @@ }; A.main__closure.prototype = { call$0() { - return A.FutureOfJSAnyToJSPromise_get_toJS(this.manager._restarter.hotReloadStart$1(A.hotReloadSourcesPath()), type$.JSObject); + return A.FutureOfJSAnyToJSPromise_get_toJS(this.manager._restarter.hotReloadStart$1(A.hotReloadSourcesPath()), type$.JSArray_nullable_Object); }, $signature: 10 }; @@ -27296,8 +27296,8 @@ }, hotReloadStart$1(hotReloadSourcesPath) { var $async$goto = 0, - $async$completer = A._makeAsyncAwaitCompleter(type$.JSObject), - $async$returnValue, $async$self = this, t4, srcModuleLibraries, filesToLoad, _this, librariesToReload, t5, t6, srcModuleLibraryCast, module, libraries, t7, t1, t2, t3, xhr, $async$temp1, $async$temp2, $async$temp3; + $async$completer = A._makeAsyncAwaitCompleter(type$.JSArray_nullable_Object), + $async$returnValue, $async$self = this, t4, srcModuleLibraries, filesToLoad, librariesToReload, t5, t6, t7, srcModuleLibraryCast, src, libraries, t8, t1, t2, t3, xhr, $async$temp1, $async$temp2, $async$temp3; var $async$hotReloadStart$1 = A._wrapJsFunctionForAsync(function($async$errorCode, $async$result) { if ($async$errorCode === 1) return A._asyncRethrow($async$result, $async$completer); @@ -27324,23 +27324,21 @@ srcModuleLibraries = $async$temp1.cast$1$0$ax($async$temp2._as($async$temp3.decode$1($async$result)), type$.Map_dynamic_dynamic); t1 = type$.JSArray_nullable_Object; filesToLoad = t1._as(new t2.Array()); - _this = {}; librariesToReload = t1._as(new t2.Array()); - for (t1 = srcModuleLibraries.get$iterator(srcModuleLibraries), t5 = type$.String, t6 = type$.Object; t1.moveNext$0();) { - srcModuleLibraryCast = t1.get$current().cast$2$0(0, t5, t6); - filesToLoad.push(A._asString(srcModuleLibraryCast.$index(0, "src"))); - module = A._asString(srcModuleLibraryCast.$index(0, "module")); - libraries = J.cast$1$0$ax(t4._as(srcModuleLibraryCast.$index(0, "libraries")), t5); - _this[module] = A.jsify(libraries); - for (t7 = libraries.get$iterator(libraries); t7.moveNext$0();) - librariesToReload.push(t7.get$current()); + for (t5 = srcModuleLibraries.get$iterator(srcModuleLibraries), t6 = type$.String, t7 = type$.Object; t5.moveNext$0();) { + srcModuleLibraryCast = t5.get$current().cast$2$0(0, t6, t7); + src = A._asString(srcModuleLibraryCast.$index(0, "src")); + libraries = J.cast$1$0$ax(t4._as(srcModuleLibraryCast.$index(0, "libraries")), t6); + filesToLoad.push(src); + for (t8 = libraries.get$iterator(libraries); t8.moveNext$0();) + librariesToReload.push(t8.get$current()); } t3._as(t3._as(t2.dartDevEmbedder).config).capturedHotReloadEndHandler = A._functionToJS1(new A.DdcLibraryBundleRestarter_hotReloadStart_closure0($async$self)); $async$goto = 4; return A._asyncAwait(A.promiseToFuture(t3._as(t3._as(t2.dartDevEmbedder).hotReload(filesToLoad, librariesToReload)), type$.nullable_Object), $async$hotReloadStart$1); case 4: // returning from await. - $async$returnValue = _this; + $async$returnValue = t1._as(A.jsify(srcModuleLibraries)); // goto return $async$goto = 1; break; diff --git a/dwds/lib/src/loaders/ddc_library_bundle.dart b/dwds/lib/src/loaders/ddc_library_bundle.dart index 3fb592dde..a8f98b7ad 100644 --- a/dwds/lib/src/loaders/ddc_library_bundle.dart +++ b/dwds/lib/src/loaders/ddc_library_bundle.dart @@ -110,7 +110,8 @@ class DdcLibraryBundleStrategy extends LoadStrategy { /// ```json /// [ /// { - /// "src": "", + /// "src": "/", + /// "module": "", /// "libraries": ["", ""], /// }, /// ] @@ -118,6 +119,7 @@ class DdcLibraryBundleStrategy extends LoadStrategy { /// /// `src`: A string that corresponds to the file path containing a DDC library /// bundle. + /// `module`: The name of the library bundle in `src`. /// `libraries`: An array of strings containing the libraries that were /// compiled in `src`. /// diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index c98bbc8ab..37c50be82 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -1221,6 +1221,22 @@ class ChromeProxyService implements VmServiceInterface { Future _performClientSideHotReload(String isolateId) async { _logger.info('Attempting a hot reload'); + final debugger = await debuggerFuture; + final reloadedSrcs = {}; + final computedReloadedSrcs = Completer(); + final parsedAllReloadedSrcs = Completer(); + // Wait until all the reloaded scripts are parsed before we reinitialize + // metadata below. + final parsedScriptsSubscription = debugger.parsedScriptsController.stream + .listen((url) { + computedReloadedSrcs.future.then((_) { + reloadedSrcs.remove(Uri.parse(url).normalizePath().path); + if (reloadedSrcs.isEmpty) { + parsedAllReloadedSrcs.complete(); + } + }); + }); + // Initiate a hot reload. _logger.info('Issuing \$dartHotReloadStartDwds request'); final remoteObject = await inspector.jsEvaluate( @@ -1228,8 +1244,19 @@ class ChromeProxyService implements VmServiceInterface { awaitPromise: true, returnByValue: true, ); - final reloadedModulesToLibraries = - (remoteObject.value as Map).cast(); + final reloadedSrcModuleLibraries = (remoteObject.value as List).cast(); + final reloadedModulesToLibraries = >{}; + for (final srcModuleLibrary in reloadedSrcModuleLibraries) { + final srcModuleLibraryCast = srcModuleLibrary.cast(); + reloadedSrcs.add( + Uri.parse(srcModuleLibraryCast['src'] as String).normalizePath().path, + ); + reloadedModulesToLibraries[srcModuleLibraryCast['module'] as String] = + (srcModuleLibraryCast['libraries'] as List).cast(); + } + computedReloadedSrcs.complete(); + if (reloadedSrcs.isNotEmpty) await parsedAllReloadedSrcs.future; + await parsedScriptsSubscription.cancel(); if (!pauseIsolatesOnStart) { // Finish hot reload immediately. diff --git a/dwds/test/common/chrome_proxy_service_common.dart b/dwds/test/common/chrome_proxy_service_common.dart index 92b8a3e4e..17eb5a0a0 100644 --- a/dwds/test/common/chrome_proxy_service_common.dart +++ b/dwds/test/common/chrome_proxy_service_common.dart @@ -2225,27 +2225,6 @@ void runTests({ ); }); - test('reloadSources', () async { - final service = context.service; - final vm = await service.getVM(); - final isolateId = vm.isolates!.first.id!; - final report = await service.reloadSources(isolateId); - // TODO(srujzs): This naturally fails regardless of the module format - // because we didn't set up prerequisite state (the reload scripts). We - // should create new tests for hot reload within DWDS and remove this - // test. - expect(report.success, false); - // Make sure that a notice was provided in the expected format. - final notices = report.json?['notices']; - expect(notices, isNotNull); - expect(notices is List, isTrue); - final noticeList = (notices as List).cast(); - expect(noticeList, isNotEmpty); - final message = noticeList[0]['message']; - expect(message, isNotNull); - expect(message is String && message.isNotEmpty, isTrue); - }); - test('setIsolatePauseMode', () async { final service = context.service; final vm = await service.getVM(); diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart index 68687d0f6..1f8686cbd 100644 --- a/dwds/test/fixtures/context.dart +++ b/dwds/test/fixtures/context.dart @@ -339,11 +339,9 @@ class TestContext { _hostname = appMetadata.hostname; await webRunner.run( frontendServerFileSystem, - _hostname, - assetServerPort, - filePathToServe, - initialCompile: true, - fullRestart: false, + hostname: _hostname, + port: assetServerPort, + index: filePathToServe, ); if (testSettings.enableExpressionEvaluation) { @@ -433,6 +431,10 @@ class TestContext { final appConnectionCompleter = Completer(); final connection = ChromeConnection('localhost', debugPort); + // TODO(srujzs): In the case of the frontend server, it doesn't make sense + // that we initialize a new HTTP server instead of reusing the one in + // `TestAssetServer`. We should instead use that one to align with Flutter + // tools. _testServer = await TestServer.start( debugSettings: debugSettings.copyWith( expressionCompiler: expressionCompiler, @@ -590,13 +592,9 @@ class TestContext { } Future recompile({required bool fullRestart}) async { - await webRunner.run( - frontendServerFileSystem, - _hostname, - await findUnusedPort(), - webCompatiblePath([project.directoryToServe, project.filePathToServe]), - initialCompile: false, + await webRunner.rerun( fullRestart: fullRestart, + fileServerUri: Uri.parse('http://${testServer.host}:${testServer.port}'), ); return; } diff --git a/dwds/test/hot_reload_breakpoints_test.dart b/dwds/test/hot_reload_breakpoints_test.dart index bd9ec0bff..5b120d845 100644 --- a/dwds/test/hot_reload_breakpoints_test.dart +++ b/dwds/test/hot_reload_breakpoints_test.dart @@ -8,7 +8,6 @@ library; import 'dart:async'; -import 'dart:io'; import 'package:dwds/expression_compiler.dart'; import 'package:test/test.dart'; @@ -207,6 +206,37 @@ void main() { Future waitForBreakpoint() => expectLater(stream, emitsThrough(_hasKind(EventKind.kPauseBreakpoint))); + test('empty hot reload keeps breakpoints', () async { + final genString = 'main gen0'; + + final bp = await addBreakpoint( + file: mainFile, + breakpointMarker: callLogMarker, + ); + + var breakpointFuture = waitForBreakpoint(); + + await callEvaluate(); + + // Should break at `callLog`. + await breakpointFuture; + await resumeAndExpectLog(genString); + + await context.recompile(fullRestart: false); + + await hotReloadAndHandlePausePost([ + (file: mainFile, breakpointMarker: callLogMarker, bp: bp), + ]); + + breakpointFuture = waitForBreakpoint(); + + await callEvaluate(); + + // Should break at `callLog`. + await breakpointFuture; + await resumeAndExpectLog(genString); + }); + test('after edit and hot reload, breakpoint is in new file', () async { final oldString = 'main gen0'; final newString = 'main gen1'; @@ -359,52 +389,109 @@ void main() { }, ); - test( - 'breakpoint in captured code is deleted', - () async { - var bp = await addBreakpoint( - file: mainFile, - breakpointMarker: capturedStringMarker, - ); + // Test that we wait for all scripts to be parsed first before computing + // location metadata. + test('after adding many files and putting breakpoint in the last one,' + 'breakpoint is correctly registered', () async { + final genLog = 'main gen0'; - final oldLog = "log('\$mainValue');"; - final newLog = "log('\${closure()}');"; - await makeEditAndRecompile(mainFile, oldLog, newLog); + final bp = await addBreakpoint( + file: mainFile, + breakpointMarker: callLogMarker, + ); - bp = - (await hotReloadAndHandlePausePost([ - (file: mainFile, breakpointMarker: capturedStringMarker, bp: bp), - ])).first; + var breakpointFuture = waitForBreakpoint(); - final breakpointFuture = waitForBreakpoint(); + await callEvaluate(); - await callEvaluate(); + // Should break at `callLog`. + await breakpointFuture; + await resumeAndExpectLog(genLog); - // Should break at `capturedString`. - await breakpointFuture; - final oldCapturedString = 'captured closure gen0'; - // Closure gets evaluated for the first time. - await resumeAndExpectLog(oldCapturedString); - - final newCapturedString = 'captured closure gen1'; - await makeEditAndRecompile( - mainFile, - oldCapturedString, - newCapturedString, + // Add library files, import them, but only refer to the last one in main. + final numFiles = 50; + for (var i = 1; i <= numFiles; i++) { + final libFile = 'library$i.dart'; + context.addLibraryFile( + libFileName: libFile, + contents: '''String get libraryValue$i { + return 'lib gen$i'; // Breakpoint: libValue$i + }''', ); + final oldImports = "import 'dart:js_interop';"; + final newImports = + '$oldImports\n' + "import 'package:_test_hot_reload_breakpoints/$libFile';"; + makeEdit(mainFile, oldImports, newImports); + } + final oldLog = "log('\$mainValue');"; + final newLog = "log('\$libraryValue$numFiles');"; + await makeEditAndRecompile(mainFile, oldLog, newLog); - await hotReloadAndHandlePausePost([ - (file: mainFile, breakpointMarker: capturedStringMarker, bp: bp), - ]); + await hotReloadAndHandlePausePost([ + (file: mainFile, breakpointMarker: callLogMarker, bp: bp), + ( + file: 'library$numFiles.dart', + breakpointMarker: 'libValue$numFiles', + bp: null, + ), + ]); - // Breakpoint should not be hit as it's now deleted. We should also see - // the old string still as the closure has not been reevaluated. - await callEvaluateAndExpectLog(oldCapturedString); - }, - // TODO(srujzs): Re-enable after - // https://github.com/dart-lang/webdev/issues/2640. - skip: Platform.isWindows, - ); + breakpointFuture = waitForBreakpoint(); + + await callEvaluate(); + + // Should break at `callLog`. + await breakpointFuture; + + breakpointFuture = waitForBreakpoint(); + + await resume(); + // Should break at the breakpoint in the last file. + await breakpointFuture; + await resumeAndExpectLog('lib gen$numFiles'); + }); + + test('breakpoint in captured code is deleted', () async { + var bp = await addBreakpoint( + file: mainFile, + breakpointMarker: capturedStringMarker, + ); + + final oldLog = "log('\$mainValue');"; + final newLog = "log('\${closure()}');"; + await makeEditAndRecompile(mainFile, oldLog, newLog); + + bp = + (await hotReloadAndHandlePausePost([ + (file: mainFile, breakpointMarker: capturedStringMarker, bp: bp), + ])).first; + + final breakpointFuture = waitForBreakpoint(); + + await callEvaluate(); + + // Should break at `capturedString`. + await breakpointFuture; + final oldCapturedString = 'captured closure gen0'; + // Closure gets evaluated for the first time. + await resumeAndExpectLog(oldCapturedString); + + final newCapturedString = 'captured closure gen1'; + await makeEditAndRecompile( + mainFile, + oldCapturedString, + newCapturedString, + ); + + await hotReloadAndHandlePausePost([ + (file: mainFile, breakpointMarker: capturedStringMarker, bp: bp), + ]); + + // Breakpoint should not be hit as it's now deleted. We should also see + // the old string still as the closure has not been reevaluated. + await callEvaluateAndExpectLog(oldCapturedString); + }); }, timeout: Timeout.factor(2)); group('when pause_isolates_on_start is false', () { diff --git a/dwds/web/reloader/ddc_library_bundle_restarter.dart b/dwds/web/reloader/ddc_library_bundle_restarter.dart index 6ce3faea2..5752cb983 100644 --- a/dwds/web/reloader/ddc_library_bundle_restarter.dart +++ b/dwds/web/reloader/ddc_library_bundle_restarter.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; -import 'dart:js_interop_unsafe'; import 'package:dwds/src/utilities/shared.dart'; @@ -86,7 +85,7 @@ class DdcLibraryBundleRestarter implements Restarter { } @override - Future hotReloadStart(String hotReloadSourcesPath) async { + Future> hotReloadStart(String hotReloadSourcesPath) async { final completer = Completer(); final xhr = _XMLHttpRequest(); xhr.withCredentials = true; @@ -104,15 +103,13 @@ class DdcLibraryBundleRestarter implements Restarter { final srcModuleLibraries = (json.decode(responseText) as List).cast(); final filesToLoad = JSArray(); - final moduleMap = JSObject(); final librariesToReload = JSArray(); for (final srcModuleLibrary in srcModuleLibraries) { final srcModuleLibraryCast = srcModuleLibrary.cast(); - filesToLoad.push((srcModuleLibraryCast['src'] as String).toJS); - final module = srcModuleLibraryCast['module'] as String; + final src = srcModuleLibraryCast['src'] as String; final libraries = (srcModuleLibraryCast['libraries'] as List).cast(); - moduleMap[module] = libraries.jsify(); + filesToLoad.push(src.toJS); for (final library in libraries) { librariesToReload.push(library.toJS); } @@ -122,7 +119,7 @@ class DdcLibraryBundleRestarter implements Restarter { _capturedHotReloadEndCallback = hotReloadEndCallback; }.toJS; await _dartDevEmbedder.hotReload(filesToLoad, librariesToReload).toDart; - return moduleMap; + return srcModuleLibraries.jsify() as JSArray; } @override diff --git a/dwds/web/reloader/ddc_restarter.dart b/dwds/web/reloader/ddc_restarter.dart index 30399cfcb..ca324f540 100644 --- a/dwds/web/reloader/ddc_restarter.dart +++ b/dwds/web/reloader/ddc_restarter.dart @@ -47,7 +47,7 @@ class DdcRestarter implements Restarter { ); @override - Future hotReloadStart(String hotReloadSourcesPath) => + Future> hotReloadStart(String hotReloadSourcesPath) => throw UnimplementedError( 'Hot reload is not supported for the DDC module format.', ); diff --git a/dwds/web/reloader/manager.dart b/dwds/web/reloader/manager.dart index dc6c5a0b4..52ee179b5 100644 --- a/dwds/web/reloader/manager.dart +++ b/dwds/web/reloader/manager.dart @@ -42,27 +42,28 @@ class ReloadingManager { await _restarter.hotReloadEnd(); } - /// Computes the sources and libraries to reload, loads them into the page, - /// and returns a map of module names to libraries using - /// [hotReloadSourcesPath] as the path to a JSONified list of maps which + /// Using [hotReloadSourcesPath] as the path to a JSONified list of maps which /// follows the following format: /// /// ```json /// [ /// { - /// "src": "", + /// "src": "/", /// "module": "", /// "libraries": ["", ""], /// }, /// ] /// ``` /// + /// computes the sources and libraries to reload, loads them into the page, + /// and returns a JS version of the list of maps. + /// /// `src`: A string that corresponds to the file path containing a DDC library /// bundle. /// `module`: The name of the library bundle in `src`. /// `libraries`: An array of strings containing the libraries that were /// compiled in `src`. - Future hotReloadStart(String hotReloadSourcesPath) => + Future> hotReloadStart(String hotReloadSourcesPath) => _restarter.hotReloadStart(hotReloadSourcesPath); /// Does a hard reload of the application. diff --git a/dwds/web/reloader/require_restarter.dart b/dwds/web/reloader/require_restarter.dart index 01115fffb..88cc5b98e 100644 --- a/dwds/web/reloader/require_restarter.dart +++ b/dwds/web/reloader/require_restarter.dart @@ -168,7 +168,7 @@ class RequireRestarter implements Restarter { ); @override - Future hotReloadStart(String hotReloadSourcesPath) => + Future> hotReloadStart(String hotReloadSourcesPath) => throw UnimplementedError( 'Hot reload is not supported for the AMD module format.', ); diff --git a/dwds/web/reloader/restarter.dart b/dwds/web/reloader/restarter.dart index 31cb09484..aee6529c7 100644 --- a/dwds/web/reloader/restarter.dart +++ b/dwds/web/reloader/restarter.dart @@ -13,25 +13,26 @@ abstract class Restarter { /// reload by pushing the libraries into the Dart runtime. Future hotReloadEnd(); - /// Computes the sources and libraries to reload, loads them into the page, - /// and returns a map of module names to libraries using - /// [hotReloadSourcesPath] as the path to a JSONified list of maps which + /// Using [hotReloadSourcesPath] as the path to a JSONified list of maps which /// follows the following format: /// /// ```json /// [ /// { - /// "src": "", + /// "src": "/", /// "module": "", /// "libraries": ["", ""], /// }, /// ] /// ``` /// + /// computes the sources and libraries to reload, loads them into the page, + /// and returns a JS version of the list of maps. + /// /// `src`: A string that corresponds to the file path containing a DDC library /// bundle. /// `module`: The name of the library bundle in `src`. /// `libraries`: An array of strings containing the libraries that were /// compiled in `src`. - Future hotReloadStart(String hotReloadSourcesPath); + Future> hotReloadStart(String hotReloadSourcesPath); } diff --git a/frontend_server_common/lib/src/bootstrap.dart b/frontend_server_common/lib/src/bootstrap.dart index b45ff45c2..8f1073278 100644 --- a/frontend_server_common/lib/src/bootstrap.dart +++ b/frontend_server_common/lib/src/bootstrap.dart @@ -503,7 +503,7 @@ $_simpleLoaderScript for (var i = 0; i < scripts.length; i++) { var script = scripts[i]; if (script.id == null) continue; - var src = _currentDirectory + '/' + script.src.toString(); + var src = script.src.toString(); var oldSrc = window.\$dartLoader.moduleIdToUrl.get(script.id); // We might actually load from a different uri, delete the old one diff --git a/frontend_server_common/lib/src/devfs.dart b/frontend_server_common/lib/src/devfs.dart index a334b832a..b6caf471d 100644 --- a/frontend_server_common/lib/src/devfs.dart +++ b/frontend_server_common/lib/src/devfs.dart @@ -77,6 +77,10 @@ class WebDevFS { required List invalidatedFiles, required bool initialCompile, required bool fullRestart, + // The uri of the `HttpServer` that handles file requests. + // TODO(srujzs): This should be the same as the uri of the AssetServer to + // align with Flutter tools, but currently is not. Delete when that's fixed. + required Uri? fileServerUri, }) async { final mainPath = mainUri.toFilePath(); final outputDirectory = fileSystem.directory( @@ -215,9 +219,9 @@ class WebDevFS { compilerOptions.canaryFeatures && !initialCompile) { if (fullRestart) { - performRestart(modules); + performRestart(modules, fileServerUri!); } else { - performReload(modules, prefix); + performReload(modules, prefix, fileServerUri!); } } return UpdateFSReport( @@ -235,15 +239,15 @@ class WebDevFS { /// ```json /// [ /// { - /// "src": "", + /// "src": "/", /// "id": "", /// }, /// ] /// ``` - void performRestart(List modules) { + void performRestart(List modules, Uri fileServerUri) { final srcIdsList = >[]; for (final src in modules) { - srcIdsList.add({'src': src, 'id': src}); + srcIdsList.add({'src': '$fileServerUri/$src', 'id': src}); } assetServer.writeFile('restart_scripts.json', json.encode(srcIdsList)); } @@ -263,7 +267,7 @@ class WebDevFS { /// ```json /// [ /// { - /// "src": "", + /// "src": "/", /// "module": "", /// "libraries": ["", ""], /// }, @@ -275,7 +279,8 @@ class WebDevFS { /// /// [entrypointDirectory] is used to make the module paths relative to the /// entrypoint, which is needed in order to load `src`s correctly. - void performReload(List modules, String entrypointDirectory) { + void performReload( + List modules, String entrypointDirectory, Uri fileServerUri) { final moduleToLibrary = >[]; for (final module in modules) { final metadata = ModuleMetadata.fromJson( @@ -285,7 +290,7 @@ class WebDevFS { ); final libraries = metadata.libraries.keys.toList(); moduleToLibrary.add({ - 'src': _findModuleToLoad(module, entrypointDirectory), + 'src': '$fileServerUri/$module', 'module': metadata.name, 'libraries': libraries }); @@ -293,24 +298,6 @@ class WebDevFS { assetServer.writeFile(reloadScriptsFileName, json.encode(moduleToLibrary)); } - /// Given a [module] location from the [ModuleMetadata], return its path in - /// the server relative to the entrypoint in [entrypointDirectory]. - /// - /// This is needed in cases where the entrypoint is in a subdirectory in the - /// package. - String _findModuleToLoad(String module, String entrypointDirectory) { - if (entrypointDirectory.isEmpty) return module; - assert(entrypointDirectory.endsWith('/')); - if (module.startsWith(entrypointDirectory)) { - return module.substring(entrypointDirectory.length); - } - var numDirs = entrypointDirectory.split('/').length - 1; - while (numDirs-- > 0) { - module = '../$module'; - } - return module; - } - File get ddcModuleLoaderJS => fileSystem.file(sdkLayout.ddcModuleLoaderJsPath); File get requireJS => fileSystem.file(sdkLayout.requireJsPath); diff --git a/frontend_server_common/lib/src/resident_runner.dart b/frontend_server_common/lib/src/resident_runner.dart index 604b33035..6802a3689 100644 --- a/frontend_server_common/lib/src/resident_runner.dart +++ b/frontend_server_common/lib/src/resident_runner.dart @@ -66,15 +66,12 @@ class ResidentWebRunner { ProjectFileInvalidator? _projectFileInvalidator; WebDevFS? devFS; Uri? uri; - late Iterable modules; Future run( - FileSystem fileSystem, + FileSystem fileSystem, { String? hostname, - int port, - String index, { - required bool initialCompile, - required bool fullRestart, + required int port, + required String index, }) async { _projectFileInvalidator ??= ProjectFileInvalidator(fileSystem: fileSystem); devFS ??= WebDevFS( @@ -91,20 +88,43 @@ class ResidentWebRunner { uri ??= await devFS!.create(); final report = await _updateDevFS( - initialCompile: initialCompile, fullRestart: fullRestart); + initialCompile: true, fullRestart: false, fileServerUri: null); if (!report.success) { _logger.severe('Failed to compile application.'); return 1; } - modules = report.invalidatedModules!; + generator.accept(); + return 0; + } + + Future rerun( + {required bool fullRestart, + // The uri of the `HttpServer` that handles file requests. + // TODO(srujzs): This should be the same as the uri of the AssetServer to + // align with Flutter tools, but currently is not. Delete when that's fixed. + required Uri fileServerUri}) async { + final report = await _updateDevFS( + initialCompile: false, + fullRestart: fullRestart, + fileServerUri: fileServerUri); + if (!report.success) { + _logger.severe('Failed to compile application.'); + return 1; + } generator.accept(); return 0; } - Future _updateDevFS( - {required bool initialCompile, required bool fullRestart}) async { + Future _updateDevFS({ + required bool initialCompile, + required bool fullRestart, + // The uri of the `TestServer` that handles file requests. + // TODO(srujzs): This should be the same as the uri of the AssetServer to + // align with Flutter tools, but currently is not. Delete when that's fixed. + required Uri? fileServerUri, + }) async { final invalidationResult = await _projectFileInvalidator!.findInvalidated( lastCompiled: devFS!.lastCompiled, urisToMonitor: devFS!.sources, @@ -116,7 +136,8 @@ class ResidentWebRunner { generator: generator, invalidatedFiles: invalidationResult.uris!, initialCompile: initialCompile, - fullRestart: fullRestart); + fullRestart: fullRestart, + fileServerUri: fileServerUri); return report; }