From ee94fe262b63b0761e8e1f889ae52322fef068d2 Mon Sep 17 00:00:00 2001 From: MarkZ Date: Fri, 23 Feb 2024 16:26:04 -0800 Subject: [PATCH] Adding support for DDC modules when running Flutter Web in debug mode (#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](https://github.com/dart-lang/webdev/pull/2295) and [additional artifacts in the engine](https://github.com/flutter/engine/pull/47783). This is part of a greater effort to deprecate the AMD module system: https://github.com/dart-lang/sdk/issues/52361 --- packages/flutter_tools/lib/src/artifacts.dart | 229 ++- .../flutter_tools/lib/src/build_info.dart | 26 + .../lib/src/isolated/devfs_web.dart | 90 +- .../lib/src/isolated/resident_web_runner.dart | 13 +- .../lib/src/test/flutter_web_platform.dart | 26 +- .../flutter_tools/lib/src/web/bootstrap.dart | 163 ++ .../flutter_tools/lib/src/web/compile.dart | 66 +- .../hermetic/flutter_web_platform_test.dart | 69 +- .../test/general.shard/artifacts_test.dart | 53 +- .../general.shard/web/bootstrap_test.dart | 163 ++ .../web/devfs_web_ddc_modules_test.dart | 1662 +++++++++++++++++ .../general.shard/web/devfs_web_test.dart | 33 +- 12 files changed, 2458 insertions(+), 135 deletions(-) create mode 100644 packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 7f705ece161..1dba8e9b3b3 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -94,19 +94,34 @@ enum HostArtifact { /// The summary dill with null safety enabled for the dartdevc target. webPlatformDart2JSSoundKernelDill, - /// The precompiled SDKs and sourcemaps for web debug builds. - webPrecompiledSdk, - webPrecompiledSdkSourcemaps, - webPrecompiledCanvaskitSdk, - webPrecompiledCanvaskitSdkSourcemaps, - webPrecompiledCanvaskitAndHtmlSdk, - webPrecompiledCanvaskitAndHtmlSdkSourcemaps, - webPrecompiledSoundSdk, - webPrecompiledSoundSdkSourcemaps, - webPrecompiledCanvaskitSoundSdk, - webPrecompiledCanvaskitSoundSdkSourcemaps, - webPrecompiledCanvaskitAndHtmlSoundSdk, - webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps, + /// The precompiled SDKs and sourcemaps for web debug builds with the AMD module system. + // TODO(markzipan): delete these when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060. + webPrecompiledAmdSdk, + webPrecompiledAmdSdkSourcemaps, + webPrecompiledAmdCanvaskitSdk, + webPrecompiledAmdCanvaskitSdkSourcemaps, + webPrecompiledAmdCanvaskitAndHtmlSdk, + webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps, + webPrecompiledAmdSoundSdk, + webPrecompiledAmdSoundSdkSourcemaps, + webPrecompiledAmdCanvaskitSoundSdk, + webPrecompiledAmdCanvaskitSoundSdkSourcemaps, + webPrecompiledAmdCanvaskitAndHtmlSoundSdk, + webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps, + + /// The precompiled SDKs and sourcemaps for web debug builds with the DDC module system. + webPrecompiledDdcSdk, + webPrecompiledDdcSdkSourcemaps, + webPrecompiledDdcCanvaskitSdk, + webPrecompiledDdcCanvaskitSdkSourcemaps, + webPrecompiledDdcCanvaskitAndHtmlSdk, + webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps, + webPrecompiledDdcSoundSdk, + webPrecompiledDdcSoundSdkSourcemaps, + webPrecompiledDdcCanvaskitSoundSdk, + webPrecompiledDdcCanvaskitSoundSdkSourcemaps, + webPrecompiledDdcCanvaskitAndHtmlSoundSdk, + webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps, iosDeploy, idevicesyslog, @@ -256,19 +271,31 @@ String _hostArtifactToFileName(HostArtifact artifact, Platform platform) { return 'dart2js_platform.dill'; case HostArtifact.flutterWebLibrariesJson: return 'libraries.json'; - case HostArtifact.webPrecompiledSdk: - case HostArtifact.webPrecompiledCanvaskitSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: - case HostArtifact.webPrecompiledSoundSdk: - case HostArtifact.webPrecompiledCanvaskitSoundSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledAmdSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledAmdSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledDdcSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledDdcSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk: return 'dart_sdk.js'; - case HostArtifact.webPrecompiledSdkSourcemaps: - case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: - case HostArtifact.webPrecompiledSoundSdkSourcemaps: - case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps: + case HostArtifact.webPrecompiledDdcSdkSourcemaps: + case HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps: + case HostArtifact.webPrecompiledDdcSoundSdkSourcemaps: + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps: return 'dart_sdk.js.map'; case HostArtifact.impellerc: return 'impellerc$exe'; @@ -454,30 +481,54 @@ class CachedArtifacts implements Artifacts { case HostArtifact.webPlatformDart2JSSoundKernelDill: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledSdk: - case HostArtifact.webPrecompiledSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSdk: + case HostArtifact.webPrecompiledAmdSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitSdk: - case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledSoundSdk: - case HostArtifact.webPrecompiledSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSoundSdk: + case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitSoundSdk: - case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcSdk: + case HostArtifact.webPrecompiledDdcSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcSoundSdk: + case HostArtifact.webPrecompiledDdcSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); case HostArtifact.idevicesyslog: case HostArtifact.idevicescreenshot: final String artifactFileName = _hostArtifactToFileName(artifact, _platform); @@ -886,7 +937,9 @@ class CachedLocalEngineArtifacts implements Artifacts { final Artifacts _backupCache; @override - FileSystemEntity getHostArtifact(HostArtifact artifact) { + FileSystemEntity getHostArtifact( + HostArtifact artifact, + ) { switch (artifact) { case HostArtifact.flutterWebSdk: final String path = _getFlutterWebSdkPath(); @@ -906,30 +959,54 @@ class CachedLocalEngineArtifacts implements Artifacts { case HostArtifact.webPlatformDart2JSSoundKernelDill: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledSdk: - case HostArtifact.webPrecompiledSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSdk: + case HostArtifact.webPrecompiledAmdSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitSdk: - case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledSoundSdk: - case HostArtifact.webPrecompiledSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSoundSdk: + case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitSoundSdk: - case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcSdk: + case HostArtifact.webPrecompiledDdcSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcSoundSdk: + case HostArtifact.webPrecompiledDdcSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); case HostArtifact.idevicesyslog: case HostArtifact.idevicescreenshot: final String artifactFileName = _hostArtifactToFileName(artifact, _platform); @@ -1196,7 +1273,9 @@ class CachedLocalWebSdkArtifacts implements Artifacts { String getEngineType(TargetPlatform platform, [BuildMode? mode]) => _parent.getEngineType(platform, mode); @override - FileSystemEntity getHostArtifact(HostArtifact artifact) { + FileSystemEntity getHostArtifact( + HostArtifact artifact, + ) { switch (artifact) { case HostArtifact.flutterWebSdk: final String path = _getFlutterWebSdkPath(); @@ -1216,30 +1295,54 @@ class CachedLocalWebSdkArtifacts implements Artifacts { case HostArtifact.webPlatformDart2JSSoundKernelDill: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledSdk: - case HostArtifact.webPrecompiledSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSdk: + case HostArtifact.webPrecompiledAmdSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitSdk: - case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledSoundSdk: - case HostArtifact.webPrecompiledSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdSoundSdk: + case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitSoundSdk: - case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: - case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps: final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcSdk: + case HostArtifact.webPrecompiledDdcSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcSoundSdk: + case HostArtifact.webPrecompiledDdcSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk: + case HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps: + final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'ddc-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); + return _fileSystem.file(path); case HostArtifact.iosDeploy: case HostArtifact.idevicesyslog: case HostArtifact.idevicescreenshot: diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index ba1eace61ae..5d3c0d56f93 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -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]. /// @@ -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? 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; diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index b6ac161c989..d5b822fd678 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -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(); @@ -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') { @@ -227,6 +230,7 @@ class WebAssetServer implements AssetReader { modules, digests, nullSafetyMode, + ddcModuleSystem, webRenderer: webRenderer, ); if (testMode) { @@ -285,7 +289,8 @@ class WebAssetServer implements AssetReader { return chromium.chromeConnection; }, toolConfiguration: ToolConfiguration( - loadStrategy: FrontendServerRequireStrategyProvider( + loadStrategy: ddcModuleSystem + ? FrontendServerLegacyStrategyProvider( ReloadConfiguration.none, server, PackageUriMapper(packageConfig), @@ -293,8 +298,17 @@ class WebAssetServer implements AssetReader { 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, @@ -328,6 +342,7 @@ class WebAssetServer implements AssetReader { } final NullSafetyMode _nullSafetyMode; + final bool _ddcModuleSystem; final HttpServer _httpServer; final WebMemoryFS _webMemoryFS = WebMemoryFS(); final PackageConfig _packages; @@ -510,9 +525,7 @@ class WebAssetServer implements AssetReader { final WebRendererMode webRenderer; shelf.Response _serveIndex() { - final IndexHtml indexHtml = _getIndexHtml(); - final Map buildConfig = { 'engineRevision': globals.flutterVersion.engineRevision, 'builds': [ @@ -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> + 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> + dartSdkArtifactMap = _ddcModuleSystem + ? kDdcDartSdkJsMapArtifactMap + : kAmdDartSdkJsMapArtifactMap; + return globals.fs.file(globals.artifacts! + .getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!)); + } @override Future dartSourceContents(String serverPath) async { @@ -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; @@ -695,6 +716,7 @@ class WebDevFS implements DevFS { final bool enableDds; final Map extraHeaders; final bool testMode; + final bool ddcModuleSystem; final ExpressionCompiler? expressionCompiler; final ChromiumLauncher? chromiumLauncher; final bool nullAssertions; @@ -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!; @@ -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()); @@ -878,7 +906,14 @@ 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, @@ -886,7 +921,14 @@ class WebDevFS implements DevFS { ); 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, @@ -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, diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 7a62b7815e0..772d0b001ab 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -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 @@ -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(), @@ -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(); @@ -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); } @@ -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; @@ -651,8 +653,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). _connectionResult!.appConnection!.runMain(); } else { late StreamSubscription 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(); @@ -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 ⚠️', diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index 32ca9beedd9..b5a3508ef75 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -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), @@ -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> 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> 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( @@ -303,6 +316,11 @@ class FlutterWebPlatform extends PlatformPlugin { _requireJs.openRead(), headers: {'Content-Type': 'text/javascript'}, ); + } else if (request.requestedUri.path.contains('ddc_module_loader.js')) { + return shelf.Response.ok( + _ddcModuleLoaderJs.openRead(), + headers: {'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')) { diff --git a/packages/flutter_tools/lib/src/web/bootstrap.dart b/packages/flutter_tools/lib/src/web/bootstrap.dart index f7c80ec4d5a..f1a55f44978 100644 --- a/packages/flutter_tools/lib/src/web/bootstrap.dart +++ b/packages/flutter_tools/lib/src/web/bootstrap.dart @@ -4,6 +4,136 @@ import 'package:package_config/package_config.dart'; +String generateDDCBootstrapScript({ + required String entrypoint, + required String ddcModuleLoaderUrl, + required String mapperUrl, + required bool generateLoadingIndicator, + String appRootDirectory = '/', +}) { + return ''' +${generateLoadingIndicator ? _generateLoadingIndicator() : ""} +// TODO(markzipan): This is safe if Flutter app roots are always equal to the +// host root '/'. Validate if this is true. +var _currentDirectory = "$appRootDirectory"; + +window.\$dartCreateScript = (function() { + // Find the nonce value. (Note, this is only computed once.) + var scripts = Array.from(document.getElementsByTagName("script")); + var nonce; + scripts.some( + script => (nonce = script.nonce || script.getAttribute("nonce"))); + // If present, return a closure that automatically appends the nonce. + if (nonce) { + return function() { + var script = document.createElement("script"); + script.nonce = nonce; + return script; + }; + } else { + return function() { + return document.createElement("script"); + }; + } +})(); + +// Loads a module [relativeUrl] relative to [root]. +// +// If not specified, [root] defaults to the directory serving the main app. +var forceLoadModule = function (relativeUrl, root) { + var actualRoot = root ?? _currentDirectory; + return new Promise(function(resolve, reject) { + var script = self.\$dartCreateScript(); + let policy = { + createScriptURL: function(src) {return src;} + }; + if (self.trustedTypes && self.trustedTypes.createPolicy) { + policy = self.trustedTypes.createPolicy('dartDdcModuleUrl', policy); + } + script.onload = resolve; + script.onerror = reject; + script.src = policy.createScriptURL(actualRoot + relativeUrl); + document.head.appendChild(script); + }); +}; + +// A map containing the URLs for the bootstrap scripts in debug. +let _scriptUrls = { + "mapper": "$mapperUrl", + "moduleLoader": "$ddcModuleLoaderUrl" +}; + +(function() { + let appName = "$entrypoint"; + + // A uuid that identifies a subapp. + // Stubbed out since subapps aren't supported in Flutter. + let uuid = "00000000-0000-0000-0000-000000000000"; + + window.postMessage( + {type: "DDC_STATE_CHANGE", state: "initial_load", targetUuid: uuid}, "*"); + + // Load pre-requisite DDC scripts. + // We intentionally use invalid names to avoid namespace clashes. + let prerequisiteScripts = [ + { + "src": "$ddcModuleLoaderUrl", + "id": "ddc_module_loader \x00" + }, + { + "src": "$mapperUrl", + "id": "dart_stack_trace_mapper \x00" + } + ]; + + // Load ddc_module_loader.js to access DDC's module loader API. + let prerequisiteLoads = []; + for (let i = 0; i < prerequisiteScripts.length; i++) { + prerequisiteLoads.push(forceLoadModule(prerequisiteScripts[i].src)); + } + Promise.all(prerequisiteLoads).then((_) => afterPrerequisiteLogic()); + + // Save the current script so we can access it in a closure. + var _currentScript = document.currentScript; + + var afterPrerequisiteLogic = function() { + window.\$dartLoader.rootDirectories.push(_currentDirectory); + let scripts = [ + { + "src": "dart_sdk.js", + "id": "dart_sdk" + }, + { + "src": "main_module.bootstrap.js", + "id": "data-main" + } + ]; + let loadConfig = new window.\$dartLoader.LoadConfiguration(); + loadConfig.bootstrapScript = scripts[scripts.length - 1]; + + loadConfig.loadScriptFn = function(loader) { + loader.addScriptsToQueue(scripts, null); + loader.loadEnqueuedModules(); + } + loadConfig.ddcEventForLoadStart = /* LOAD_ALL_MODULES_START */ 1; + loadConfig.ddcEventForLoadedOk = /* LOAD_ALL_MODULES_END_OK */ 2; + loadConfig.ddcEventForLoadedError = /* LOAD_ALL_MODULES_END_ERROR */ 3; + + let loader = new window.\$dartLoader.DDCLoader(loadConfig); + + // Record prerequisite scripts' fully resolved URLs. + prerequisiteScripts.forEach(script => loader.registerScript(script)); + + // Note: these variables should only be used in non-multi-app scenarios since + // they can be arbitrarily overridden based on multi-app load order. + window.\$dartLoader.loadConfig = loadConfig; + window.\$dartLoader.loader = loader; + loader.nextAttempt(); + } +})(); +'''; +} + /// The JavaScript bootstrap script to support in-browser hot restart. /// /// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl] @@ -157,6 +287,39 @@ document.addEventListener('dart-app-ready', function (e) { '''; } +String generateDDCMainModule({ + required String entrypoint, + required bool nullAssertions, + required bool nativeNullAssertions, + String? exportedMain, +}) { + final String entrypointMainName = exportedMain ?? entrypoint.split('.')[0]; + // The typo below in "EXTENTION" is load-bearing, package:build depends on it. + return ''' +/* ENTRYPOINT_EXTENTION_MARKER */ + +(function() { + // Flutter Web uses a generated main entrypoint, which shares app and module names. + let appName = "$entrypoint"; + let moduleName = "$entrypoint"; + + // Use a dummy UUID since multi-apps are not supported on Flutter Web. + let uuid = "00000000-0000-0000-0000-000000000000"; + + let child = {}; + child.main = function() { + let dart = self.dart_library.import('dart_sdk', appName).dart; + dart.nonNullAsserts($nullAssertions); + dart.nativeNonNullAsserts($nativeNullAssertions); + self.dart_library.start(appName, uuid, moduleName, "$entrypointMainName"); + } + + /* MAIN_EXTENSION_MARKER */ + child.main(); +})(); +'''; +} + /// Generate a synthetic main module which captures the application's main /// method. /// diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 00d5a0337ed..27dedc063bd 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -219,35 +219,69 @@ enum WebRendererMode implements CliEnum { } } -/// The correct precompiled artifact to use for each build and render mode. -const Map> kDartSdkJsArtifactMap = >{ +/// The correct precompiled artifact to use for each build and render mode for DDC with AMD modules. +// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060. +const Map> kAmdDartSdkJsArtifactMap = >{ WebRendererMode.auto: { - NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk, - NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitAndHtmlSdk, + NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk, + NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk, }, WebRendererMode.canvaskit: { - NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitSoundSdk, - NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitSdk, + NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitSoundSdk, + NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitSdk, }, WebRendererMode.html: { - NullSafetyMode.sound: HostArtifact.webPrecompiledSoundSdk, - NullSafetyMode.unsound: HostArtifact.webPrecompiledSdk, + NullSafetyMode.sound: HostArtifact.webPrecompiledAmdSoundSdk, + NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdSdk, }, }; -/// The correct source map artifact to use for each build and render mode. -const Map> kDartSdkJsMapArtifactMap = >{ +/// The correct source map artifact to use for each build and render mode for DDC with AMD modules. +// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060. +const Map> kAmdDartSdkJsMapArtifactMap = >{ WebRendererMode.auto: { - NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps, - NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps, + NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps, + NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps, }, WebRendererMode.canvaskit: { - NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps, - NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitSdkSourcemaps, + NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps, + NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps, }, WebRendererMode.html: { - NullSafetyMode.sound: HostArtifact.webPrecompiledSoundSdkSourcemaps, - NullSafetyMode.unsound: HostArtifact.webPrecompiledSdkSourcemaps, + NullSafetyMode.sound: HostArtifact.webPrecompiledAmdSoundSdkSourcemaps, + NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdSdkSourcemaps, + }, +}; + +/// The correct precompiled artifact to use for each build and render mode for DDC with DDC modules. +const Map> kDdcDartSdkJsArtifactMap = >{ + WebRendererMode.auto: { + NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk, + NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk, + }, + WebRendererMode.canvaskit: { + NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitSoundSdk, + NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitSdk, + }, + WebRendererMode.html: { + NullSafetyMode.sound: HostArtifact.webPrecompiledDdcSoundSdk, + NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcSdk, + }, +}; + +/// The correct source map artifact to use for each build and render mode for DDC with DDC modules. +const Map> kDdcDartSdkJsMapArtifactMap = >{ + WebRendererMode.auto: { + NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps, + NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps, + }, + WebRendererMode.canvaskit: { + NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps, + NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps, + }, + WebRendererMode.html: { + NullSafetyMode.sound: HostArtifact.webPrecompiledDdcSoundSdkSourcemaps, + NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcSdkSourcemaps, }, }; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart index 54faa715085..9879ed21080 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart @@ -50,12 +50,18 @@ void main() { operatingSystemUtils = FakeOperatingSystemUtils(); for (final HostArtifact artifact in [ - HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk, - HostArtifact.webPrecompiledCanvaskitAndHtmlSdk, - HostArtifact.webPrecompiledCanvaskitSoundSdk, - HostArtifact.webPrecompiledCanvaskitSdk, - HostArtifact.webPrecompiledSoundSdk, - HostArtifact.webPrecompiledSdk, + HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk, + HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk, + HostArtifact.webPrecompiledAmdCanvaskitSoundSdk, + HostArtifact.webPrecompiledAmdCanvaskitSdk, + HostArtifact.webPrecompiledAmdSoundSdk, + HostArtifact.webPrecompiledAmdSdk, + HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk, + HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk, + HostArtifact.webPrecompiledDdcCanvaskitSoundSdk, + HostArtifact.webPrecompiledDdcCanvaskitSdk, + HostArtifact.webPrecompiledDdcSoundSdk, + HostArtifact.webPrecompiledDdcSdk, ]) { final File artifactFile = artifacts.getHostArtifact(artifact) as File; artifactFile.createSync(); @@ -63,7 +69,51 @@ void main() { } }); - testUsingContext('FlutterWebPlatform serves the correct dart_sdk.js for the passed web renderer', () async { + testUsingContext( + 'FlutterWebPlatform serves the correct dart_sdk.js (amd module system) for the passed web renderer', + () async { + final ChromiumLauncher chromiumLauncher = ChromiumLauncher( + fileSystem: fileSystem, + platform: platform, + processManager: processManager, + operatingSystemUtils: operatingSystemUtils, + browserFinder: (Platform platform, FileSystem filesystem) => 'chrome', + logger: logger, + ); + final MockServer server = MockServer(); + fileSystem.directory('/test').createSync(); + final FlutterWebPlatform webPlatform = await FlutterWebPlatform.start( + 'ProjectRoot', + buildInfo: const BuildInfo(BuildMode.debug, '', treeShakeIcons: false), + webMemoryFS: WebMemoryFS(), + fileSystem: fileSystem, + logger: logger, + chromiumLauncher: chromiumLauncher, + artifacts: artifacts, + processManager: processManager, + webRenderer: WebRendererMode.canvaskit, + serverFactory: () async => server, + testPackageUri: Uri.parse('test'), + ); + final shelf.Handler? handler = server.mountedHandler; + expect(handler, isNotNull); + handler!; + final shelf.Response response = await handler(shelf.Request( + 'GET', + Uri.parse('http://localhost/dart_sdk.js'), + )); + final String contents = await response.readAsString(); + expect(contents, HostArtifact.webPrecompiledAmdCanvaskitSoundSdk.name); + await webPlatform.close(); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Logger: () => logger, + }); + + testUsingContext( + 'FlutterWebPlatform serves the correct dart_sdk.js (ddc module system) for the passed web renderer', + () async { final ChromiumLauncher chromiumLauncher = ChromiumLauncher( fileSystem: fileSystem, platform: platform, @@ -79,7 +129,8 @@ void main() { buildInfo: const BuildInfo( BuildMode.debug, '', - treeShakeIcons: false + treeShakeIcons: false, + extraFrontEndOptions: ['--dartdevc-module-format=ddc'], ), webMemoryFS: WebMemoryFS(), fileSystem: fileSystem, @@ -99,7 +150,7 @@ void main() { Uri.parse('http://localhost/dart_sdk.js'), )); final String contents = await response.readAsString(); - expect(contents, HostArtifact.webPrecompiledCanvaskitSoundSdk.name); + expect(contents, HostArtifact.webPrecompiledDdcCanvaskitSoundSdk.name); await webPlatform.close(); }, overrides: { FileSystem: () => fileSystem, diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart index 5d174c6433a..b8312332629 100644 --- a/packages/flutter_tools/test/general.shard/artifacts_test.dart +++ b/packages/flutter_tools/test/general.shard/artifacts_test.dart @@ -148,41 +148,76 @@ void main() { ); }); - testWithoutContext('precompiled web artifact paths are correct', () { + testWithoutContext('Precompiled web AMD module system artifact paths are correct', () { expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledSdk).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSdk).path, 'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledSdkSourcemaps).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSdkSourcemaps).path, 'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js.map', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdk).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdk).path, 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdkSourcemaps).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps).path, 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js.map', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledSoundSdk).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdk).path, 'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledSoundSdkSourcemaps).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdkSourcemaps).path, 'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js.map', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdk).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdk).path, 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit-sound/dart_sdk.js', ); expect( - artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps).path, + artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps).path, 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit-sound/dart_sdk.js.map', ); }); + testWithoutContext('Precompiled web DDC module system artifact paths are correct', () { + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcSdk).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc/dart_sdk.js', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcSdkSourcemaps).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc/dart_sdk.js.map', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcCanvaskitSdk).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc-canvaskit/dart_sdk.js', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc-canvaskit/dart_sdk.js.map', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcSoundSdk).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc-sound/dart_sdk.js', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcSoundSdkSourcemaps).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc-sound/dart_sdk.js.map', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcCanvaskitSoundSdk).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc-canvaskit-sound/dart_sdk.js', + ); + expect( + artifacts.getHostArtifact(HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps).path, + 'root/bin/cache/flutter_web_sdk/kernel/ddc-canvaskit-sound/dart_sdk.js.map', + ); + }); + testWithoutContext('getEngineType', () { expect( artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), diff --git a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart index de5224143a7..65b05ca603a 100644 --- a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart +++ b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart @@ -170,4 +170,167 @@ void main() { expect(result, contains("Uri.parse('file:///test/absolute_path.dart')")); }); + + group('Using the DDC module system', () { + test('generateDDCBootstrapScript embeds urls correctly', () { + final String result = generateDDCBootstrapScript( + entrypoint: 'foo/bar/main.js', + ddcModuleLoaderUrl: 'ddc_module_loader.js', + mapperUrl: 'mapper.js', + generateLoadingIndicator: true, + ); + // ddc module loader js source is interpolated correctly. + expect(result, contains('"moduleLoader": "ddc_module_loader.js"')); + expect(result, contains('"src": "ddc_module_loader.js"')); + // stack trace mapper source is interpolated correctly. + expect(result, contains('"mapper": "mapper.js"')); + expect(result, contains('"src": "mapper.js"')); + // data-main is set to correct bootstrap module. + expect(result, contains('"src": "main_module.bootstrap.js"')); + expect(result, contains('"id": "data-main"')); + }); + + test('generateDDCBootstrapScript initializes configuration objects', () { + final String result = generateDDCBootstrapScript( + entrypoint: 'foo/bar/main.js', + ddcModuleLoaderUrl: 'ddc_module_loader.js', + mapperUrl: 'mapper.js', + generateLoadingIndicator: true, + ); + // LoadConfiguration and DDCLoader objects must be constructed. + expect(result, contains(r'new window.$dartLoader.LoadConfiguration(')); + expect(result, contains(r'new window.$dartLoader.DDCLoader(')); + // Specific fields must be set on the LoadConfiguration. + expect(result, contains('.bootstrapScript =')); + expect(result, contains('.loadScriptFn =')); + // DDCLoader.nextAttempt must be invoked to begin loading. + expect(result, contains('nextAttempt()')); + // Proper window objects are initialized. + expect(result, contains(r'window.$dartLoader.loadConfig =')); + expect(result, contains(r'window.$dartLoader.loader =')); + }); + + test('generateDDCBootstrapScript includes loading indicator', () { + final String result = generateDDCBootstrapScript( + entrypoint: 'foo/bar/main.js', + ddcModuleLoaderUrl: 'ddc_module_loader.js', + mapperUrl: 'mapper.js', + generateLoadingIndicator: true, + ); + expect(result, contains('"flutter-loader"')); + expect(result, contains('"indeterminate"')); + }); + + test('generateDDCBootstrapScript does not include loading indicator', () { + final String result = generateDDCBootstrapScript( + entrypoint: 'foo/bar/main.js', + ddcModuleLoaderUrl: 'ddc_module_loader.js', + mapperUrl: 'mapper.js', + generateLoadingIndicator: false, + ); + expect(result, isNot(contains('"flutter-loader"'))); + expect(result, isNot(contains('"indeterminate"'))); + }); + + // https://github.com/flutter/flutter/issues/107742 + test('generateDDCBootstrapScript loading indicator does not trigger scrollbars', () { + final String result = generateDDCBootstrapScript( + entrypoint: 'foo/bar/main.js', + ddcModuleLoaderUrl: 'ddc_module_loader.js', + mapperUrl: 'mapper.js', + generateLoadingIndicator: true, + ); + + // See: https://regexr.com/6q0ft + final RegExp regex = RegExp(r'(?:\.flutter-loader\s*\{)[^}]+(?:overflow\:\s*hidden;)[^}]+}'); + + expect(result, matches(regex), reason: '.flutter-loader must have overflow: hidden'); + }); + + test('generateDDCMainModule embeds the entrypoint correctly', () { + final String result = generateDDCMainModule( + entrypoint: 'main.js', + nullAssertions: false, + nativeNullAssertions: false, + ); + // bootstrap main module has correct defined module. + expect(result, contains('let appName = "main.js"')); + expect(result, contains('let moduleName = "main.js"')); + expect(result, contains('dart_library.start(appName, uuid, moduleName, "main");')); + }); + + test('generateDDCMainModule embeds its exported main correctly', () { + final String result = generateDDCMainModule( + entrypoint: 'foo/bar/main.js', + nullAssertions: false, + nativeNullAssertions: false, + exportedMain: 'foo__bar__main' + ); + // bootstrap main module has correct defined module. + expect(result, contains('let appName = "foo/bar/main.js"')); + expect(result, contains('let moduleName = "foo/bar/main.js"')); + expect(result, contains('dart_library.start(appName, uuid, moduleName, "foo__bar__main");')); + }); + + test('generateDDCMainModule includes null safety switches', () { + final String result = generateDDCMainModule( + entrypoint: 'main.js', + nullAssertions: true, + nativeNullAssertions: true, + ); + + expect(result, contains('''dart.nonNullAsserts(true);''')); + expect(result, contains('''dart.nativeNonNullAsserts(true);''')); + }); + + test('generateDDCMainModule can disable null safety switches', () { + final String result = generateDDCMainModule( + entrypoint: 'main.js', + nullAssertions: false, + nativeNullAssertions: false, + ); + + expect(result, contains('''dart.nonNullAsserts(false);''')); + expect(result, contains('''dart.nativeNonNullAsserts(false);''')); + }); + + test('generateTestBootstrapFileContents embeds urls correctly', () { + final String result = generateTestBootstrapFileContents('foo.dart.js', 'require.js', 'mapper.js'); + + expect(result, contains('el.setAttribute("data-main", \'foo.dart.js\');')); + }); + + test('generateTestEntrypoint does not generate test config wrappers when testConfigPath is not passed', () { + final String result = generateTestEntrypoint( + relativeTestPath: 'relative_path.dart', + absolutePath: 'absolute_path.dart', + testConfigPath: null, + languageVersion: LanguageVersion(2, 8), + ); + + expect(result, isNot(contains('test_config.testExecutable'))); + }); + + test('generateTestEntrypoint generates test config wrappers when testConfigPath is passed', () { + final String result = generateTestEntrypoint( + relativeTestPath: 'relative_path.dart', + absolutePath: 'absolute_path.dart', + testConfigPath: 'test_config_path.dart', + languageVersion: LanguageVersion(2, 8), + ); + + expect(result, contains('test_config.testExecutable')); + }); + + test('generateTestEntrypoint embeds urls correctly', () { + final String result = generateTestEntrypoint( + relativeTestPath: 'relative_path.dart', + absolutePath: '/test/absolute_path.dart', + testConfigPath: null, + languageVersion: LanguageVersion(2, 8), + ); + + expect(result, contains("Uri.parse('file:///test/absolute_path.dart')")); + }); + }); } diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart new file mode 100644 index 00000000000..1c95231e1db --- /dev/null +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart @@ -0,0 +1,1662 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io' hide Directory, File; + +import 'package:dwds/dwds.dart'; +import 'package:fake_async/fake_async.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart'; +import 'package:flutter_tools/src/compile.dart'; +import 'package:flutter_tools/src/convert.dart'; +import 'package:flutter_tools/src/devfs.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/html_utils.dart'; +import 'package:flutter_tools/src/isolated/devfs_web.dart'; +import 'package:flutter_tools/src/web/compile.dart'; +import 'package:logging/logging.dart' as logging; +import 'package:package_config/package_config.dart'; +import 'package:shelf/shelf.dart'; +import 'package:test/fake.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + +import '../../src/common.dart'; +import '../../src/testbed.dart'; + +const List kTransparentImage = [ + 0x89, + 0x50, + 0x4E, + 0x47, + 0x0D, + 0x0A, + 0x1A, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x49, + 0x48, + 0x44, + 0x52, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x01, + 0x08, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1F, + 0x15, + 0xC4, + 0x89, + 0x00, + 0x00, + 0x00, + 0x0A, + 0x49, + 0x44, + 0x41, + 0x54, + 0x78, + 0x9C, + 0x63, + 0x00, + 0x01, + 0x00, + 0x00, + 0x05, + 0x00, + 0x01, + 0x0D, + 0x0A, + 0x2D, + 0xB4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x49, + 0x45, + 0x4E, + 0x44, + 0xAE, +]; + +void main() { + late Testbed testbed; + late WebAssetServer webAssetServer; + late ReleaseAssetServer releaseAssetServer; + late Platform linux; + late PackageConfig packages; + late Platform windows; + late FakeHttpServer httpServer; + late BufferLogger logger; + const bool usesDdcModuleSystem = true; + + setUpAll(() async { + packages = PackageConfig([ + Package('flutter_tools', Uri.file('/flutter_tools/lib/').normalizePath()), + ]); + }); + + setUp(() { + httpServer = FakeHttpServer(); + linux = FakePlatform(environment: {}); + windows = FakePlatform( + operatingSystem: 'windows', environment: {}); + logger = BufferLogger.test(); + testbed = Testbed( + setup: () { + webAssetServer = WebAssetServer( + httpServer, + packages, + InternetAddress.loopbackIPv4, + {}, + {}, + NullSafetyMode.unsound, + usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + releaseAssetServer = ReleaseAssetServer( + globals.fs.file('main.dart').uri, + fileSystem: globals.fs, + flutterRoot: null, // ignore: avoid_redundant_argument_values + platform: FakePlatform(), + webBuildDirectory: null, // ignore: avoid_redundant_argument_values + ); + }, + overrides: { + Logger: () => logger, + }); + }); + + test( + '.log() reports warnings', + () => testbed.run(() { + const String unresolvedUriMessage = 'Unresolved uri:'; + const String otherMessage = 'Something bad happened'; + + final List events = [ + logging.LogRecord( + logging.Level.WARNING, + unresolvedUriMessage, + 'DartUri', + ), + logging.LogRecord( + logging.Level.WARNING, + otherMessage, + 'DartUri', + ), + ]; + + events.forEach(log); + expect(logger.warningText, contains(unresolvedUriMessage)); + expect(logger.warningText, contains(otherMessage)); + })); + + test( + 'Handles against malformed manifest', + () => testbed.run(() async { + final File source = globals.fs.file('source') + ..writeAsStringSync('main() {}'); + final File sourcemap = globals.fs.file('sourcemap') + ..writeAsStringSync('{}'); + final File metadata = globals.fs.file('metadata') + ..writeAsStringSync('{}'); + + // Missing ending offset. + final File manifestMissingOffset = globals.fs.file('manifestA') + ..writeAsStringSync(json.encode({ + '/foo.js': { + 'code': [0], + 'sourcemap': [0], + 'metadata': [0], + }, + })); + final File manifestOutOfBounds = globals.fs.file('manifest') + ..writeAsStringSync(json.encode({ + '/foo.js': { + 'code': [0, 100], + 'sourcemap': [0], + 'metadata': [0], + }, + })); + + expect( + webAssetServer.write( + source, manifestMissingOffset, sourcemap, metadata), + isEmpty); + expect( + webAssetServer.write( + source, manifestOutOfBounds, sourcemap, metadata), + isEmpty); + })); + + test( + 'serves JavaScript files from in memory cache', + () => testbed.run(() async { + final File source = globals.fs.file('source') + ..writeAsStringSync('main() {}'); + final File sourcemap = globals.fs.file('sourcemap') + ..writeAsStringSync('{}'); + final File metadata = globals.fs.file('metadata') + ..writeAsStringSync('{}'); + final File manifest = globals.fs.file('manifest') + ..writeAsStringSync(json.encode({ + '/foo.js': { + 'code': [0, source.lengthSync()], + 'sourcemap': [0, 2], + 'metadata': [0, 2], + }, + })); + webAssetServer.write(source, manifest, sourcemap, metadata); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.js'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair( + HttpHeaders.contentTypeHeader, 'application/javascript'), + containsPair(HttpHeaders.etagHeader, isNotNull), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + }, overrides: { + Platform: () => linux, + })); + + test( + 'serves metadata files from in memory cache', + () => testbed.run(() async { + const String metadataContents = '{"name":"foo"}'; + final File source = globals.fs.file('source') + ..writeAsStringSync('main() {}'); + final File sourcemap = globals.fs.file('sourcemap') + ..writeAsStringSync('{}'); + final File metadata = globals.fs.file('metadata') + ..writeAsStringSync(metadataContents); + final File manifest = globals.fs.file('manifest') + ..writeAsStringSync(json.encode({ + '/foo.js': { + 'code': [0, source.lengthSync()], + 'sourcemap': [0, sourcemap.lengthSync()], + 'metadata': [0, metadata.lengthSync()], + }, + })); + webAssetServer.write(source, manifest, sourcemap, metadata); + + final String? merged = await webAssetServer + .metadataContents('main_module.ddc_merged_metadata'); + expect(merged, equals(metadataContents)); + + final String? single = + await webAssetServer.metadataContents('foo.js.metadata'); + expect(single, equals(metadataContents)); + }, overrides: { + Platform: () => linux, + })); + + test( + 'Removes leading slashes for valid requests to avoid requesting outside' + ' of served directory', + () => testbed.run(() async { + globals.fs.file('foo.png').createSync(); + globals.fs.currentDirectory = + globals.fs.directory('project_directory')..createSync(); + + final File source = + globals.fs.file(globals.fs.path.join('web', 'foo.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar////foo.png'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair(HttpHeaders.contentTypeHeader, 'image/png'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'takes base path into account when serving', + () => testbed.run(() async { + webAssetServer.basePath = 'base/path'; + + globals.fs.file('foo.png').createSync(); + globals.fs.currentDirectory = + globals.fs.directory('project_directory')..createSync(); + + final File source = + globals.fs.file(globals.fs.path.join('web', 'foo.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/base/path/foo.png')), + ); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair(HttpHeaders.contentTypeHeader, 'image/png'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'serves index.html at the base path', + () => testbed.run(() async { + webAssetServer.basePath = 'base/path'; + + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/base/path/'))); + + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), htmlContent); + })); + + test( + 'serves index.html at / if href attribute is $kBaseHrefPlaceholder', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/'))); + + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), + htmlContent.replaceAll(kBaseHrefPlaceholder, '/')); + })); + + test( + 'does not serve outside the base path', + () => testbed.run(() async { + webAssetServer.basePath = 'base/path'; + + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/'))); + + expect(response.statusCode, HttpStatus.notFound); + })); + + test( + 'parses base path from index.html', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final WebAssetServer webAssetServer = WebAssetServer( + httpServer, + packages, + InternetAddress.loopbackIPv4, + {}, + {}, + NullSafetyMode.unsound, + usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + + expect(webAssetServer.basePath, 'foo/bar'); + })); + + test( + 'handles lack of base path in index.html', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final WebAssetServer webAssetServer = WebAssetServer( + httpServer, + packages, + InternetAddress.loopbackIPv4, + {}, + {}, + NullSafetyMode.unsound, + usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + + // Defaults to "/" when there's no base element. + expect(webAssetServer.basePath, ''); + })); + + test( + 'throws if base path is relative', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + expect( + () => WebAssetServer( + httpServer, + packages, + InternetAddress.loopbackIPv4, + {}, + {}, + NullSafetyMode.unsound, + usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ), + throwsToolExit(), + ); + })); + + test( + 'throws if base path does not end with slash', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + expect( + () => WebAssetServer( + httpServer, + packages, + InternetAddress.loopbackIPv4, + {}, + {}, + NullSafetyMode.unsound, + usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ), + throwsToolExit(), + ); + })); + + test( + 'serves JavaScript files from in memory cache not from manifest', + () => testbed.run(() async { + webAssetServer.writeFile('foo.js', 'main() {}'); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.js'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, '9'), + containsPair( + HttpHeaders.contentTypeHeader, 'application/javascript'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + utf8.encode('main() {}')); + })); + + test( + 'Returns notModified when the ifNoneMatch header matches the etag', + () => testbed.run(() async { + webAssetServer.writeFile('foo.js', 'main() {}'); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.js'))); + final String etag = response.headers[HttpHeaders.etagHeader]!; + + final Response cachedResponse = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.js'), + headers: { + HttpHeaders.ifNoneMatchHeader: etag, + })); + + expect(cachedResponse.statusCode, HttpStatus.notModified); + expect(await cachedResponse.read().toList(), isEmpty); + })); + + test( + 'serves index.html when path is unknown', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/bar/baz'))); + + expect(response.statusCode, HttpStatus.ok); + expect(await response.readAsString(), htmlContent); + })); + + test( + 'does not serve outside the base path', + () => testbed.run(() async { + webAssetServer.basePath = 'base/path'; + + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/'))); + + expect(response.statusCode, HttpStatus.notFound); + })); + + test( + 'does not serve index.html when path is inside assets or packages', + () => testbed.run(() async { + const String htmlContent = + ''; + final Directory webDir = + globals.fs.currentDirectory.childDirectory('web')..createSync(); + webDir.childFile('index.html').writeAsStringSync(htmlContent); + + Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/assets/foo/bar.png'))); + expect(response.statusCode, HttpStatus.notFound); + + response = await webAssetServer.handleRequest(Request( + 'GET', Uri.parse('http://foobar/packages/foo/bar.dart.js'))); + expect(response.statusCode, HttpStatus.notFound); + + webAssetServer.basePath = 'base/path'; + + response = await webAssetServer.handleRequest(Request('GET', + Uri.parse('http://foobar/base/path/assets/foo/bar.png'))); + expect(response.statusCode, HttpStatus.notFound); + + response = await webAssetServer.handleRequest(Request('GET', + Uri.parse('http://foobar/base/path/packages/foo/bar.dart.js'))); + expect(response.statusCode, HttpStatus.notFound); + })); + + test( + 'serves default index.html', + () => testbed.run(() async { + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/'))); + + expect(response.statusCode, HttpStatus.ok); + expect((await response.read().toList()).first, + containsAllInOrder(utf8.encode(''))); + })); + + test( + 'handles web server paths without .lib extension', + () => testbed.run(() async { + final File source = globals.fs.file('source') + ..writeAsStringSync('main() {}'); + final File sourcemap = globals.fs.file('sourcemap') + ..writeAsStringSync('{}'); + final File metadata = globals.fs.file('metadata') + ..writeAsStringSync('{}'); + final File manifest = globals.fs.file('manifest') + ..writeAsStringSync(json.encode({ + '/foo.dart.lib.js': { + 'code': [0, source.lengthSync()], + 'sourcemap': [0, 2], + 'metadata': [0, 2], + }, + })); + webAssetServer.write(source, manifest, sourcemap, metadata); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.dart.js'))); + + expect(response.statusCode, HttpStatus.ok); + })); + + test( + 'serves JavaScript files from in memory cache on Windows', + () => testbed.run(() async { + final File source = globals.fs.file('source') + ..writeAsStringSync('main() {}'); + final File sourcemap = globals.fs.file('sourcemap') + ..writeAsStringSync('{}'); + final File metadata = globals.fs.file('metadata') + ..writeAsStringSync('{}'); + final File manifest = globals.fs.file('manifest') + ..writeAsStringSync(json.encode({ + '/foo.js': { + 'code': [0, source.lengthSync()], + 'sourcemap': [0, 2], + 'metadata': [0, 2], + }, + })); + webAssetServer.write(source, manifest, sourcemap, metadata); + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://localhost/foo.js'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair( + HttpHeaders.contentTypeHeader, 'application/javascript'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + }, overrides: { + Platform: () => windows, + })); + + test( + 'serves asset files from in filesystem with url-encoded paths', + () => testbed.run(() async { + final File source = globals.fs.file(globals.fs.path + .join('build', 'flutter_assets', Uri.encodeFull('abcd象形字.png'))) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + final Response response = await webAssetServer.handleRequest(Request( + 'GET', + Uri.parse( + 'http://foobar/assets/abcd%25E8%25B1%25A1%25E5%25BD%25A2%25E5%25AD%2597.png'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair(HttpHeaders.contentTypeHeader, 'image/png'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + test( + 'serves files from web directory', + () => testbed.run(() async { + final File source = + globals.fs.file(globals.fs.path.join('web', 'foo.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.png'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair(HttpHeaders.contentTypeHeader, 'image/png'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'serves asset files from in filesystem with known mime type on Windows', + () => testbed.run(() async { + final File source = globals.fs.file( + globals.fs.path.join('build', 'flutter_assets', 'foo.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/assets/foo.png'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair(HttpHeaders.contentTypeHeader, 'image/png'), + containsPair(HttpHeaders.etagHeader, isNotNull), + containsPair(HttpHeaders.cacheControlHeader, + 'max-age=0, must-revalidate'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + }, overrides: { + Platform: () => windows, + })); + + test( + 'serves Dart files from in filesystem on Linux/macOS', + () => testbed.run(() async { + final File source = globals.fs.file('foo.dart').absolute + ..createSync(recursive: true) + ..writeAsStringSync('void main() {}'); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.dart'))); + + expect( + response.headers, + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString())); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + }, overrides: { + Platform: () => linux, + })); + + test( + 'serves asset files from in filesystem with known mime type', + () => testbed.run(() async { + final File source = globals.fs.file( + globals.fs.path.join('build', 'flutter_assets', 'foo.png')) + ..createSync(recursive: true) + ..writeAsBytesSync(kTransparentImage); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/assets/foo.png'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, + source.lengthSync().toString()), + containsPair(HttpHeaders.contentTypeHeader, 'image/png'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'serves asset files from in filesystem with known mime type and empty content', + () => testbed.run(() async { + final File source = globals.fs + .file(globals.fs.path.join('web', 'foo.js')) + ..createSync(recursive: true); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/foo.js'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, '0'), + containsPair( + HttpHeaders.contentTypeHeader, 'text/javascript'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'serves asset files from in filesystem with unknown mime type', + () => testbed.run(() async { + final File source = globals.fs + .file(globals.fs.path.join('build', 'flutter_assets', 'foo')) + ..createSync(recursive: true) + ..writeAsBytesSync(List.filled(100, 0)); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/assets/foo'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, '100'), + containsPair(HttpHeaders.contentTypeHeader, + 'application/octet-stream'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'serves valid etag header for asset files with non-ascii characters', + () => testbed.run(() async { + globals.fs + .file(globals.fs.path.join('build', 'flutter_assets', 'fooπ')) + ..createSync(recursive: true) + ..writeAsBytesSync([1, 2, 3]); + + final Response response = await webAssetServer.handleRequest( + Request('GET', Uri.parse('http://foobar/assets/fooπ'))); + final String etag = response.headers[HttpHeaders.etagHeader]!; + + expect( + etag.runes, everyElement(predicate((int char) => char < 255))); + })); + + test( + 'serves /packages// files as if they were ' + 'package:/ uris', + () => testbed.run(() async { + final Uri? expectedUri = + packages.resolve(Uri.parse('package:flutter_tools/foo.dart')); + final File source = + globals.fs.file(globals.fs.path.fromUri(expectedUri)) + ..createSync(recursive: true) + ..writeAsBytesSync([1, 2, 3]); + + final Response response = await webAssetServer.handleRequest( + Request('GET', + Uri.parse('http:///packages/flutter_tools/foo.dart'))); + + expect( + response.headers, + allOf([ + containsPair(HttpHeaders.contentLengthHeader, '3'), + containsPair(HttpHeaders.contentTypeHeader, 'text/x-dart'), + ])); + expect((await response.read().toList()).first, + source.readAsBytesSync()); + })); + + test( + 'calling dispose closes the http server', + () => testbed.run(() async { + await webAssetServer.dispose(); + + expect(httpServer.closed, true); + })); + + test( + 'Can start web server with specified DDC module system assets', + () => testbed.run(() async { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + outputFile.parent.childFile('a.metadata').writeAsStringSync('{}'); + + final ResidentCompiler residentCompiler = FakeResidentCompiler() + ..output = const CompilerOutput('a', 0, []); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + nullSafetyMode: NullSafetyMode.unsound, + ), + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullSafetyMode: NullSafetyMode.unsound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.html, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.flutterJs.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + final Uri uri = await webDevFS.create(); + webDevFS.webAssetServer.entrypointCacheDirectory = + globals.fs.currentDirectory; + final String webPrecompiledSdk = globals.artifacts! + .getHostArtifact(HostArtifact.webPrecompiledDdcSdk) + .path; + final String webPrecompiledSdkSourcemaps = globals.artifacts! + .getHostArtifact(HostArtifact.webPrecompiledDdcSdkSourcemaps) + .path; + final String webPrecompiledCanvaskitSdk = globals.artifacts! + .getHostArtifact(HostArtifact.webPrecompiledDdcCanvaskitSdk) + .path; + final String webPrecompiledCanvaskitSdkSourcemaps = globals + .artifacts! + .getHostArtifact( + HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps) + .path; + globals.fs.currentDirectory + .childDirectory('lib') + .childFile('web_entrypoint.dart') + ..createSync(recursive: true) + ..writeAsStringSync('GENERATED'); + globals.fs.file(webPrecompiledSdk) + ..createSync(recursive: true) + ..writeAsStringSync('HELLO'); + globals.fs.file(webPrecompiledSdkSourcemaps) + ..createSync(recursive: true) + ..writeAsStringSync('THERE'); + globals.fs.file(webPrecompiledCanvaskitSdk) + ..createSync(recursive: true) + ..writeAsStringSync('OL'); + globals.fs.file(webPrecompiledCanvaskitSdkSourcemaps) + ..createSync(recursive: true) + ..writeAsStringSync('CHUM'); + + await webDevFS.update( + mainUri: + globals.fs.file(globals.fs.path.join('lib', 'main.dart')).uri, + generator: residentCompiler, + trackWidgetCreation: true, + bundleFirstUpload: true, + invalidatedFiles: [], + packageConfig: PackageConfig.empty, + pathToReload: '', + dillOutputPath: 'out.dill', + shaderCompiler: const FakeShaderCompiler(), + ); + + expect(webDevFS.webAssetServer.getFile('ddc_module_loader.js'), + isNotNull); + expect(webDevFS.webAssetServer.getFile('stack_trace_mapper.js'), + isNotNull); + expect(webDevFS.webAssetServer.getFile('main.dart'), isNotNull); + expect(webDevFS.webAssetServer.getFile('manifest.json'), isNotNull); + expect(webDevFS.webAssetServer.getFile('flutter.js'), isNotNull); + expect(webDevFS.webAssetServer.getFile('flutter_service_worker.js'), + isNotNull); + expect(webDevFS.webAssetServer.getFile('version.json'), isNotNull); + expect( + await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), + 'HELLO'); + expect( + await webDevFS.webAssetServer + .dartSourceContents('dart_sdk.js.map'), + 'THERE'); + + // Update to the SDK. + globals.fs.file(webPrecompiledSdk).writeAsStringSync('BELLOW'); + + // New SDK should be visible.. + expect( + await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), + 'BELLOW'); + + // Generated entrypoint. + expect( + await webDevFS.webAssetServer + .dartSourceContents('web_entrypoint.dart'), + contains('GENERATED')); + + // served on localhost + expect(uri.host, 'localhost'); + + await webDevFS.destroy(); + }, overrides: { + Artifacts: () => Artifacts.test(), + })); + + test( + 'Can start web server with specified assets in sound null safety mode', + () => testbed.run(() async { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + outputFile.parent.childFile('a.metadata').writeAsStringSync('{}'); + + final ResidentCompiler residentCompiler = FakeResidentCompiler() + ..output = const CompilerOutput('a', 0, []); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + ), + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.html, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.flutterJs.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + final Uri uri = await webDevFS.create(); + webDevFS.webAssetServer.entrypointCacheDirectory = + globals.fs.currentDirectory; + globals.fs.currentDirectory + .childDirectory('lib') + .childFile('web_entrypoint.dart') + ..createSync(recursive: true) + ..writeAsStringSync('GENERATED'); + final String webPrecompiledSdk = globals.artifacts! + .getHostArtifact(HostArtifact.webPrecompiledDdcSoundSdk) + .path; + final String webPrecompiledSdkSourcemaps = globals.artifacts! + .getHostArtifact( + HostArtifact.webPrecompiledDdcSoundSdkSourcemaps) + .path; + final String webPrecompiledCanvaskitSdk = globals.artifacts! + .getHostArtifact( + HostArtifact.webPrecompiledDdcCanvaskitSoundSdk) + .path; + final String webPrecompiledCanvaskitSdkSourcemaps = globals + .artifacts! + .getHostArtifact( + HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps) + .path; + globals.fs.file(webPrecompiledSdk) + ..createSync(recursive: true) + ..writeAsStringSync('HELLO'); + globals.fs.file(webPrecompiledSdkSourcemaps) + ..createSync(recursive: true) + ..writeAsStringSync('THERE'); + globals.fs.file(webPrecompiledCanvaskitSdk) + ..createSync(recursive: true) + ..writeAsStringSync('OL'); + globals.fs.file(webPrecompiledCanvaskitSdkSourcemaps) + ..createSync(recursive: true) + ..writeAsStringSync('CHUM'); + + await webDevFS.update( + mainUri: + globals.fs.file(globals.fs.path.join('lib', 'main.dart')).uri, + generator: residentCompiler, + trackWidgetCreation: true, + bundleFirstUpload: true, + invalidatedFiles: [], + packageConfig: PackageConfig.empty, + pathToReload: '', + dillOutputPath: '', + shaderCompiler: const FakeShaderCompiler(), + ); + + expect(webDevFS.webAssetServer.getFile('ddc_module_loader.js'), + isNotNull); + expect(webDevFS.webAssetServer.getFile('stack_trace_mapper.js'), + isNotNull); + expect(webDevFS.webAssetServer.getFile('main.dart'), isNotNull); + expect(webDevFS.webAssetServer.getFile('manifest.json'), isNotNull); + expect(webDevFS.webAssetServer.getFile('flutter.js'), isNotNull); + expect(webDevFS.webAssetServer.getFile('flutter_service_worker.js'), + isNotNull); + expect(webDevFS.webAssetServer.getFile('version.json'), isNotNull); + expect( + await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), + 'HELLO'); + expect( + await webDevFS.webAssetServer + .dartSourceContents('dart_sdk.js.map'), + 'THERE'); + + // Update to the SDK. + globals.fs.file(webPrecompiledSdk).writeAsStringSync('BELLOW'); + + // New SDK should be visible.. + expect( + await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), + 'BELLOW'); + + // Generated entrypoint. + expect( + await webDevFS.webAssetServer + .dartSourceContents('web_entrypoint.dart'), + contains('GENERATED')); + + // served on localhost + expect(uri.host, 'localhost'); + + await webDevFS.destroy(); + }, overrides: { + Artifacts: () => Artifacts.test(), + })); + + test( + '.connect() will never call vmServiceFactory twice', + () => testbed.run(() async { + await FakeAsync().run>((FakeAsync time) { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + outputFile.parent.childFile('a.metadata').writeAsStringSync('{}'); + + final WebDevFS webDevFS = WebDevFS( + // if this is any other value, we will do a real ip lookup + hostname: 'any', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + ), + enableDwds: true, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: null, + extraHeaders: const {}, + chromiumLauncher: null, + nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + final FakeAppConnection firstConnection = FakeAppConnection(); + final FakeAppConnection secondConnection = FakeAppConnection(); + + final Future done = webDevFS.create().then((Uri _) { + // In non-test mode, webDevFS.create() would have initialized DWDS + webDevFS.webAssetServer.dwds = FakeDwds( + [firstConnection, secondConnection]); + + int vmServiceFactoryInvocationCount = 0; + Future vmServiceFactory(Uri uri, + {CompressionOptions? compression, required Logger logger}) { + if (vmServiceFactoryInvocationCount > 0) { + fail('Called vmServiceFactory twice!'); + } + vmServiceFactoryInvocationCount += 1; + return Future.delayed( + const Duration(seconds: 2), + () => FakeVmService(), + ); + } + + return webDevFS + .connect(false, vmServiceFactory: vmServiceFactory) + .then((ConnectionResult? firstConnectionResult) { + return webDevFS.destroy(); + }); + }); + time.elapse(const Duration(seconds: 1)); + time.elapse(const Duration(seconds: 2)); + return done; + }); + }, overrides: { + Artifacts: () => Artifacts.test(), + })); + + test( + 'Can start web server with hostname any', + () => testbed.run(() async { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'any', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + buildInfo: BuildInfo.debug, + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullAssertions: true, + nativeNullAssertions: true, + nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + final Uri uri = await webDevFS.create(); + + expect(uri.host, 'localhost'); + await webDevFS.destroy(); + })); + + test( + 'Can start web server with canvaskit enabled', + () => testbed.run(() async { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: const BuildInfo(BuildMode.debug, '', + treeShakeIcons: false, + dartDefines: [ + 'FLUTTER_WEB_USE_SKIA=true', + ]), + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + await webDevFS.create(); + + expect( + webDevFS.webAssetServer.webRenderer, WebRendererMode.canvaskit); + + await webDevFS.destroy(); + })); + + test( + 'Can start web server with auto detect enabled', + () => testbed.run(() async { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: const BuildInfo(BuildMode.debug, '', + treeShakeIcons: false, + dartDefines: [ + 'FLUTTER_WEB_AUTO_DETECT=true', + ]), + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.auto, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + await webDevFS.create(); + + expect(webDevFS.webAssetServer.webRenderer, WebRendererMode.auto); + + await webDevFS.destroy(); + })); + + test( + 'Can start web server with tls connection', + () => testbed.run(() async { + final String dataPath = globals.fs.path.join( + getFlutterRoot(), + 'packages', + 'flutter_tools', + 'test', + 'data', + 'asset_test', + ); + + final String dummyCertPath = + globals.fs.path.join(dataPath, 'tls_cert', 'dummy-cert.pem'); + final String dummyCertKeyPath = + globals.fs.path.join(dataPath, 'tls_cert', 'dummy-key.pem'); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + tlsCertPath: dummyCertPath, + tlsCertKeyPath: dummyCertKeyPath, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: BuildInfo.debug, + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullSafetyMode: NullSafetyMode.unsound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + final Uri uri = await webDevFS.create(); + + // Ensure the connection established is secure + expect(uri.scheme, 'https'); + + await webDevFS.destroy(); + }, overrides: { + Artifacts: () => Artifacts.test(), + })); + + test('allows frame embedding', () async { + final WebAssetServer webAssetServer = await WebAssetServer.start( + null, + 'localhost', + 0, + null, + null, + null, + true, + true, + true, + const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + ), + false, + false, + Uri.base, + null, + const {}, + NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, + testMode: true); + + expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null); + await webAssetServer.dispose(); + }); + + test('passes on extra headers', () async { + const String extraHeaderKey = 'hurray'; + const String extraHeaderValue = 'flutter'; + final WebAssetServer webAssetServer = await WebAssetServer.start( + null, + 'localhost', + 0, + null, + null, + null, + true, + true, + true, + const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false, + ), + false, + false, + Uri.base, + null, + const { + extraHeaderKey: extraHeaderValue, + }, + NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, + testMode: true); + + expect(webAssetServer.defaultResponseHeaders[extraHeaderKey], + [extraHeaderValue]); + + await webAssetServer.dispose(); + }); + + test( + 'WebAssetServer responds to POST requests with 404 not found', + () => testbed.run(() async { + final Response response = await webAssetServer.handleRequest( + Request('POST', Uri.parse('http://foobar/something')), + ); + expect(response.statusCode, 404); + })); + + test( + 'ReleaseAssetServer responds to POST requests with 404 not found', + () => testbed.run(() async { + final Response response = await releaseAssetServer.handle( + Request('POST', Uri.parse('http://foobar/something')), + ); + expect(response.statusCode, 404); + })); + + test( + 'WebAssetServer strips leading base href off of asset requests', + () => testbed.run(() async { + const String htmlContent = + ''; + globals.fs.currentDirectory + .childDirectory('web') + .childFile('index.html') + ..createSync(recursive: true) + ..writeAsStringSync(htmlContent); + final WebAssetServer webAssetServer = WebAssetServer( + FakeHttpServer(), + PackageConfig.empty, + InternetAddress.anyIPv4, + {}, + {}, + NullSafetyMode.sound, + usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + + expect( + await webAssetServer + .metadataContents('foo/main_module.ddc_merged_metadata'), + null); + // Not base href. + expect( + () async => webAssetServer + .metadataContents('bar/main_module.ddc_merged_metadata'), + throwsException); + })); + + test( + 'DevFS URI includes any specified base path.', + () => testbed.run(() async { + final File outputFile = globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + const String htmlContent = + ''; + globals.fs.currentDirectory + .childDirectory('web') + .childFile('index.html') + ..createSync(recursive: true) + ..writeAsStringSync(htmlContent); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + outputFile.parent.childFile('a.metadata').writeAsStringSync('{}'); + + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + tlsCertPath: null, + tlsCertKeyPath: null, + packagesFilePath: '.packages', + urlTunneller: null, // ignore: avoid_redundant_argument_values + useSseForDebugProxy: true, + useSseForDebugBackend: true, + useSseForInjectedClient: true, + nullAssertions: true, + nativeNullAssertions: true, + buildInfo: BuildInfo.debug, + enableDwds: false, + enableDds: false, + entrypoint: Uri.base, + testMode: true, + expressionCompiler: + null, // ignore: avoid_redundant_argument_values + extraHeaders: const {}, + chromiumLauncher: null, // ignore: avoid_redundant_argument_values + nullSafetyMode: NullSafetyMode.unsound, + ddcModuleSystem: usesDdcModuleSystem, + webRenderer: WebRendererMode.canvaskit, + ); + webDevFS.ddcModuleLoaderJS.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); + + final Uri uri = await webDevFS.create(); + + // served on localhost + expect(uri.host, 'localhost'); + // Matches base URI specified in html. + expect(uri.path, '/foo'); + + await webDevFS.destroy(); + }, overrides: { + Artifacts: () => Artifacts.test(), + })); +} + +class FakeHttpServer extends Fake implements HttpServer { + bool closed = false; + + @override + Future close({bool force = false}) async { + closed = true; + } +} + +class FakeResidentCompiler extends Fake implements ResidentCompiler { + CompilerOutput? output; + + @override + void addFileSystemRoot(String root) {} + + @override + Future recompile( + Uri mainUri, + List? invalidatedFiles, { + String? outputPath, + PackageConfig? packageConfig, + String? projectRootPath, + FileSystem? fs, + bool suppressErrors = false, + bool checkDartPluginRegistry = false, + File? dartPluginRegistrant, + Uri? nativeAssetsYaml, + }) async { + return output; + } +} + +class FakeShaderCompiler implements DevelopmentShaderCompiler { + const FakeShaderCompiler(); + + @override + void configureCompiler(TargetPlatform? platform) {} + + @override + Future recompileShader(DevFSContent inputShader) { + throw UnimplementedError(); + } +} + +class FakeDwds extends Fake implements Dwds { + FakeDwds(Iterable connectedAppsIterable) + : connectedApps = + Stream.fromIterable(connectedAppsIterable); + + @override + final Stream connectedApps; + + @override + Future debugConnection(AppConnection appConnection) => + Future.value(FakeDebugConnection()); +} + +class FakeAppConnection extends Fake implements AppConnection { + @override + void runMain() {} +} + +class FakeDebugConnection extends Fake implements DebugConnection { + FakeDebugConnection({ + this.uri = 'http://foo', + }); + + @override + final String uri; +} + +class FakeVmService extends Fake implements vm_service.VmService {} diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index 143282e76a2..9955b6c47e3 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -46,6 +46,7 @@ void main() { late Platform windows; late FakeHttpServer httpServer; late BufferLogger logger; + const bool usesDdcModuleSystem = false; setUpAll(() async { packages = PackageConfig([ @@ -66,6 +67,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); releaseAssetServer = ReleaseAssetServer( @@ -292,6 +294,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); @@ -312,6 +315,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); @@ -334,6 +338,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ), throwsToolExit(), @@ -355,6 +360,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ), throwsToolExit(), @@ -652,7 +658,7 @@ void main() { expect(httpServer.closed, true); })); - test('Can start web server with specified assets', () => testbed.run(() async { + test('Can start web server with specified AMD module system assets', () => testbed.run(() async { final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart')) ..createSync(recursive: true); outputFile.parent.childFile('a.sources').writeAsStringSync(''); @@ -689,6 +695,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.unsound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.html, ); webDevFS.requireJS.createSync(recursive: true); @@ -698,13 +705,13 @@ void main() { final Uri uri = await webDevFS.create(); webDevFS.webAssetServer.entrypointCacheDirectory = globals.fs.currentDirectory; final String webPrecompiledSdk = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledSdk).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdSdk).path; final String webPrecompiledSdkSourcemaps = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledSdkSourcemaps).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdSdkSourcemaps).path; final String webPrecompiledCanvaskitSdk = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdk).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdk).path; final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdkSourcemaps).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps).path; globals.fs.currentDirectory .childDirectory('lib') .childFile('web_entrypoint.dart') @@ -799,6 +806,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.html, ); webDevFS.requireJS.createSync(recursive: true); @@ -813,13 +821,13 @@ void main() { ..createSync(recursive: true) ..writeAsStringSync('GENERATED'); final String webPrecompiledSdk = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledSoundSdk).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdk).path; final String webPrecompiledSdkSourcemaps = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledSoundSdkSourcemaps).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdkSourcemaps).path; final String webPrecompiledCanvaskitSdk = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdk).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdk).path; final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts! - .getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps).path; + .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps).path; globals.fs.file(webPrecompiledSdk) ..createSync(recursive: true) ..writeAsStringSync('HELLO'); @@ -908,6 +916,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); @@ -970,6 +979,7 @@ void main() { nullAssertions: true, nativeNullAssertions: true, nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); @@ -1016,6 +1026,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); @@ -1063,6 +1074,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.auto, ); webDevFS.requireJS.createSync(recursive: true); @@ -1111,6 +1123,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.unsound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); @@ -1219,6 +1232,7 @@ void main() { {}, {}, NullSafetyMode.sound, + usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); @@ -1262,6 +1276,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.unsound, + ddcModuleSystem: usesDdcModuleSystem, webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true);