Skip to content

Commit

Permalink
Adding support for DDC modules when running Flutter Web in debug mode…
Browse files Browse the repository at this point in the history
… (#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
  • Loading branch information
Markzipan authored Feb 24, 2024
1 parent b781da9 commit ee94fe2
Show file tree
Hide file tree
Showing 12 changed files with 2,458 additions and 135 deletions.
229 changes: 166 additions & 63 deletions packages/flutter_tools/lib/src/artifacts.dart

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions packages/flutter_tools/lib/src/build_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ class BuildInfo {
/// so the uncapitalized flavor name is used to compute the output file name
String? get uncapitalizedFlavor => _uncapitalize(flavor);

/// The module system DDC is targeting, or null if not using DDC.
// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
DdcModuleFormat? get ddcModuleFormat => _ddcModuleFormatFromFrontEndArgs(extraFrontEndOptions);

/// Convert to a structured string encoded structure appropriate for usage
/// in build system [Environment.defines].
///
Expand Down Expand Up @@ -1044,6 +1048,28 @@ enum NullSafetyMode {
autodetect,
}

/// Indicates the module system DDC is targeting.
enum DdcModuleFormat {
amd,
ddc,
}

// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
DdcModuleFormat? _ddcModuleFormatFromFrontEndArgs(List<String>? extraFrontEndArgs) {
if (extraFrontEndArgs == null) {
return null;
}
const String ddcModuleFormatString = '--dartdevc-module-format=';
for (final String flag in extraFrontEndArgs) {
if (flag.startsWith(ddcModuleFormatString)) {
final String moduleFormatString = flag
.substring(ddcModuleFormatString.length, flag.length);
return DdcModuleFormat.values.byName(moduleFormatString);
}
}
return null;
}

String _getCurrentHostPlatformArchName() {
final HostPlatform hostPlatform = getCurrentHostPlatform();
return hostPlatform.platformName;
Expand Down
90 changes: 71 additions & 19 deletions packages/flutter_tools/lib/src/isolated/devfs_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ class WebAssetServer implements AssetReader {
this.internetAddress,
this._modules,
this._digests,
this._nullSafetyMode, {
this._nullSafetyMode,
this._ddcModuleSystem, {
required this.webRenderer,
}) : basePath = _getIndexHtml().getBaseHref();

Expand Down Expand Up @@ -181,6 +182,8 @@ class WebAssetServer implements AssetReader {
required WebRendererMode webRenderer,
bool testMode = false,
DwdsLauncher dwdsLauncher = Dwds.start,
// TODO(markzipan): Make sure this default value aligns with that in the debugger options.
bool ddcModuleSystem = false,
}) async {
InternetAddress address;
if (hostname == 'any') {
Expand Down Expand Up @@ -227,6 +230,7 @@ class WebAssetServer implements AssetReader {
modules,
digests,
nullSafetyMode,
ddcModuleSystem,
webRenderer: webRenderer,
);
if (testMode) {
Expand Down Expand Up @@ -285,16 +289,26 @@ class WebAssetServer implements AssetReader {
return chromium.chromeConnection;
},
toolConfiguration: ToolConfiguration(
loadStrategy: FrontendServerRequireStrategyProvider(
loadStrategy: ddcModuleSystem
? FrontendServerLegacyStrategyProvider(
ReloadConfiguration.none,
server,
PackageUriMapper(packageConfig),
digestProvider,
BuildSettings(
appEntrypoint: packageConfig.toPackageUri(
globals.fs.file(entrypoint).absolute.uri,
),
),
)),
).strategy
: FrontendServerRequireStrategyProvider(
ReloadConfiguration.none,
server,
PackageUriMapper(packageConfig),
digestProvider,
BuildSettings(
appEntrypoint: packageConfig.toPackageUri(
globals.fs.file(entrypoint).absolute.uri,
)),
).strategy,
debugSettings: DebugSettings(
enableDebugExtension: true,
Expand Down Expand Up @@ -328,6 +342,7 @@ class WebAssetServer implements AssetReader {
}

final NullSafetyMode _nullSafetyMode;
final bool _ddcModuleSystem;
final HttpServer _httpServer;
final WebMemoryFS _webMemoryFS = WebMemoryFS();
final PackageConfig _packages;
Expand Down Expand Up @@ -510,9 +525,7 @@ class WebAssetServer implements AssetReader {
final WebRendererMode webRenderer;

shelf.Response _serveIndex() {

final IndexHtml indexHtml = _getIndexHtml();

final Map<String, dynamic> buildConfig = <String, dynamic>{
'engineRevision': globals.flutterVersion.engineRevision,
'builds': <dynamic>[
Expand Down Expand Up @@ -597,15 +610,22 @@ class WebAssetServer implements AssetReader {
return webSdkFile;
}

File get _resolveDartSdkJsFile =>
globals.fs.file(globals.artifacts!.getHostArtifact(
kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]!
));
File get _resolveDartSdkJsFile {
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>>
dartSdkArtifactMap =
_ddcModuleSystem ? kDdcDartSdkJsArtifactMap : kAmdDartSdkJsArtifactMap;
return globals.fs.file(globals.artifacts!
.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
}

File get _resolveDartSdkJsMapFile =>
globals.fs.file(globals.artifacts!.getHostArtifact(
kDartSdkJsMapArtifactMap[webRenderer]![_nullSafetyMode]!
));
File get _resolveDartSdkJsMapFile {
final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>>
dartSdkArtifactMap = _ddcModuleSystem
? kDdcDartSdkJsMapArtifactMap
: kAmdDartSdkJsMapArtifactMap;
return globals.fs.file(globals.artifacts!
.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
}

@override
Future<String?> dartSourceContents(String serverPath) async {
Expand Down Expand Up @@ -679,6 +699,7 @@ class WebDevFS implements DevFS {
required this.nullAssertions,
required this.nativeNullAssertions,
required this.nullSafetyMode,
required this.ddcModuleSystem,
required this.webRenderer,
this.testMode = false,
}) : _port = port;
Expand All @@ -695,6 +716,7 @@ class WebDevFS implements DevFS {
final bool enableDds;
final Map<String, String> extraHeaders;
final bool testMode;
final bool ddcModuleSystem;
final ExpressionCompiler? expressionCompiler;
final ChromiumLauncher? chromiumLauncher;
final bool nullAssertions;
Expand Down Expand Up @@ -805,15 +827,16 @@ class WebDevFS implements DevFS {
nullSafetyMode,
webRenderer: webRenderer,
testMode: testMode,
ddcModuleSystem: ddcModuleSystem,
);

final int selectedPort = webAssetServer.selectedPort;
String url = '$hostname:$selectedPort';
if (hostname == 'any') {
url ='localhost:$selectedPort';
url = 'localhost:$selectedPort';
}
_baseUri = Uri.http(url, webAssetServer.basePath);
if (tlsCertPath != null && tlsCertKeyPath!= null) {
if (tlsCertPath != null && tlsCertKeyPath != null) {
_baseUri = Uri.https(url, webAssetServer.basePath);
}
return _baseUri!;
Expand Down Expand Up @@ -866,7 +889,12 @@ class WebDevFS implements DevFS {
generator.addFileSystemRoot(outputDirectoryPath);
final String entrypoint = globals.fs.path.basename(mainFile.path);
webAssetServer.writeBytes(entrypoint, mainFile.readAsBytesSync());
webAssetServer.writeBytes('require.js', requireJS.readAsBytesSync());
if (ddcModuleSystem) {
webAssetServer.writeBytes(
'ddc_module_loader.js', ddcModuleLoaderJS.readAsBytesSync());
} else {
webAssetServer.writeBytes('require.js', requireJS.readAsBytesSync());
}
webAssetServer.writeBytes('flutter.js', flutterJs.readAsBytesSync());
webAssetServer.writeBytes(
'stack_trace_mapper.js', stackTraceMapper.readAsBytesSync());
Expand All @@ -878,15 +906,29 @@ class WebDevFS implements DevFS {
'version.json', FlutterProject.current().getVersionInfo());
webAssetServer.writeFile(
'main.dart.js',
generateBootstrapScript(
ddcModuleSystem
? generateDDCBootstrapScript(
entrypoint: entrypoint,
ddcModuleLoaderUrl: 'ddc_module_loader.js',
mapperUrl: 'stack_trace_mapper.js',
generateLoadingIndicator: enableDwds,
)
: generateBootstrapScript(
requireUrl: 'require.js',
mapperUrl: 'stack_trace_mapper.js',
generateLoadingIndicator: enableDwds,
),
);
webAssetServer.writeFile(
'main_module.bootstrap.js',
generateMainModule(
ddcModuleSystem
? generateDDCMainModule(
entrypoint: entrypoint,
nullAssertions: nullAssertions,
nativeNullAssertions: nativeNullAssertions,
exportedMain: pathToJSIdentifier(entrypoint.split('.')[0]),
)
: generateMainModule(
entrypoint: entrypoint,
nullAssertions: nullAssertions,
nativeNullAssertions: nativeNullAssertions,
Expand Down Expand Up @@ -968,6 +1010,16 @@ class WebDevFS implements DevFS {
'require.js',
));

@visibleForTesting
final File ddcModuleLoaderJS = globals.fs.file(globals.fs.path.join(
globals.artifacts!.getArtifactPath(Artifact.engineDartSdkPath,
platform: TargetPlatform.web_javascript),
'lib',
'dev_compiler',
'ddc',
'ddc_module_loader.js',
));

@visibleForTesting
final File flutterJs = globals.fs.file(globals.fs.path.join(
globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,
Expand Down
13 changes: 7 additions & 6 deletions packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,12 @@ class ResidentWebRunner extends ResidentRunner {
if (_instance != null) {
return _instance!;
}
final vmservice.VmService? service =_connectionResult?.vmService;
final vmservice.VmService? service = _connectionResult?.vmService;
final Uri websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
final Uri httpUri = _httpUriFromWebsocketUri(websocketUri);
return _instance ??= FlutterVmService(service!, wsAddress: websocketUri, httpAddress: httpUri);
}

FlutterVmService? _instance;

@override
Expand Down Expand Up @@ -289,6 +290,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
debuggingOptions.webEnableExpressionEvaluation
? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem)
: null;

device!.devFS = WebDevFS(
hostname: debuggingOptions.hostname ?? 'localhost',
port: await getPort(),
Expand All @@ -309,6 +311,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
nullAssertions: debuggingOptions.nullAssertions,
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
webRenderer: debuggingOptions.webRenderer,
);
Uri url = await device!.devFS!.create();
Expand Down Expand Up @@ -605,7 +608,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
_connectionResult = await webDevFS.connect(useDebugExtension);
unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit));

void onLogEvent(vmservice.Event event) {
void onLogEvent(vmservice.Event event) {
final String message = processVmServiceMessage(event);
_logger.printStatus(message);
}
Expand Down Expand Up @@ -640,7 +643,6 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
vmService: _vmService.service,
);


websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
device!.vmService = _vmService;

Expand All @@ -651,8 +653,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
_connectionResult!.appConnection!.runMain();
} else {
late StreamSubscription<void> resumeSub;
resumeSub = _vmService.service.onDebugEvent
.listen((vmservice.Event event) {
resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) {
if (event.type == vmservice.EventKind.kResume) {
_connectionResult!.appConnection!.runMain();
resumeSub.cancel();
Expand All @@ -674,7 +675,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
..writeAsStringSync(websocketUri.toString());
}
_logger.printStatus('Debug service listening on $websocketUri');
if (debuggingOptions.buildInfo.nullSafetyMode != NullSafetyMode.sound) {
if (debuggingOptions.buildInfo.nullSafetyMode != NullSafetyMode.sound) {
_logger.printStatus('');
_logger.printStatus(
'Running without sound null safety ⚠️',
Expand Down
26 changes: 22 additions & 4 deletions packages/flutter_tools/lib/src/test/flutter_web_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ class FlutterWebPlatform extends PlatformPlugin {
'require.js',
));

/// The ddc module loader js binary.
File get _ddcModuleLoaderJs => _fileSystem.file(_fileSystem.path.join(
_artifacts!.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript),
'lib',
'dev_compiler',
'ddc',
'ddc_module_loader.js',
));

/// The ddc to dart stack trace mapper.
File get _stackTraceMapper => _fileSystem.file(_fileSystem.path.join(
_artifacts!.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript),
Expand All @@ -245,11 +254,15 @@ class FlutterWebPlatform extends PlatformPlugin {
'dart_stack_trace_mapper.js',
));

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

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

File _canvasKitFile(String relativePath) {
final String canvasKitPath = _fileSystem.path.join(
Expand Down Expand Up @@ -303,6 +316,11 @@ class FlutterWebPlatform extends PlatformPlugin {
_requireJs.openRead(),
headers: <String, String>{'Content-Type': 'text/javascript'},
);
} else if (request.requestedUri.path.contains('ddc_module_loader.js')) {
return shelf.Response.ok(
_ddcModuleLoaderJs.openRead(),
headers: <String, String>{'Content-Type': 'text/javascript'},
);
} else if (request.requestedUri.path.contains('ahem.ttf')) {
return shelf.Response.ok(_ahem.openRead());
} else if (request.requestedUri.path.contains('dart_sdk.js')) {
Expand Down
Loading

0 comments on commit ee94fe2

Please sign in to comment.