Skip to content

Commit ee94fe2

Browse files
authored
Adding support for DDC modules when running Flutter Web in debug mode (flutter#141423)
### Context: DDC modules are abstractions over how libraries are loaded/updated. The entirety of google3 uses the DDC/legacy module system due to its flexibility extensibility over the other two (ES6 and AMD/RequireJS). Unifying DDC's module system saves us from duplicating work and will allow us to have finer grained control over how JS modules are loaded. This is a a prerequisite to features such as hot reload. ### Overview: This change plumbs a boolean flag through flutter_tools that switches between DDC (new) and AMD (current) modules. This mode is automatically applied when `--extra-front-end-options=--dartdevc-module-format=ddc` is specified alongside `flutter run`. Other important additions include: * Splitting Flutter artifacts between DDC and AMD modules * Adding unit tests for the DDC module system * Additional bootstrapper logic for the DDC module system We don't expect to see any user-visible behavior or performance differences. This is dependent on [incoming module system support in DWDS](dart-lang/webdev#2295) and [additional artifacts in the engine](flutter/engine#47783). This is part of a greater effort to deprecate the AMD module system: dart-lang/sdk#52361
1 parent b781da9 commit ee94fe2

File tree

12 files changed

+2458
-135
lines changed

12 files changed

+2458
-135
lines changed

packages/flutter_tools/lib/src/artifacts.dart

Lines changed: 166 additions & 63 deletions
Large diffs are not rendered by default.

packages/flutter_tools/lib/src/build_info.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ class BuildInfo {
222222
/// so the uncapitalized flavor name is used to compute the output file name
223223
String? get uncapitalizedFlavor => _uncapitalize(flavor);
224224

225+
/// The module system DDC is targeting, or null if not using DDC.
226+
// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
227+
DdcModuleFormat? get ddcModuleFormat => _ddcModuleFormatFromFrontEndArgs(extraFrontEndOptions);
228+
225229
/// Convert to a structured string encoded structure appropriate for usage
226230
/// in build system [Environment.defines].
227231
///
@@ -1044,6 +1048,28 @@ enum NullSafetyMode {
10441048
autodetect,
10451049
}
10461050

1051+
/// Indicates the module system DDC is targeting.
1052+
enum DdcModuleFormat {
1053+
amd,
1054+
ddc,
1055+
}
1056+
1057+
// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
1058+
DdcModuleFormat? _ddcModuleFormatFromFrontEndArgs(List<String>? extraFrontEndArgs) {
1059+
if (extraFrontEndArgs == null) {
1060+
return null;
1061+
}
1062+
const String ddcModuleFormatString = '--dartdevc-module-format=';
1063+
for (final String flag in extraFrontEndArgs) {
1064+
if (flag.startsWith(ddcModuleFormatString)) {
1065+
final String moduleFormatString = flag
1066+
.substring(ddcModuleFormatString.length, flag.length);
1067+
return DdcModuleFormat.values.byName(moduleFormatString);
1068+
}
1069+
}
1070+
return null;
1071+
}
1072+
10471073
String _getCurrentHostPlatformArchName() {
10481074
final HostPlatform hostPlatform = getCurrentHostPlatform();
10491075
return hostPlatform.platformName;

packages/flutter_tools/lib/src/isolated/devfs_web.dart

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ class WebAssetServer implements AssetReader {
117117
this.internetAddress,
118118
this._modules,
119119
this._digests,
120-
this._nullSafetyMode, {
120+
this._nullSafetyMode,
121+
this._ddcModuleSystem, {
121122
required this.webRenderer,
122123
}) : basePath = _getIndexHtml().getBaseHref();
123124

@@ -181,6 +182,8 @@ class WebAssetServer implements AssetReader {
181182
required WebRendererMode webRenderer,
182183
bool testMode = false,
183184
DwdsLauncher dwdsLauncher = Dwds.start,
185+
// TODO(markzipan): Make sure this default value aligns with that in the debugger options.
186+
bool ddcModuleSystem = false,
184187
}) async {
185188
InternetAddress address;
186189
if (hostname == 'any') {
@@ -227,6 +230,7 @@ class WebAssetServer implements AssetReader {
227230
modules,
228231
digests,
229232
nullSafetyMode,
233+
ddcModuleSystem,
230234
webRenderer: webRenderer,
231235
);
232236
if (testMode) {
@@ -285,16 +289,26 @@ class WebAssetServer implements AssetReader {
285289
return chromium.chromeConnection;
286290
},
287291
toolConfiguration: ToolConfiguration(
288-
loadStrategy: FrontendServerRequireStrategyProvider(
292+
loadStrategy: ddcModuleSystem
293+
? FrontendServerLegacyStrategyProvider(
289294
ReloadConfiguration.none,
290295
server,
291296
PackageUriMapper(packageConfig),
292297
digestProvider,
293298
BuildSettings(
294299
appEntrypoint: packageConfig.toPackageUri(
295300
globals.fs.file(entrypoint).absolute.uri,
296-
),
297-
),
301+
)),
302+
).strategy
303+
: FrontendServerRequireStrategyProvider(
304+
ReloadConfiguration.none,
305+
server,
306+
PackageUriMapper(packageConfig),
307+
digestProvider,
308+
BuildSettings(
309+
appEntrypoint: packageConfig.toPackageUri(
310+
globals.fs.file(entrypoint).absolute.uri,
311+
)),
298312
).strategy,
299313
debugSettings: DebugSettings(
300314
enableDebugExtension: true,
@@ -328,6 +342,7 @@ class WebAssetServer implements AssetReader {
328342
}
329343

330344
final NullSafetyMode _nullSafetyMode;
345+
final bool _ddcModuleSystem;
331346
final HttpServer _httpServer;
332347
final WebMemoryFS _webMemoryFS = WebMemoryFS();
333348
final PackageConfig _packages;
@@ -510,9 +525,7 @@ class WebAssetServer implements AssetReader {
510525
final WebRendererMode webRenderer;
511526

512527
shelf.Response _serveIndex() {
513-
514528
final IndexHtml indexHtml = _getIndexHtml();
515-
516529
final Map<String, dynamic> buildConfig = <String, dynamic>{
517530
'engineRevision': globals.flutterVersion.engineRevision,
518531
'builds': <dynamic>[
@@ -597,15 +610,22 @@ class WebAssetServer implements AssetReader {
597610
return webSdkFile;
598611
}
599612

600-
File get _resolveDartSdkJsFile =>
601-
globals.fs.file(globals.artifacts!.getHostArtifact(
602-
kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]!
603-
));
613+
File get _resolveDartSdkJsFile {
614+
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>>
615+
dartSdkArtifactMap =
616+
_ddcModuleSystem ? kDdcDartSdkJsArtifactMap : kAmdDartSdkJsArtifactMap;
617+
return globals.fs.file(globals.artifacts!
618+
.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
619+
}
604620

605-
File get _resolveDartSdkJsMapFile =>
606-
globals.fs.file(globals.artifacts!.getHostArtifact(
607-
kDartSdkJsMapArtifactMap[webRenderer]![_nullSafetyMode]!
608-
));
621+
File get _resolveDartSdkJsMapFile {
622+
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>>
623+
dartSdkArtifactMap = _ddcModuleSystem
624+
? kDdcDartSdkJsMapArtifactMap
625+
: kAmdDartSdkJsMapArtifactMap;
626+
return globals.fs.file(globals.artifacts!
627+
.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
628+
}
609629

610630
@override
611631
Future<String?> dartSourceContents(String serverPath) async {
@@ -679,6 +699,7 @@ class WebDevFS implements DevFS {
679699
required this.nullAssertions,
680700
required this.nativeNullAssertions,
681701
required this.nullSafetyMode,
702+
required this.ddcModuleSystem,
682703
required this.webRenderer,
683704
this.testMode = false,
684705
}) : _port = port;
@@ -695,6 +716,7 @@ class WebDevFS implements DevFS {
695716
final bool enableDds;
696717
final Map<String, String> extraHeaders;
697718
final bool testMode;
719+
final bool ddcModuleSystem;
698720
final ExpressionCompiler? expressionCompiler;
699721
final ChromiumLauncher? chromiumLauncher;
700722
final bool nullAssertions;
@@ -805,15 +827,16 @@ class WebDevFS implements DevFS {
805827
nullSafetyMode,
806828
webRenderer: webRenderer,
807829
testMode: testMode,
830+
ddcModuleSystem: ddcModuleSystem,
808831
);
809832

810833
final int selectedPort = webAssetServer.selectedPort;
811834
String url = '$hostname:$selectedPort';
812835
if (hostname == 'any') {
813-
url ='localhost:$selectedPort';
836+
url = 'localhost:$selectedPort';
814837
}
815838
_baseUri = Uri.http(url, webAssetServer.basePath);
816-
if (tlsCertPath != null && tlsCertKeyPath!= null) {
839+
if (tlsCertPath != null && tlsCertKeyPath != null) {
817840
_baseUri = Uri.https(url, webAssetServer.basePath);
818841
}
819842
return _baseUri!;
@@ -866,7 +889,12 @@ class WebDevFS implements DevFS {
866889
generator.addFileSystemRoot(outputDirectoryPath);
867890
final String entrypoint = globals.fs.path.basename(mainFile.path);
868891
webAssetServer.writeBytes(entrypoint, mainFile.readAsBytesSync());
869-
webAssetServer.writeBytes('require.js', requireJS.readAsBytesSync());
892+
if (ddcModuleSystem) {
893+
webAssetServer.writeBytes(
894+
'ddc_module_loader.js', ddcModuleLoaderJS.readAsBytesSync());
895+
} else {
896+
webAssetServer.writeBytes('require.js', requireJS.readAsBytesSync());
897+
}
870898
webAssetServer.writeBytes('flutter.js', flutterJs.readAsBytesSync());
871899
webAssetServer.writeBytes(
872900
'stack_trace_mapper.js', stackTraceMapper.readAsBytesSync());
@@ -878,15 +906,29 @@ class WebDevFS implements DevFS {
878906
'version.json', FlutterProject.current().getVersionInfo());
879907
webAssetServer.writeFile(
880908
'main.dart.js',
881-
generateBootstrapScript(
909+
ddcModuleSystem
910+
? generateDDCBootstrapScript(
911+
entrypoint: entrypoint,
912+
ddcModuleLoaderUrl: 'ddc_module_loader.js',
913+
mapperUrl: 'stack_trace_mapper.js',
914+
generateLoadingIndicator: enableDwds,
915+
)
916+
: generateBootstrapScript(
882917
requireUrl: 'require.js',
883918
mapperUrl: 'stack_trace_mapper.js',
884919
generateLoadingIndicator: enableDwds,
885920
),
886921
);
887922
webAssetServer.writeFile(
888923
'main_module.bootstrap.js',
889-
generateMainModule(
924+
ddcModuleSystem
925+
? generateDDCMainModule(
926+
entrypoint: entrypoint,
927+
nullAssertions: nullAssertions,
928+
nativeNullAssertions: nativeNullAssertions,
929+
exportedMain: pathToJSIdentifier(entrypoint.split('.')[0]),
930+
)
931+
: generateMainModule(
890932
entrypoint: entrypoint,
891933
nullAssertions: nullAssertions,
892934
nativeNullAssertions: nativeNullAssertions,
@@ -968,6 +1010,16 @@ class WebDevFS implements DevFS {
9681010
'require.js',
9691011
));
9701012

1013+
@visibleForTesting
1014+
final File ddcModuleLoaderJS = globals.fs.file(globals.fs.path.join(
1015+
globals.artifacts!.getArtifactPath(Artifact.engineDartSdkPath,
1016+
platform: TargetPlatform.web_javascript),
1017+
'lib',
1018+
'dev_compiler',
1019+
'ddc',
1020+
'ddc_module_loader.js',
1021+
));
1022+
9711023
@visibleForTesting
9721024
final File flutterJs = globals.fs.file(globals.fs.path.join(
9731025
globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,

packages/flutter_tools/lib/src/isolated/resident_web_runner.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,12 @@ class ResidentWebRunner extends ResidentRunner {
166166
if (_instance != null) {
167167
return _instance!;
168168
}
169-
final vmservice.VmService? service =_connectionResult?.vmService;
169+
final vmservice.VmService? service = _connectionResult?.vmService;
170170
final Uri websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
171171
final Uri httpUri = _httpUriFromWebsocketUri(websocketUri);
172172
return _instance ??= FlutterVmService(service!, wsAddress: websocketUri, httpAddress: httpUri);
173173
}
174+
174175
FlutterVmService? _instance;
175176

176177
@override
@@ -289,6 +290,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
289290
debuggingOptions.webEnableExpressionEvaluation
290291
? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem)
291292
: null;
293+
292294
device!.devFS = WebDevFS(
293295
hostname: debuggingOptions.hostname ?? 'localhost',
294296
port: await getPort(),
@@ -309,6 +311,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
309311
nullAssertions: debuggingOptions.nullAssertions,
310312
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
311313
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
314+
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
312315
webRenderer: debuggingOptions.webRenderer,
313316
);
314317
Uri url = await device!.devFS!.create();
@@ -605,7 +608,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
605608
_connectionResult = await webDevFS.connect(useDebugExtension);
606609
unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit));
607610

608-
void onLogEvent(vmservice.Event event) {
611+
void onLogEvent(vmservice.Event event) {
609612
final String message = processVmServiceMessage(event);
610613
_logger.printStatus(message);
611614
}
@@ -640,7 +643,6 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
640643
vmService: _vmService.service,
641644
);
642645

643-
644646
websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
645647
device!.vmService = _vmService;
646648

@@ -651,8 +653,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
651653
_connectionResult!.appConnection!.runMain();
652654
} else {
653655
late StreamSubscription<void> resumeSub;
654-
resumeSub = _vmService.service.onDebugEvent
655-
.listen((vmservice.Event event) {
656+
resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) {
656657
if (event.type == vmservice.EventKind.kResume) {
657658
_connectionResult!.appConnection!.runMain();
658659
resumeSub.cancel();
@@ -674,7 +675,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
674675
..writeAsStringSync(websocketUri.toString());
675676
}
676677
_logger.printStatus('Debug service listening on $websocketUri');
677-
if (debuggingOptions.buildInfo.nullSafetyMode != NullSafetyMode.sound) {
678+
if (debuggingOptions.buildInfo.nullSafetyMode != NullSafetyMode.sound) {
678679
_logger.printStatus('');
679680
_logger.printStatus(
680681
'Running without sound null safety ⚠️',

packages/flutter_tools/lib/src/test/flutter_web_platform.dart

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ class FlutterWebPlatform extends PlatformPlugin {
236236
'require.js',
237237
));
238238

239+
/// The ddc module loader js binary.
240+
File get _ddcModuleLoaderJs => _fileSystem.file(_fileSystem.path.join(
241+
_artifacts!.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript),
242+
'lib',
243+
'dev_compiler',
244+
'ddc',
245+
'ddc_module_loader.js',
246+
));
247+
239248
/// The ddc to dart stack trace mapper.
240249
File get _stackTraceMapper => _fileSystem.file(_fileSystem.path.join(
241250
_artifacts!.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript),
@@ -245,11 +254,15 @@ class FlutterWebPlatform extends PlatformPlugin {
245254
'dart_stack_trace_mapper.js',
246255
));
247256

248-
File get _dartSdk => _fileSystem.file(
249-
_artifacts!.getHostArtifact(kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]!));
257+
File get _dartSdk {
258+
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> dartSdkArtifactMap = buildInfo.ddcModuleFormat == DdcModuleFormat.ddc ? kDdcDartSdkJsArtifactMap : kAmdDartSdkJsArtifactMap;
259+
return _fileSystem.file(_artifacts!.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
260+
}
250261

251-
File get _dartSdkSourcemaps => _fileSystem.file(
252-
_artifacts!.getHostArtifact(kDartSdkJsMapArtifactMap[webRenderer]![_nullSafetyMode]!));
262+
File get _dartSdkSourcemaps {
263+
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> dartSdkArtifactMap = buildInfo.ddcModuleFormat == DdcModuleFormat.ddc ? kDdcDartSdkJsMapArtifactMap : kAmdDartSdkJsMapArtifactMap;
264+
return _fileSystem.file(_artifacts!.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
265+
}
253266

254267
File _canvasKitFile(String relativePath) {
255268
final String canvasKitPath = _fileSystem.path.join(
@@ -303,6 +316,11 @@ class FlutterWebPlatform extends PlatformPlugin {
303316
_requireJs.openRead(),
304317
headers: <String, String>{'Content-Type': 'text/javascript'},
305318
);
319+
} else if (request.requestedUri.path.contains('ddc_module_loader.js')) {
320+
return shelf.Response.ok(
321+
_ddcModuleLoaderJs.openRead(),
322+
headers: <String, String>{'Content-Type': 'text/javascript'},
323+
);
306324
} else if (request.requestedUri.path.contains('ahem.ttf')) {
307325
return shelf.Response.ok(_ahem.openRead());
308326
} else if (request.requestedUri.path.contains('dart_sdk.js')) {

0 commit comments

Comments
 (0)