Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion dwds/lib/dwds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export 'src/loaders/frontend_server_strategy_provider.dart'
FrontendServerRequireStrategyProvider;
export 'src/loaders/require.dart' show RequireStrategy;
export 'src/loaders/strategy.dart'
show BuildSettings, LoadStrategy, ReloadConfiguration;
show
BuildSettings,
LoadStrategy,
ReloadConfiguration,
ReloadableLoadStrategy;
export 'src/readers/asset_reader.dart' show AssetReader, PackageUriMapper;
export 'src/readers/frontend_server_asset_reader.dart'
show FrontendServerAssetReader;
Expand Down
3 changes: 1 addition & 2 deletions dwds/lib/src/debugging/chrome_inspector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import 'package:dwds/src/debugging/libraries.dart';
import 'package:dwds/src/debugging/location.dart';
import 'package:dwds/src/debugging/metadata/provider.dart';
import 'package:dwds/src/debugging/remote_debugger.dart';
import 'package:dwds/src/loaders/ddc_library_bundle.dart';
import 'package:dwds/src/readers/asset_reader.dart';
import 'package:dwds/src/utilities/conversions.dart';
import 'package:dwds/src/utilities/dart_uri.dart';
Expand Down Expand Up @@ -265,7 +264,7 @@ class ChromeAppInspector extends AppInspector {
if (libraryUri == null) {
throwInvalidParam('invoke', 'library uri is null');
}
return globalToolConfiguration.loadStrategy is DdcLibraryBundleStrategy
return globalToolConfiguration.loadStrategy.id == 'ddc-library-bundle'
? _evaluateLibraryMethodWithDdcLibraryBundle(
libraryUri,
selector,
Expand Down
63 changes: 46 additions & 17 deletions dwds/lib/src/dwds_vm_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'dart:convert';

import 'package:dwds/src/config/tool_configuration.dart';
import 'package:dwds/src/events.dart';
import 'package:dwds/src/loaders/ddc_library_bundle.dart';
import 'package:dwds/src/services/chrome/chrome_debug_exception.dart';
import 'package:dwds/src/services/chrome/chrome_debug_service.dart';
import 'package:dwds/src/services/chrome/chrome_proxy_service.dart';
Expand Down Expand Up @@ -441,11 +440,17 @@ final class ChromeDwdsVmClient
(event) => event.kind == EventKind.kIsolateStart,
);
try {
final isDdcLibraryBundle =
globalToolConfiguration.loadStrategy.id == 'ddc-library-bundle';
// If we should pause isolates on start, then only run main once we get a
// resume event.
final pauseIsolatesOnStart = chromeProxyService.pauseIsolatesOnStart;
if (pauseIsolatesOnStart) {
_waitForResumeEventToRunMain(chromeProxyService);
if (isDdcLibraryBundle) {
_waitForResumeEventToEndHotRestart(chromeProxyService);
} else {
_waitForResumeEventToRunMain(chromeProxyService);
}
}
// Generate run id to hot restart all apps loaded into the tab.
final runId = const Uuid().v4();
Expand All @@ -458,18 +463,18 @@ final class ChromeDwdsVmClient
// TODO(srujzs): We don't do this for the AMD module format, should we? It
// would require adding an extra parameter in the AMD strategy. As we're
// planning to deprecate it, for now, do nothing.
final isDdcLibraryBundle =
globalToolConfiguration.loadStrategy is DdcLibraryBundleStrategy;
final computedReloadedSrcs = Completer<void>();
final reloadedSrcs = <String>{};
late StreamSubscription<String> parsedScriptsSubscription;

if (isDdcLibraryBundle) {
final computedReloadedSrcs = Completer<void>();
final reloadedSrcs = <String>{};
// Injected client should send a request to recreate the isolate after
// the hot restart. The creation of the isolate should in turn wait
// until all scripts are parsed.
chromeProxyService.allowedToCreateIsolate = Completer<void>();
final debugger = await chromeProxyService.debuggerFuture;
parsedScriptsSubscription = debugger.parsedScriptsController.stream
final parsedScriptsSubscription = debugger
.parsedScriptsController
.stream
.listen((url) {
computedReloadedSrcs.future.then((_) async {
reloadedSrcs.remove(Uri.parse(url).normalizePath().path);
Expand All @@ -479,14 +484,13 @@ final class ChromeDwdsVmClient
}
});
});
}
logger.info('Issuing \$dartHotRestartDwds request');
final remoteObject = await chromeProxyService.inspector.jsEvaluate(
'\$dartHotRestartDwds(\'$runId\', $pauseIsolatesOnStart);',
awaitPromise: true,
returnByValue: true,
);
if (isDdcLibraryBundle) {
logger.info('Issuing \$dartHotRestartBeginDwds request');
final remoteObject = await chromeProxyService.inspector.jsEvaluate(
'\$dartHotRestartBeginDwds(\'$runId\', $pauseIsolatesOnStart);',
awaitPromise: true,
returnByValue: true,
);
logger.info('\$dartHotRestartBeginDwds request complete.');
final reloadedSrcModuleLibraries = (remoteObject.value as List)
.cast<Map>();
for (final srcModuleLibrary in reloadedSrcModuleLibraries) {
Expand All @@ -504,9 +508,15 @@ final class ChromeDwdsVmClient
await chromeProxyService.allowedToCreateIsolate.future;
await parsedScriptsSubscription.cancel();
} else {
logger.info('Issuing \$dartHotRestartDwds request.');
final remoteObject = await chromeProxyService.inspector.jsEvaluate(
'\$dartHotRestartDwds(\'$runId\', $pauseIsolatesOnStart);',
awaitPromise: true,
returnByValue: true,
);
assert(remoteObject.value == null);
logger.info('\$dartHotRestartDwds request complete.');
}
logger.info('\$dartHotRestartDwds request complete.');
} on WipError catch (exception) {
final code = exception.error?['code'];
final message = exception.error?['message'];
Expand Down Expand Up @@ -545,6 +555,25 @@ final class ChromeDwdsVmClient
});
}

/// Waits for the isolate to start after a hot restart and then issues the
/// request to finish the hot restart operation.
void _waitForResumeEventToEndHotRestart(
ChromeProxyService chromeProxyService,
) {
StreamSubscription<String>? resumeEventsSubscription;
resumeEventsSubscription = chromeProxyService.resumeAfterRestartEventsStream
.listen((_) async {
await resumeEventsSubscription!.cancel();
logger.info('Issuing \$dartHotRestartEndDwds request.');
await chromeProxyService.inspector.jsEvaluate(
'\$dartHotRestartEndDwds();',
awaitPromise: true,
returnByValue: true,
);
logger.info('\$dartHotRestartEndDwds request complete.');
});
}

Future<Map<String, dynamic>> _fullReload(
ChromeProxyService chromeProxyService,
) async {
Expand Down
4 changes: 2 additions & 2 deletions dwds/lib/src/handlers/injector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:dwds/src/config/tool_configuration.dart';
import 'package:dwds/src/handlers/injected_client_js.dart';
import 'package:dwds/src/loaders/ddc_library_bundle.dart';
import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/version.dart';
import 'package:logging/logging.dart';
import 'package:shelf/shelf.dart';
Expand Down Expand Up @@ -189,7 +189,7 @@ Future<String> _injectedClientSnippet(
final buildSettings = loadStrategy.buildSettings;
final appMetadata = globalToolConfiguration.appMetadata;
final debugSettings = globalToolConfiguration.debugSettings;
final reloadedSourcesPath = loadStrategy is DdcLibraryBundleStrategy
final reloadedSourcesPath = loadStrategy is ReloadableLoadStrategy
? 'window.\$reloadedSourcesPath = "${loadStrategy.reloadedSourcesUri}";\n'
: '';

Expand Down
27 changes: 3 additions & 24 deletions dwds/lib/src/loaders/ddc_library_bundle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import 'package:shelf/shelf.dart';

// TODO(srujzs): This is mostly a copy of `DdcStrategy`. Some of the
// functionality in here may not make sense with the library bundle format yet.
class DdcLibraryBundleStrategy extends LoadStrategy {
class DdcLibraryBundleStrategy extends LoadStrategy
implements ReloadableLoadStrategy {
@override
final ReloadConfiguration reloadConfiguration;

Expand Down Expand Up @@ -104,29 +105,7 @@ class DdcLibraryBundleStrategy extends LoadStrategy {

final BuildSettings _buildSettings;

/// The [Uri] of the file that contains a JSONified list of maps which follows
/// the following format:
///
/// ```json
/// [
/// {
/// "src": "<base_uri>/<file_name>",
/// "module": "<module_name>",
/// "libraries": ["<lib1>", "<lib2>"],
/// },
/// ]
/// ```
///
/// `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`.
///
/// This is needed for hot reloads and restarts in order to tell the module
/// loader what files need to be loaded and what libraries need to be
/// reloaded. The contents of the file this [Uri] points to should be updated
/// whenever a hot reload or hot restart is executed.
@override
final Uri? reloadedSourcesUri;

/// When enabled, injects the script loader into the bootstrapper from
Expand Down
29 changes: 29 additions & 0 deletions dwds/lib/src/loaders/strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,35 @@ import 'package:dwds/src/utilities/dart_uri.dart';
import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart';

/// A load strategy that supports reading from a URI to find the sources needed
/// to complete the hot reload or hot restart operation.
abstract class ReloadableLoadStrategy implements LoadStrategy {
/// The [Uri] of the file that contains a JSONified list of maps which follows
/// the following format:
///
/// ```json
/// [
/// {
/// "src": "<base_uri>/<file_name>",
/// "module": "<module_name>",
/// "libraries": ["<lib1>", "<lib2>"],
/// },
/// ]
/// ```
///
/// `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`.
///
/// This is needed for hot reloads and restarts in order to tell the module
/// loader what files need to be loaded and what libraries need to be
/// reloaded. The contents of the file this [Uri] points to should be updated
/// whenever a hot reload or hot restart is executed.
Uri? get reloadedSourcesUri;
}

abstract class LoadStrategy {
final AssetReader _assetReader;
final String? _packageConfigPath;
Expand Down
29 changes: 25 additions & 4 deletions dwds/web/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ Future<void>? main() {
return manager.hotReloadEnd().toJS;
}.toJS;

hotRestartBeginJs = () {
return manager.hotRestartBegin(hotReloadReloadedSourcesPath).toJS;
}.toJS;

hotRestartEndJs = manager.hotRestartEnd.toJS;

Completer? readyToRunMainCompleter;

hotRestartJs = (String runId, [bool? pauseIsolatesOnStart]) {
Expand Down Expand Up @@ -523,10 +529,17 @@ Future<void> handleWebSocketHotRestartRequest(
final requestId = event.id;
try {
final runId = const Uuid().v4();
await manager.hotRestart(
runId: runId,
reloadedSourcesPath: hotRestartReloadedSourcesPath,
);
if (manager.supportsTwoPhaseHotRestart) {
await manager.hotRestartBegin(hotRestartReloadedSourcesPath!);
manager.hotRestartEnd();
} else {
// TODO(nshahan): Remove after migrating to hotRestartBegin/hotRestartEnd.
// https://github.com/dart-lang/webdev/issues/2826
await manager.hotRestart(
runId: runId,
reloadedSourcesPath: hotRestartReloadedSourcesPath,
);
}
_sendHotRestartResponse(clientSink, requestId, success: true);
} catch (e) {
_sendHotRestartResponse(
Expand Down Expand Up @@ -597,6 +610,12 @@ external set hotReloadStartJs(JSFunction cb);
@JS(r'$dartHotReloadEndDwds')
external set hotReloadEndJs(JSFunction cb);

@JS(r'$dartHotRestartBeginDwds')
external set hotRestartBeginJs(JSFunction cb);

@JS(r'$dartHotRestartEndDwds')
external set hotRestartEndJs(JSFunction cb);

@JS(r'$reloadedSourcesPath')
external String? get _reloadedSourcesPath;

Expand All @@ -612,6 +631,8 @@ String get hotReloadReloadedSourcesPath {
}

/// Debugger-initiated hot restart.
// TODO(nshahan): Remove after migrating to hotRestartBegin/hotRestartEnd.
// https://github.com/dart-lang/webdev/issues/2826
@JS(r'$dartHotRestartDwds')
external set hotRestartJs(JSFunction cb);

Expand Down
27 changes: 25 additions & 2 deletions dwds/web/reloader/ddc_library_bundle_restarter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ external _DartDevEmbedder get _dartDevEmbedder;

extension type _DartDevEmbedder._(JSObject _) implements JSObject {
external _Debugger get debugger;
external JSPromise<JSAny?> hotRestart();
external JSPromise<JSArray<JSObject>?> hotRestart([
JSArray<JSObject>? reloadedSources,
]);
external JSPromise<JSArray<JSObject>> hotRestartBegin([
JSArray<JSObject>? reloadedSources,
]);
external JSAny? hotRestartEnd();

external JSPromise<JSAny?> hotReload(
JSArray<JSString> filesToLoad,
JSArray<JSString> librariesToReload,
Expand Down Expand Up @@ -64,7 +71,7 @@ extension on JSArray<JSString> {
external void push(JSString value);
}

class DdcLibraryBundleRestarter implements Restarter {
class DdcLibraryBundleRestarter implements Restarter, TwoPhaseRestarter {
JSFunction? _capturedHotReloadEndCallback;

Future<void> _runMainWhenReady(
Expand Down Expand Up @@ -125,6 +132,22 @@ class DdcLibraryBundleRestarter implements Restarter {
return (true, srcModuleLibraries.jsify() as JSArray<JSObject>);
}

@override
Future<JSArray<JSObject>> hotRestartBegin(String reloadedSourcesPath) async {
await _dartDevEmbedder.debugger.maybeInvokeFlutterDisassemble();
final srcModuleLibraries = await _getSrcModuleLibraries(
reloadedSourcesPath,
);
final jsFilesToRequest = srcModuleLibraries.jsify() as JSArray<JSObject>;
final JSArray<JSObject> requestedJsFiles = await _dartDevEmbedder
.hotRestartBegin(jsFilesToRequest)
.toDart;
return requestedJsFiles;
}

@override
void hotRestartEnd() => _dartDevEmbedder.hotRestartEnd();

@override
Future<JSArray<JSObject>> hotReloadStart(String reloadedSourcesPath) async {
final filesToLoad = JSArray<JSString>();
Expand Down
16 changes: 16 additions & 0 deletions dwds/web/reloader/manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ class ReloadingManager {
return result.$2;
}

bool get supportsTwoPhaseHotRestart => _restarter is TwoPhaseRestarter;

Future<JSArray<JSObject>> hotRestartBegin(String reloadedSourcesPath) async {
final requestedSources = await (_restarter as TwoPhaseRestarter)
.hotRestartBegin(reloadedSourcesPath);
// Notify package:dwds that the isolate is exiting and a new isolate will
// be created.
_beforeRestart();
_afterRestart(true);
return requestedSources;
}

void hotRestartEnd() {
(_restarter as TwoPhaseRestarter).hotRestartEnd();
}

/// After a previous call to [hotReloadStart], completes the hot
/// reload by pushing the libraries into the Dart runtime.
Future<void> hotReloadEnd() async {
Expand Down
15 changes: 15 additions & 0 deletions dwds/web/reloader/restarter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

import 'dart:js_interop';

/// A Restarter that supports a hot restart over two phases.
abstract class TwoPhaseRestarter implements Restarter {
/// Starts a hot restart operation.
///
/// Passes the [reloadedSourcesPath] through to the `DartDevEmbedder` and
/// bubbles up the returned array of scripts that were actually requested.
Future<JSArray<JSObject>> hotRestartBegin(String reloadedSourcesPath);

/// Finishes the hot restart operation that must have been previously started by
/// [hotRestartBegin].
void hotRestartEnd();
}

abstract class Restarter {
/// Attempts to perform a hot restart.
///
Expand All @@ -30,6 +43,8 @@ abstract class Restarter {
/// Returns a record containing whether the hot restart succeeded and either
/// the JS version of the list of maps from [reloadedSourcesPath] if
/// [reloadedSourcesPath] is non-null and null otherwise.
// TODO(nshahan): Remove after migrating to hotRestartBegin/hotRestartEnd.
// https://github.com/dart-lang/webdev/issues/2826
Future<(bool, JSArray<JSObject>?)> restart({
String? runId,
Future? readyToRunMain,
Expand Down
Loading