Skip to content

Commit 76b8be4

Browse files
authored
Basic graph handling for hot reload (#1759)
We still lack handling situations when graph is dynamically updated between reloads.
1 parent 8b9e18d commit 76b8be4

File tree

9 files changed

+2694
-2217
lines changed

9 files changed

+2694
-2217
lines changed

build_runner/lib/src/server/hot_reload_client/client.dart

Lines changed: 117 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,88 @@ import 'dart:async';
99
import 'dart:convert';
1010
import 'dart:html';
1111

12-
import 'package:build_runner/src/server/hot_reload_client/reload_handler.dart';
13-
1412
import 'package:js/js.dart';
1513
import 'package:js/js_util.dart';
1614

15+
import 'reload_handler.dart';
16+
import 'reloading_manager.dart';
17+
1718
final _assetsDigestPath = r'$assetDigests';
1819
final _buildUpdatesProtocol = r'$livereload';
1920

21+
@anonymous
22+
@JS()
23+
abstract class HotReloadableModule {
24+
/// Implement this function with any code to release resources before destroy.
25+
///
26+
/// Any object returned from this function will be passed to update hooks. Use
27+
/// it to save any state you need to be preserved between hot reloadings.
28+
/// Try do not use any custom types here, as it might prevent their code from
29+
/// reloading. Better serialise to JSON or plain types.
30+
///
31+
/// This function will be called on old version of module before unloading.
32+
@JS()
33+
external Object hot$onDestroy();
34+
35+
/// Implement this function to handle update of the module itself.
36+
///
37+
/// May return nullable bool. To indicate that reload completes successfully
38+
/// return true. To indicate that hot-reload is undoable return false - this
39+
/// will lead to full page reload. If null returned, reloading will be
40+
/// propagated to parent.
41+
///
42+
/// If any state was saved from previous version, it will be passed to [data].
43+
///
44+
/// This function will be called on new version of module after reloading.
45+
@JS()
46+
external bool hot$onSelfUpdate([Object data]);
47+
48+
/// Implement this function to handle update of child modules.
49+
///
50+
/// May return nullable bool. To indicate that reload of child completes
51+
/// successfully return true. To indicate that hot-reload is undoable for this
52+
/// child return false - this will lead to full page reload. If null returned,
53+
/// reloading will be propagated to current module itself.
54+
///
55+
/// The name of the child will be provided in [childId]. New version of child
56+
/// module object will be provided in [child].
57+
/// If any state was saved from previous version, it will be passed to [data].
58+
///
59+
/// This function will be called on old version of module current after child
60+
/// reloading.
61+
@JS()
62+
external bool hot$onChildUpdate(String childId, HotReloadableModule child,
63+
[Object data]);
64+
}
65+
66+
class ModuleWrapper implements Module {
67+
final HotReloadableModule _internal;
68+
69+
ModuleWrapper(this._internal);
70+
71+
@override
72+
bool get hasOnDestroy =>
73+
_internal != null && hasProperty(_internal, r'hot$onDestroy');
74+
75+
@override
76+
bool get hasOnSelfUpdate =>
77+
_internal != null && hasProperty(_internal, r'hot$onSelfUpdate');
78+
79+
@override
80+
bool get hasOnChildUpdate =>
81+
_internal != null && hasProperty(_internal, r'hot$onChildUpdate');
82+
83+
@override
84+
Object onDestroy() => _internal.hot$onDestroy();
85+
86+
@override
87+
bool onSelfUpdate([Object data]) => _internal.hot$onSelfUpdate(data);
88+
89+
@override
90+
bool onChildUpdate(String childId, Module child, [Object data]) => _internal
91+
.hot$onChildUpdate(childId, (child as ModuleWrapper)._internal, data);
92+
}
93+
2094
@JS('Map')
2195
abstract class JsMap<K, V> {
2296
@JS()
@@ -32,42 +106,70 @@ class DartLoader {
32106
@JS()
33107
external JsMap<String, String> get urlToModuleId;
34108

109+
@JS()
110+
external JsMap<String, List<String>> get moduleParentsGraph;
111+
35112
@JS()
36113
external void forceLoadModule(
37-
String moduleId, void Function(Object module) callback);
114+
String moduleId, void Function(HotReloadableModule module) callback);
115+
116+
@JS()
117+
external void loadModule(
118+
String moduleId, void Function(HotReloadableModule module) callback);
38119
}
39120

40121
@JS(r'$dartLoader')
41122
external DartLoader get dartLoader;
42123

43124
@JS('Array.from')
44-
external List jsArrayFrom(Object any);
125+
external List _jsArrayFrom(Object any);
45126

46-
List<String> get _moduleUrls {
47-
return List.from(jsArrayFrom(dartLoader.urlToModuleId.keys()));
127+
List<K> keys<K, V>(JsMap<K, V> map) {
128+
return List.from(_jsArrayFrom(map.keys()));
48129
}
49130

50-
String _moduleIdByPath(String path) =>
51-
dartLoader.urlToModuleId.get(window.location.origin + '/' + path);
131+
Future<Module> _reloadModule(String moduleId) {
132+
var completer = Completer<Module>();
133+
dartLoader.forceLoadModule(
134+
moduleId,
135+
allowInterop((HotReloadableModule module) =>
136+
completer.complete(ModuleWrapper(module))));
137+
return completer.future;
138+
}
52139

53-
Future<Object> _reloadModule(String moduleId) {
54-
var completer = Completer<Object>();
55-
dartLoader.forceLoadModule(moduleId, allowInterop(completer.complete));
140+
Future<Module> _loadModule(String moduleId) {
141+
var completer = Completer<Module>();
142+
dartLoader.loadModule(
143+
moduleId,
144+
allowInterop((HotReloadableModule module) =>
145+
completer.complete(ModuleWrapper(module))));
56146
return completer.future;
57147
}
58148

149+
void _reloadPage() {
150+
window.location.reload();
151+
}
152+
59153
main() async {
60-
var modulePaths = _moduleUrls
61-
.map((key) => key.replaceFirst(window.location.origin + '/', ''))
154+
var currentOrigin = window.location.origin + '/';
155+
var modulePaths = keys(dartLoader.urlToModuleId)
156+
.map((key) => key.replaceFirst(currentOrigin, ''))
62157
.toList();
63158
var modulePathsJson = json.encode(modulePaths);
64159

65160
var request = await HttpRequest.request('/$_assetsDigestPath',
66161
responseType: 'json', sendData: modulePathsJson, method: 'POST');
67162
var digests = (request.response as Map).cast<String, String>();
68163

69-
var handler = ReloadHandler(digests, _moduleIdByPath, _reloadModule,
70-
(module) => callMethod(getProperty(module, 'main'), 'main', []));
164+
var manager = ReloadingManager(
165+
_reloadModule,
166+
_loadModule,
167+
_reloadPage,
168+
(module) => dartLoader.moduleParentsGraph.get(module),
169+
() => keys(dartLoader.moduleParentsGraph));
170+
171+
var handler = ReloadHandler(digests,
172+
(path) => dartLoader.urlToModuleId.get(currentOrigin + path), manager);
71173

72174
var webSocket =
73175
WebSocket('ws://' + window.location.host, [_buildUpdatesProtocol]);

0 commit comments

Comments
 (0)