[ddc] Split hot restart into two phases
Exposes two new methods `hotRestartBegin()` and `hotRestartEnd()` in the `DartDevEmbedder`. This provides a more customizable loading of sources across the variety of environments we are supporting. Change-Id: Id7a35695234f0625fe4de1d67c9d5a8055bad461 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500240 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
377c02e4d0
commit
de18b40933
@@ -1694,8 +1694,53 @@ if (!self.deferred_loader) {
|
||||
this.hotReloadGeneration += 1;
|
||||
}
|
||||
|
||||
// See docs on `DartDevEmbedder.hotRestartBegin`.
|
||||
async hotRestartBegin(filesToRequest) {
|
||||
if (!this.savedEntryPointLibraryName) {
|
||||
throw 'Error: Hot restart requested before application started.';
|
||||
}
|
||||
this.hotRestartInProgress = true;
|
||||
let reloadedFiles = await self.$dartReloadModifiedModules(
|
||||
filesToRequest, this.savedEntryPointLibraryName);
|
||||
return reloadedFiles;
|
||||
}
|
||||
|
||||
// See docs on `DartDevEmbedder.hotRestartEnd`.
|
||||
hotRestartEnd() {
|
||||
if (!this.hotRestartInProgress) {
|
||||
throw 'Error: Hot restart end called without a corresponding start.';
|
||||
}
|
||||
// Clear all libraries.
|
||||
this.libraries = Object.create(null);
|
||||
this.triggeredSDKLibrariesWithSideEffects = false;
|
||||
this.setDartSDKRuntimeOptions(this.savedDartSdkRuntimeOptions);
|
||||
// Update initializers. They'll be invoked later at some point after we
|
||||
// call main.
|
||||
for (let name in this.pendingHotRestartLibraryInitializers) {
|
||||
let initializer = this.pendingHotRestartLibraryInitializers[name];
|
||||
this.libraryInitializers[name] = initializer;
|
||||
}
|
||||
let entryPointLibrary =
|
||||
this.initializeAndLinkLibrary(this.savedEntryPointLibraryName);
|
||||
// TODO(nshahan): Start sharing a single source of truth for the restart
|
||||
// generation between the dart:_runtime and this module system.
|
||||
this.hotRestartGeneration += 1;
|
||||
console.log(
|
||||
'Hot restarting application from main method in: ' +
|
||||
this.savedEntryPointLibraryName +
|
||||
' (generation: ' + this.hotRestartGeneration + ').');
|
||||
// Cleanup.
|
||||
this.hotRestartInProgress = false;
|
||||
this.pendingHotRestartLibraryInitializers = Object.create(null);
|
||||
|
||||
this._runMain(entryPointLibrary);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Completes a hot restart operation.
|
||||
*
|
||||
* @deprecated Use `hotRestartBegin` and `hotRestartEnd` instead.
|
||||
*/
|
||||
hotRestart() {
|
||||
if (!this.savedEntryPointLibraryName) {
|
||||
@@ -2076,6 +2121,7 @@ if (!self.deferred_loader) {
|
||||
* that invokes 'main' when called.
|
||||
* @type {?function(function())}
|
||||
*/
|
||||
// TODO(nshahan): Remove this once the migration is complete.
|
||||
capturedMainHandler = null;
|
||||
|
||||
/**
|
||||
@@ -2194,9 +2240,63 @@ if (!self.deferred_loader) {
|
||||
await libraryManager.hotReloadStart(filesToLoad, librariesToReload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately triggers the start of a hot restart of the application.
|
||||
*
|
||||
* Assumes that a function named `$dartReloadModifiedModules` is available
|
||||
* in the global scope and relies on it to actually request the files
|
||||
* that are being reloaded and add them to the document.
|
||||
*
|
||||
* @param {!Array<!Object>} filesToRequest The descriptor Objects
|
||||
* describing the JavaScript files that could be part of this hot
|
||||
* restart. This must include all the files that changed and need
|
||||
* to be reloaded on the page.
|
||||
*
|
||||
* Each descriptor Object must have the following properties:
|
||||
* - src: The url of the Javascript file to request.
|
||||
* - id: The unique and stable identifier for this file, typically
|
||||
* the "module name" is used.
|
||||
*
|
||||
* Additional properties may be present and will simply be passed
|
||||
* through to the `$dartReloadModifiedModules` function.
|
||||
*
|
||||
* @return {!Promise<!Array<!Object>>} Descriptor Objects for the
|
||||
* JavaScript files that were actually requested by
|
||||
* `$dartReloadModifiedModules`.
|
||||
*
|
||||
* This is expected to be the same set of files described by
|
||||
* `filesToRequest` or a subset. Naming additional files could cause
|
||||
* undefined behavior.
|
||||
*
|
||||
* Note: when a Dart debugger is attached, the hot restart operation
|
||||
* will block until a script parse event is received from Chrome
|
||||
* DevTools for each of the JavaScript files described here.
|
||||
*
|
||||
* Each descriptor Object returned from `$dartReloadModifiedModules`
|
||||
* must contain the following properties:
|
||||
* - src: The url of the Javascript file that was requested.
|
||||
*
|
||||
* Additional properties may be present.
|
||||
*/
|
||||
async hotRestartBegin(filesToRequest) {
|
||||
return await libraryManager.hotRestartBegin(filesToRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the hot restart operation that must have been previously
|
||||
* started by [hotRestartBegin] losing all application state and running
|
||||
* the main method again.
|
||||
*/
|
||||
hotRestartEnd() {
|
||||
libraryManager.hotRestartEnd();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Immediately triggers a hot restart of the application losing all state
|
||||
* and running the main method again.
|
||||
*
|
||||
* @deprecated Use `hotRestartBegin` and `hotRestartEnd` instead.
|
||||
*/
|
||||
async hotRestart() {
|
||||
libraryManager.hotRestartInProgress = true;
|
||||
|
||||
@@ -136,16 +136,8 @@ self.\$dartLoader.loader = loader;
|
||||
let modifiedFilesPerGeneration = ${_encoder.convert(modifiedFilesPerGeneration)};
|
||||
let previousGenerations = new Set();
|
||||
|
||||
// Append a helper function for hot restart.
|
||||
self.\$dartReloadModifiedModules = async function(subAppName, callback) {
|
||||
let expectedName = "$entrypointModuleName";
|
||||
if (subAppName !== expectedName) {
|
||||
throw Error("Unexpected app name " + subAppName
|
||||
+ " (expected: " + expectedName + "). "
|
||||
+ "Hot Reload Runner does not support multiple subapps, so only "
|
||||
+ "one app name should be provided across reloads/restarts.");
|
||||
}
|
||||
|
||||
// Append a helper function to provide reloaded sources information.
|
||||
self.\$injectedReloadedSourcesHelper = function() {
|
||||
// Resolve the next generation's directory and load all modified files.
|
||||
let nextGeneration = self.\$dartLoader.loader.intendedHotRestartGeneration;
|
||||
if (previousGenerations.has(nextGeneration)) {
|
||||
@@ -156,18 +148,37 @@ self.\$dartReloadModifiedModules = async function(subAppName, callback) {
|
||||
let modifiedFilePaths = modifiedFilesPerGeneration[nextGeneration];
|
||||
// Stop if the next generation does not exist.
|
||||
if (modifiedFilePaths == void 0) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load all modified files.
|
||||
// Collect reload generation resources.
|
||||
let fileDescriptors = [];
|
||||
for (let i = 0; i < modifiedFilePaths.length; i++) {
|
||||
let modifiedFilePath = modifiedFilePaths[i][1];
|
||||
self.\$dartLoader.forceLoadScript(modifiedFilePath);
|
||||
fileDescriptors.push({src: modifiedFilePath});
|
||||
}
|
||||
return fileDescriptors;
|
||||
}
|
||||
|
||||
// Run main in an async callback. D8 performs synchronous loads, so we need
|
||||
// to insert an async task to match its semantics to that of Chrome.
|
||||
await Promise.resolve().then(() => { callback(); });
|
||||
// Append a helper function for hot restart.
|
||||
self.\$dartReloadModifiedModules = function(filesToReload, appName) {
|
||||
let expectedName = "$entrypointModuleName";
|
||||
if (appName !== expectedName) {
|
||||
throw Error("Unexpected app name " + appName
|
||||
+ " (expected: " + expectedName + "). "
|
||||
+ "Hot Reload Runner does not support multiple subapps, so only "
|
||||
+ "one app name should be provided across reloads/restarts.");
|
||||
}
|
||||
return new Promise(function(resolve) {
|
||||
// Load all modified files.
|
||||
for (let i = 0; i < filesToReload.length; i++) {
|
||||
let modifiedFilePath = filesToReload[i].src;
|
||||
self.\$dartLoader.forceLoadScript(modifiedFilePath);
|
||||
}
|
||||
// D8 performs synchronous loads, but we return a Promise to match the
|
||||
// semantics in Chrome.
|
||||
resolve(filesToReload);
|
||||
});
|
||||
}
|
||||
|
||||
// Append a helper function for hot reload.
|
||||
@@ -505,15 +516,8 @@ let _scriptUrls = {
|
||||
}
|
||||
|
||||
let previousGenerations = new Set();
|
||||
self.\$dartReloadModifiedModules = async function(subAppName, callback) {
|
||||
let expectedName = "$entrypointModuleName";
|
||||
if (subAppName !== expectedName) {
|
||||
throw Error("Unexpected app name " + subAppName
|
||||
+ " (expected: " + expectedName + "). "
|
||||
+ "Hot Reload Runner does not support multiple subapps, so only "
|
||||
+ "one app name should be provided across reloads/restarts.");
|
||||
}
|
||||
|
||||
// Append a helper function to provide reloaded sources information.
|
||||
self.\$injectedReloadedSourcesHelper = function() {
|
||||
// Resolve the next generation's directory and load all modified files.
|
||||
let nextGeneration = self.\$dartLoader.loader.intendedHotRestartGeneration;
|
||||
if (previousGenerations.has(nextGeneration)) {
|
||||
@@ -524,33 +528,57 @@ let _scriptUrls = {
|
||||
let modifiedFilePaths = modifiedFilesPerGeneration[nextGeneration];
|
||||
// Stop if the next generation does not exist.
|
||||
if (modifiedFilePaths == void 0) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load all modified files.
|
||||
var numToLoad = 0;
|
||||
var numLoaded = 0;
|
||||
// Collect reload generation resources.
|
||||
let fileDescriptors = [];
|
||||
for (let i = 0; i < modifiedFilePaths.length; i++) {
|
||||
numToLoad++
|
||||
let modifiedFileId = modifiedFilePaths[i][0];
|
||||
let modifiedFilePath = modifiedFilePaths[i][1];
|
||||
|
||||
// Invalidate DDC state for hot restart.
|
||||
self.\$dartLoader.moduleIdToUrl.set(modifiedFileId, modifiedFilePath);
|
||||
self.\$dartLoader.urlToModuleId.set(modifiedFilePath, modifiedFileId);
|
||||
|
||||
// Remove the old script.
|
||||
var el = document.getElementById(modifiedFileId);
|
||||
if (el) el.remove();
|
||||
|
||||
loadHotRestartScript(modifiedFileId, modifiedFilePath, function() {
|
||||
numLoaded++;
|
||||
if (numToLoad == numLoaded) callback();
|
||||
});
|
||||
fileDescriptors.push({src: modifiedFilePath, id: modifiedFileId});
|
||||
}
|
||||
return fileDescriptors;
|
||||
}
|
||||
|
||||
// Call the callback immediately if we found no updated scripts.
|
||||
if (numToLoad == 0) callback();
|
||||
// Append a helper function for hot restart.
|
||||
self.\$dartReloadModifiedModules = async function(filesToReload, subAppName) {
|
||||
let expectedName = "$entrypointModuleName";
|
||||
if (subAppName !== expectedName) {
|
||||
throw Error("Unexpected app name " + subAppName
|
||||
+ " (expected: " + expectedName + "). "
|
||||
+ "Hot Reload Runner does not support multiple subapps, so only "
|
||||
+ "one app name should be provided across reloads/restarts.");
|
||||
}
|
||||
// Load all modified files.
|
||||
return new Promise(function(resolve) {
|
||||
function callback() {
|
||||
resolve(filesToReload);
|
||||
}
|
||||
var numToLoad = 0;
|
||||
var numLoaded = 0;
|
||||
for (let i = 0; i < filesToReload.length; i++) {
|
||||
numToLoad++
|
||||
let modifiedFileId = filesToReload[i].id;
|
||||
let modifiedFilePath = filesToReload[i].src;
|
||||
|
||||
// Invalidate DDC state for hot restart.
|
||||
self.\$dartLoader.moduleIdToUrl.set(modifiedFileId, modifiedFilePath);
|
||||
self.\$dartLoader.urlToModuleId.set(modifiedFilePath, modifiedFileId);
|
||||
|
||||
// Remove the old script.
|
||||
var el = document.getElementById(modifiedFileId);
|
||||
if (el) el.remove();
|
||||
|
||||
loadHotRestartScript(modifiedFileId, modifiedFilePath, function() {
|
||||
numLoaded++;
|
||||
if (numToLoad == numLoaded) callback();
|
||||
});
|
||||
}
|
||||
|
||||
// Call the callback immediately if we found no updated scripts.
|
||||
if (numToLoad == 0) callback();
|
||||
});
|
||||
}
|
||||
|
||||
// Begin loading libraries
|
||||
|
||||
@@ -26,7 +26,10 @@ extension type _DDCLoader(JSObject _) implements JSObject {
|
||||
|
||||
extension type _DartDevEmbedder(JSObject _) implements JSObject {
|
||||
external JSPromise hotReload(JSArray<JSString> files, JSArray<JSString> ids);
|
||||
external JSPromise hotRestart();
|
||||
external JSPromise<JSArray<JSObject>> hotRestartBegin(
|
||||
JSArray<JSObject> filesToRequest,
|
||||
);
|
||||
external void hotRestartEnd();
|
||||
external JSNumber get hotReloadGeneration;
|
||||
external JSNumber get hotRestartGeneration;
|
||||
}
|
||||
@@ -39,6 +42,9 @@ external JSArray<JSArray<JSString>>? injectedFilesAndLibrariesToReload(
|
||||
JSNumber requestedFileGeneration,
|
||||
);
|
||||
|
||||
@JS('\$injectedReloadedSourcesHelper')
|
||||
external JSArray<JSObject>? injectedReloadedSourcesHelper();
|
||||
|
||||
@JS('\$dartLoader')
|
||||
external _DartLoader get _dartLoader;
|
||||
|
||||
@@ -48,6 +54,12 @@ int get hotRestartGeneration => _dartDevEmbedder.hotRestartGeneration.toDartInt;
|
||||
|
||||
Future<void> hotRestart() async {
|
||||
_ddcLoader.intendedHotRestartGeneration++;
|
||||
final filesToRequest = injectedReloadedSourcesHelper();
|
||||
if (filesToRequest == null) {
|
||||
throw Exception('Restart requested but no remaining generations found.');
|
||||
}
|
||||
await _dartDevEmbedder.hotRestartBegin(filesToRequest).toDart;
|
||||
// Must return the receipt before re-running the main method.
|
||||
final restartReceipt = HotReloadReceipt(
|
||||
generation: _ddcLoader.intendedHotRestartGeneration,
|
||||
status: Status.restarted,
|
||||
@@ -56,7 +68,7 @@ Future<void> hotRestart() async {
|
||||
'${HotReloadReceipt.hotReloadReceiptTag}'
|
||||
'${jsonEncode(restartReceipt.toJson())}',
|
||||
);
|
||||
await _dartDevEmbedder.hotRestart().toDart;
|
||||
_dartDevEmbedder.hotRestartEnd();
|
||||
}
|
||||
|
||||
/// The reload generation of the currently running application.
|
||||
|
||||
Reference in New Issue
Block a user