mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_tools] Enable hot reload on the web (#169174)
[flutter_tools] Enable hot reload on the web Update the defaults so hot reload is enabled on web development builds by default. This enables the use of a new module representation in the compiled JavaScript. Passing `--no-web-experimental-hot-reload` will disable the ability to hot reload and return to the AMD JavaScript module representation. This change avoids enabling hot reload in the flutter drive tests since they rely on `-d web-server` which has known startup issues. When https://github.com/dart-lang/sdk/issues/60289 is resolved it should be safe to enable hot reload by default for the `flutter drive` tests. Fixes: https://github.com/flutter/flutter/issues/167510
This commit is contained in:
parent
d4f60bddd2
commit
7e32a77210
@ -360,6 +360,8 @@ class WebTestsSuite {
|
||||
'--browser-name=chrome',
|
||||
'-d',
|
||||
'web-server',
|
||||
// TODO(nshahan): Remove when web-server can run with hot reload, https://github.com/dart-lang/sdk/issues/60289.
|
||||
if (buildMode == 'debug') '--no-web-experimental-hot-reload',
|
||||
'--$buildMode',
|
||||
if (webRenderer == 'skwasm') ...<String>[
|
||||
// See: WebRendererMode.dartDefines[skwasm]
|
||||
@ -487,6 +489,7 @@ class WebTestsSuite {
|
||||
'--browser-name=chrome',
|
||||
'-d',
|
||||
'web-server',
|
||||
if (buildMode == 'debug') '--no-web-experimental-hot-reload',
|
||||
'--$buildMode',
|
||||
],
|
||||
workingDirectory: testAppDirectory,
|
||||
|
@ -17,6 +17,7 @@ import 'base/os.dart';
|
||||
import 'base/utils.dart';
|
||||
import 'convert.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'runner/flutter_command.dart' show FlutterOptions;
|
||||
|
||||
/// Whether icon font subsetting is enabled by default.
|
||||
const bool kIconTreeShakerEnabledDefault = true;
|
||||
@ -50,6 +51,7 @@ class BuildInfo {
|
||||
this.assumeInitializeFromDillUpToDate = false,
|
||||
this.buildNativeAssets = true,
|
||||
this.useLocalCanvasKit = false,
|
||||
this.webEnableHotReload = false,
|
||||
}) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[],
|
||||
extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[],
|
||||
fileSystemRoots = fileSystemRoots ?? const <String>[],
|
||||
@ -177,6 +179,9 @@ class BuildInfo {
|
||||
/// If set, web builds will use the locally built CanvasKit instead of using the CDN
|
||||
final bool useLocalCanvasKit;
|
||||
|
||||
/// If set, web builds with DDC will run with support for hot reload.
|
||||
final bool webEnableHotReload;
|
||||
|
||||
/// Can be used when the actual information is not needed.
|
||||
static const BuildInfo dummy = BuildInfo(
|
||||
BuildMode.debug,
|
||||
@ -259,13 +264,36 @@ class BuildInfo {
|
||||
/// The module system DDC is targeting, or null if not using DDC or the
|
||||
/// associated flag isn't present.
|
||||
// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
|
||||
DdcModuleFormat? get ddcModuleFormat =>
|
||||
_ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs(extraFrontEndOptions).ddcModuleFormat;
|
||||
DdcModuleFormat get ddcModuleFormat {
|
||||
final DdcModuleFormat moduleFormat =
|
||||
webEnableHotReload ? DdcModuleFormat.ddc : DdcModuleFormat.amd;
|
||||
final DdcModuleFormat? parsedFormat =
|
||||
_ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs(extraFrontEndOptions).ddcModuleFormat;
|
||||
if (parsedFormat != null && moduleFormat != parsedFormat) {
|
||||
throw Exception(
|
||||
'Unsupported option combination:\n'
|
||||
'${FlutterOptions.kWebExperimentalHotReload}: $webEnableHotReload\n'
|
||||
'${FlutterOptions.kExtraFrontEndOptions}: --dartdevc-module-format=${parsedFormat.name}',
|
||||
);
|
||||
}
|
||||
return moduleFormat;
|
||||
}
|
||||
|
||||
/// Whether to enable canary features when using DDC, or null if not using
|
||||
/// DDC or the associated flag isn't present.
|
||||
bool? get canaryFeatures =>
|
||||
_ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs(extraFrontEndOptions).canaryFeatures;
|
||||
bool get canaryFeatures {
|
||||
final bool canaryEnabled = webEnableHotReload;
|
||||
final bool? parsedCanary =
|
||||
_ddcModuleFormatAndCanaryFeaturesFromFrontEndArgs(extraFrontEndOptions).canaryFeatures;
|
||||
if (parsedCanary != null && canaryEnabled != parsedCanary) {
|
||||
throw Exception(
|
||||
'Unsupported option combination:\n'
|
||||
'${FlutterOptions.kWebExperimentalHotReload}: $webEnableHotReload\n'
|
||||
'${FlutterOptions.kExtraFrontEndOptions}: --dartdevc-canary=$parsedCanary',
|
||||
);
|
||||
}
|
||||
return canaryEnabled;
|
||||
}
|
||||
|
||||
/// Convert to a structured string encoded structure appropriate for usage
|
||||
/// in build system [Environment.defines].
|
||||
|
@ -375,7 +375,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
|
||||
// extensions will work with Flutter web embedded in VSCode without a Chrome debugger
|
||||
// connection.
|
||||
dartDefines: <String>['$kWidgetPreviewDtdUriEnvVar=${_dtdService.dtdUri}'],
|
||||
extraFrontEndOptions: <String>['--dartdevc-canary', '--dartdevc-module-format=ddc'],
|
||||
packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path,
|
||||
packageConfig: PackageConfig.parseBytes(
|
||||
widgetPreviewScaffoldProject.packageConfig.readAsBytesSync(),
|
||||
|
@ -167,12 +167,13 @@ class ResidentWebRunner extends ResidentRunner {
|
||||
|
||||
@override
|
||||
bool get reloadIsRestart =>
|
||||
debuggingOptions.webUseWasm ||
|
||||
// Web behavior when not using the DDC library bundle format is to restart
|
||||
// when a reload is issued. We can't use `canHotReload` to signal this
|
||||
// since we still want a reload command to succeed, but to do a hot
|
||||
// restart.
|
||||
debuggingOptions.buildInfo.ddcModuleFormat != DdcModuleFormat.ddc ||
|
||||
debuggingOptions.buildInfo.canaryFeatures != true;
|
||||
!debuggingOptions.buildInfo.canaryFeatures;
|
||||
|
||||
@override
|
||||
bool get supportsDetach => stopAppDuringCleanup;
|
||||
@ -321,7 +322,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
|
||||
chromiumLauncher: _chromiumLauncher,
|
||||
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
|
||||
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
|
||||
canaryFeatures: debuggingOptions.buildInfo.canaryFeatures ?? false,
|
||||
canaryFeatures: debuggingOptions.buildInfo.canaryFeatures,
|
||||
webRenderer: debuggingOptions.webRenderer,
|
||||
isWasm: debuggingOptions.webUseWasm,
|
||||
useLocalCanvasKit: debuggingOptions.buildInfo.useLocalCanvasKit,
|
||||
@ -420,7 +421,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
|
||||
final DateTime start = _systemClock.now();
|
||||
final Status status;
|
||||
if (debuggingOptions.buildInfo.ddcModuleFormat != DdcModuleFormat.ddc ||
|
||||
debuggingOptions.buildInfo.canaryFeatures == false) {
|
||||
!debuggingOptions.buildInfo.canaryFeatures) {
|
||||
// Triggering hot reload performed hot restart for the old module formats
|
||||
// historically. Keep that behavior and only perform hot reload when the
|
||||
// new module format is used.
|
||||
|
@ -114,6 +114,12 @@ class FlutterDevice {
|
||||
globals.artifacts!.getHostArtifact(HostArtifact.webPlatformKernelFolder).path,
|
||||
platformDillName,
|
||||
);
|
||||
final List<String> extraFrontEndOptions = <String>[
|
||||
...buildInfo.extraFrontEndOptions,
|
||||
if (buildInfo.webEnableHotReload)
|
||||
// These flags are only valid to be passed when compiling with DDC.
|
||||
...<String>['--dartdevc-canary', '--dartdevc-module-format=ddc'],
|
||||
];
|
||||
|
||||
generator = ResidentCompiler(
|
||||
globals.artifacts!.getHostArtifact(HostArtifact.flutterWebSdk).path,
|
||||
@ -133,7 +139,7 @@ class FlutterDevice {
|
||||
assumeInitializeFromDillUpToDate: buildInfo.assumeInitializeFromDillUpToDate,
|
||||
targetModel: TargetModel.dartdevc,
|
||||
frontendServerStarterPath: buildInfo.frontendServerStarterPath,
|
||||
extraFrontEndOptions: buildInfo.extraFrontEndOptions,
|
||||
extraFrontEndOptions: extraFrontEndOptions,
|
||||
platformDill: globals.fs.file(platformDillPath).absolute.uri.toString(),
|
||||
dartDefines: buildInfo.dartDefines,
|
||||
librariesSpec:
|
||||
|
@ -369,6 +369,7 @@ abstract class FlutterCommand extends Command<void> {
|
||||
argParser.addFlag(
|
||||
FlutterOptions.kWebExperimentalHotReload,
|
||||
help: 'Enables new module format that supports hot reload.',
|
||||
defaultsTo: true,
|
||||
hide: !verboseHelp,
|
||||
);
|
||||
argParser.addOption(
|
||||
@ -1325,10 +1326,9 @@ abstract class FlutterCommand extends Command<void> {
|
||||
}
|
||||
|
||||
// TODO(natebiggs): Delete this when new DDC module system is the default.
|
||||
if (argParser.options.containsKey(FlutterOptions.kWebExperimentalHotReload) &&
|
||||
boolArg(FlutterOptions.kWebExperimentalHotReload)) {
|
||||
extraFrontEndOptions.addAll(<String>['--dartdevc-canary', '--dartdevc-module-format=ddc']);
|
||||
}
|
||||
final bool webEnableHotReload =
|
||||
argParser.options.containsKey(FlutterOptions.kWebExperimentalHotReload) &&
|
||||
boolArg(FlutterOptions.kWebExperimentalHotReload);
|
||||
|
||||
String? codeSizeDirectory;
|
||||
if (argParser.options.containsKey(FlutterOptions.kAnalyzeSize) &&
|
||||
@ -1457,6 +1457,7 @@ abstract class FlutterCommand extends Command<void> {
|
||||
argParser.options.containsKey(FlutterOptions.kAssumeInitializeFromDillUpToDate) &&
|
||||
boolArg(FlutterOptions.kAssumeInitializeFromDillUpToDate),
|
||||
useLocalCanvasKit: useLocalCanvasKit,
|
||||
webEnableHotReload: webEnableHotReload,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -283,7 +283,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
// TODO(srujzs): Remove this assertion when the library bundle format is
|
||||
// supported without canary mode.
|
||||
if (buildInfo.ddcModuleFormat == DdcModuleFormat.ddc) {
|
||||
assert(buildInfo.canaryFeatures ?? true);
|
||||
assert(buildInfo.canaryFeatures);
|
||||
}
|
||||
final Map<WebRendererMode, HostArtifact> dartSdkArtifactMap =
|
||||
buildInfo.ddcModuleFormat == DdcModuleFormat.ddc
|
||||
@ -296,7 +296,7 @@ class FlutterWebPlatform extends PlatformPlugin {
|
||||
// TODO(srujzs): Remove this assertion when the library bundle format is
|
||||
// supported without canary mode.
|
||||
if (buildInfo.ddcModuleFormat == DdcModuleFormat.ddc) {
|
||||
assert(buildInfo.canaryFeatures ?? true);
|
||||
assert(buildInfo.canaryFeatures);
|
||||
}
|
||||
final Map<WebRendererMode, HostArtifact> dartSdkArtifactMap =
|
||||
buildInfo.ddcModuleFormat == DdcModuleFormat.ddc
|
||||
|
@ -133,6 +133,7 @@ void main() {
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
treeShakeIcons: false,
|
||||
extraFrontEndOptions: <String>['--dartdevc-module-format=ddc', '--canary'],
|
||||
webEnableHotReload: true,
|
||||
),
|
||||
webMemoryFS: WebMemoryFS(),
|
||||
fileSystem: fileSystem,
|
||||
|
@ -735,7 +735,8 @@ name: my_app
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
// Hot reload only supported with these flags for now.
|
||||
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
||||
webEnableHotReload: true,
|
||||
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
||||
),
|
||||
),
|
||||
@ -842,7 +843,8 @@ name: my_app
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
// Hot reload only supported with these flags for now.
|
||||
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
||||
webEnableHotReload: true,
|
||||
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
||||
),
|
||||
),
|
||||
@ -927,7 +929,8 @@ name: my_app
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
// Hot reload only supported with these flags for now.
|
||||
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
||||
webEnableHotReload: true,
|
||||
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
||||
),
|
||||
webUseWasm: true,
|
||||
@ -996,13 +999,14 @@ name: my_app
|
||||
// Test one extra config where `fullRestart` is false without the DDC library
|
||||
// bundle format - we should do a hot restart in this case because hot reload
|
||||
// is not available.
|
||||
for (final (List<String> flags, bool fullRestart) in <(List<String>, bool)>[
|
||||
(kDdcLibraryBundleFlags, true),
|
||||
(<String>[], true),
|
||||
(<String>[], false),
|
||||
for (final (bool webEnableHotReload, bool fullRestart) in <(bool, bool)>[
|
||||
(true, true),
|
||||
(false, true),
|
||||
(false, false),
|
||||
]) {
|
||||
testUsingContext(
|
||||
'Can hot restart after attaching with flags: $flags fullRestart: $fullRestart',
|
||||
'Can hot restart after attaching with '
|
||||
'webEnableHotReload: $webEnableHotReload fullRestart: $fullRestart',
|
||||
() async {
|
||||
final BufferLogger logger = BufferLogger.test();
|
||||
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
||||
@ -1016,7 +1020,8 @@ name: my_app
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
extraFrontEndOptions: flags,
|
||||
extraFrontEndOptions: webEnableHotReload ? kDdcLibraryBundleFlags : const <String>[],
|
||||
webEnableHotReload: webEnableHotReload,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -1242,8 +1247,9 @@ name: my_app
|
||||
},
|
||||
);
|
||||
|
||||
// TODO(nshahan): Delete this test case when hot reload can no longer be disabled.
|
||||
testUsingContext(
|
||||
'Fails non-fatally on vmservice response error for hot restart',
|
||||
'Fails non-fatally on vmservice response error for hot restart (legacy default case)',
|
||||
() async {
|
||||
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
||||
fakeVmServiceHost = FakeVmServiceHost(
|
||||
@ -1261,6 +1267,8 @@ name: my_app
|
||||
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
||||
await connectionInfoCompleter.future;
|
||||
|
||||
// Historically the .restart() would perform a hot restart even without
|
||||
// passing fullRestart: true.
|
||||
final OperationResult result = await residentWebRunner.restart();
|
||||
|
||||
expect(result.code, 0);
|
||||
@ -1272,8 +1280,9 @@ name: my_app
|
||||
},
|
||||
);
|
||||
|
||||
// TODO(nshahan): Delete this test case when hot reload can no longer be disabled.
|
||||
testUsingContext(
|
||||
'Fails fatally on Vm Service error response',
|
||||
'Fails fatally on Vm Service error response (legacy default case)',
|
||||
() async {
|
||||
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
||||
fakeVmServiceHost = FakeVmServiceHost(
|
||||
@ -1303,6 +1312,94 @@ name: my_app
|
||||
},
|
||||
);
|
||||
|
||||
for (final bool webEnableHotReload in <bool>[true, false]) {
|
||||
testUsingContext(
|
||||
'Fails non-fatally on vmservice response error for hot restart with webEnableHotReload: $webEnableHotReload',
|
||||
() async {
|
||||
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
||||
flutterDevice,
|
||||
debuggingOptions: DebuggingOptions.enabled(
|
||||
BuildInfo(
|
||||
BuildMode.debug,
|
||||
null,
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
webEnableHotReload: webEnableHotReload,
|
||||
extraFrontEndOptions: webEnableHotReload ? kDdcLibraryBundleFlags : <String>[],
|
||||
),
|
||||
),
|
||||
);
|
||||
fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
...kAttachExpectations,
|
||||
const FakeVmServiceRequest(
|
||||
method: kHotRestartServiceName,
|
||||
jsonResponse: <String, Object>{'type': 'Failed'},
|
||||
),
|
||||
],
|
||||
);
|
||||
setupMocks();
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
||||
Completer<DebugConnectionInfo>();
|
||||
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
||||
await connectionInfoCompleter.future;
|
||||
|
||||
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
||||
|
||||
expect(result.code, 0);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
Pub: ThrowingPub.new,
|
||||
},
|
||||
);
|
||||
|
||||
testUsingContext(
|
||||
'Fails fatally on Vm Service error response with webEnableHotReload: $webEnableHotReload',
|
||||
() async {
|
||||
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
||||
flutterDevice,
|
||||
debuggingOptions: DebuggingOptions.enabled(
|
||||
BuildInfo(
|
||||
BuildMode.debug,
|
||||
null,
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
webEnableHotReload: webEnableHotReload,
|
||||
extraFrontEndOptions: webEnableHotReload ? kDdcLibraryBundleFlags : <String>[],
|
||||
),
|
||||
),
|
||||
);
|
||||
fakeVmServiceHost = FakeVmServiceHost(
|
||||
requests: <VmServiceExpectation>[
|
||||
...kAttachExpectations,
|
||||
FakeVmServiceRequest(
|
||||
method: kHotRestartServiceName,
|
||||
// Failed response,
|
||||
error: FakeRPCError(code: vm_service.RPCErrorKind.kInternalError.code),
|
||||
),
|
||||
],
|
||||
);
|
||||
setupMocks();
|
||||
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
||||
Completer<DebugConnectionInfo>();
|
||||
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
||||
await connectionInfoCompleter.future;
|
||||
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
||||
|
||||
expect(result.code, 1);
|
||||
expect(result.message, contains(vm_service.RPCErrorKind.kInternalError.code.toString()));
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => processManager,
|
||||
Pub: ThrowingPub.new,
|
||||
},
|
||||
);
|
||||
}
|
||||
testUsingContext(
|
||||
'printHelp without details shows only hot restart help message',
|
||||
() async {
|
||||
@ -1335,7 +1432,8 @@ name: my_app
|
||||
trackWidgetCreation: true,
|
||||
treeShakeIcons: false,
|
||||
packageConfigPath: '.dart_tool/package_config.json',
|
||||
// Hot reload only supported with these flags for now.
|
||||
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
||||
webEnableHotReload: true,
|
||||
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
||||
),
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user