Unverified Commit ee94fe26 authored by MarkZ's avatar MarkZ Committed by GitHub

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
parent b781da9b
...@@ -94,19 +94,34 @@ enum HostArtifact { ...@@ -94,19 +94,34 @@ enum HostArtifact {
/// The summary dill with null safety enabled for the dartdevc target. /// The summary dill with null safety enabled for the dartdevc target.
webPlatformDart2JSSoundKernelDill, webPlatformDart2JSSoundKernelDill,
/// The precompiled SDKs and sourcemaps for web debug builds. /// The precompiled SDKs and sourcemaps for web debug builds with the AMD module system.
webPrecompiledSdk, // TODO(markzipan): delete these when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
webPrecompiledSdkSourcemaps, webPrecompiledAmdSdk,
webPrecompiledCanvaskitSdk, webPrecompiledAmdSdkSourcemaps,
webPrecompiledCanvaskitSdkSourcemaps, webPrecompiledAmdCanvaskitSdk,
webPrecompiledCanvaskitAndHtmlSdk, webPrecompiledAmdCanvaskitSdkSourcemaps,
webPrecompiledCanvaskitAndHtmlSdkSourcemaps, webPrecompiledAmdCanvaskitAndHtmlSdk,
webPrecompiledSoundSdk, webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps,
webPrecompiledSoundSdkSourcemaps, webPrecompiledAmdSoundSdk,
webPrecompiledCanvaskitSoundSdk, webPrecompiledAmdSoundSdkSourcemaps,
webPrecompiledCanvaskitSoundSdkSourcemaps, webPrecompiledAmdCanvaskitSoundSdk,
webPrecompiledCanvaskitAndHtmlSoundSdk, webPrecompiledAmdCanvaskitSoundSdkSourcemaps,
webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps, 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, iosDeploy,
idevicesyslog, idevicesyslog,
...@@ -256,19 +271,31 @@ String _hostArtifactToFileName(HostArtifact artifact, Platform platform) { ...@@ -256,19 +271,31 @@ String _hostArtifactToFileName(HostArtifact artifact, Platform platform) {
return 'dart2js_platform.dill'; return 'dart2js_platform.dill';
case HostArtifact.flutterWebLibrariesJson: case HostArtifact.flutterWebLibrariesJson:
return 'libraries.json'; return 'libraries.json';
case HostArtifact.webPrecompiledSdk: case HostArtifact.webPrecompiledAmdSdk:
case HostArtifact.webPrecompiledCanvaskitSdk: case HostArtifact.webPrecompiledAmdCanvaskitSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk:
case HostArtifact.webPrecompiledSoundSdk: case HostArtifact.webPrecompiledAmdSoundSdk:
case HostArtifact.webPrecompiledCanvaskitSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: 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'; return 'dart_sdk.js';
case HostArtifact.webPrecompiledSdkSourcemaps: case HostArtifact.webPrecompiledAmdSdkSourcemaps:
case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps:
case HostArtifact.webPrecompiledSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps:
case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: 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'; return 'dart_sdk.js.map';
case HostArtifact.impellerc: case HostArtifact.impellerc:
return 'impellerc$exe'; return 'impellerc$exe';
...@@ -454,30 +481,54 @@ class CachedArtifacts implements Artifacts { ...@@ -454,30 +481,54 @@ class CachedArtifacts implements Artifacts {
case HostArtifact.webPlatformDart2JSSoundKernelDill: case HostArtifact.webPlatformDart2JSSoundKernelDill:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledSdk: case HostArtifact.webPrecompiledAmdSdk:
case HostArtifact.webPrecompiledSdkSourcemaps: case HostArtifact.webPrecompiledAmdSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitSdk: case HostArtifact.webPrecompiledAmdCanvaskitSdk:
case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledSoundSdk: case HostArtifact.webPrecompiledAmdSoundSdk:
case HostArtifact.webPrecompiledSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk:
case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); 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.idevicesyslog:
case HostArtifact.idevicescreenshot: case HostArtifact.idevicescreenshot:
final String artifactFileName = _hostArtifactToFileName(artifact, _platform); final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
...@@ -886,7 +937,9 @@ class CachedLocalEngineArtifacts implements Artifacts { ...@@ -886,7 +937,9 @@ class CachedLocalEngineArtifacts implements Artifacts {
final Artifacts _backupCache; final Artifacts _backupCache;
@override @override
FileSystemEntity getHostArtifact(HostArtifact artifact) { FileSystemEntity getHostArtifact(
HostArtifact artifact,
) {
switch (artifact) { switch (artifact) {
case HostArtifact.flutterWebSdk: case HostArtifact.flutterWebSdk:
final String path = _getFlutterWebSdkPath(); final String path = _getFlutterWebSdkPath();
...@@ -906,30 +959,54 @@ class CachedLocalEngineArtifacts implements Artifacts { ...@@ -906,30 +959,54 @@ class CachedLocalEngineArtifacts implements Artifacts {
case HostArtifact.webPlatformDart2JSSoundKernelDill: case HostArtifact.webPlatformDart2JSSoundKernelDill:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledSdk: case HostArtifact.webPrecompiledAmdSdk:
case HostArtifact.webPrecompiledSdkSourcemaps: case HostArtifact.webPrecompiledAmdSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitSdk: case HostArtifact.webPrecompiledAmdCanvaskitSdk:
case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledSoundSdk: case HostArtifact.webPrecompiledAmdSoundSdk:
case HostArtifact.webPrecompiledSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk:
case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); 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.idevicesyslog:
case HostArtifact.idevicescreenshot: case HostArtifact.idevicescreenshot:
final String artifactFileName = _hostArtifactToFileName(artifact, _platform); final String artifactFileName = _hostArtifactToFileName(artifact, _platform);
...@@ -1196,7 +1273,9 @@ class CachedLocalWebSdkArtifacts implements Artifacts { ...@@ -1196,7 +1273,9 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
String getEngineType(TargetPlatform platform, [BuildMode? mode]) => _parent.getEngineType(platform, mode); String getEngineType(TargetPlatform platform, [BuildMode? mode]) => _parent.getEngineType(platform, mode);
@override @override
FileSystemEntity getHostArtifact(HostArtifact artifact) { FileSystemEntity getHostArtifact(
HostArtifact artifact,
) {
switch (artifact) { switch (artifact) {
case HostArtifact.flutterWebSdk: case HostArtifact.flutterWebSdk:
final String path = _getFlutterWebSdkPath(); final String path = _getFlutterWebSdkPath();
...@@ -1216,30 +1295,54 @@ class CachedLocalWebSdkArtifacts implements Artifacts { ...@@ -1216,30 +1295,54 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
case HostArtifact.webPlatformDart2JSSoundKernelDill: case HostArtifact.webPlatformDart2JSSoundKernelDill:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledSdk: case HostArtifact.webPrecompiledAmdSdk:
case HostArtifact.webPrecompiledSdkSourcemaps: case HostArtifact.webPrecompiledAmdSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitSdk: case HostArtifact.webPrecompiledAmdCanvaskitSdk:
case HostArtifact.webPrecompiledCanvaskitSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledSoundSdk: case HostArtifact.webPrecompiledAmdSoundSdk:
case HostArtifact.webPrecompiledSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdk:
case HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); return _fileSystem.file(path);
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk:
case HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps: case HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps:
final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform)); final String path = _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _hostArtifactToFileName(artifact, _platform));
return _fileSystem.file(path); 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.iosDeploy:
case HostArtifact.idevicesyslog: case HostArtifact.idevicesyslog:
case HostArtifact.idevicescreenshot: case HostArtifact.idevicescreenshot:
......
...@@ -222,6 +222,10 @@ class BuildInfo { ...@@ -222,6 +222,10 @@ class BuildInfo {
/// so the uncapitalized flavor name is used to compute the output file name /// so the uncapitalized flavor name is used to compute the output file name
String? get uncapitalizedFlavor => _uncapitalize(flavor); 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 /// Convert to a structured string encoded structure appropriate for usage
/// in build system [Environment.defines]. /// in build system [Environment.defines].
/// ///
...@@ -1044,6 +1048,28 @@ enum NullSafetyMode { ...@@ -1044,6 +1048,28 @@ enum NullSafetyMode {
autodetect, autodetect,
} }
/// Indicates the module system DDC is targeting.
enum DdcModuleFormat {
amd,
ddc,
}
// TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
DdcModuleFormat? _ddcModuleFormatFromFrontEndArgs(List<String>? extraFrontEndArgs) {
if (extraFrontEndArgs == null) {
return null;
}
const String ddcModuleFormatString = '--dartdevc-module-format=';
for (final String flag in extraFrontEndArgs) {
if (flag.startsWith(ddcModuleFormatString)) {
final String moduleFormatString = flag
.substring(ddcModuleFormatString.length, flag.length);
return DdcModuleFormat.values.byName(moduleFormatString);
}
}
return null;
}
String _getCurrentHostPlatformArchName() { String _getCurrentHostPlatformArchName() {
final HostPlatform hostPlatform = getCurrentHostPlatform(); final HostPlatform hostPlatform = getCurrentHostPlatform();
return hostPlatform.platformName; return hostPlatform.platformName;
......
...@@ -117,7 +117,8 @@ class WebAssetServer implements AssetReader { ...@@ -117,7 +117,8 @@ class WebAssetServer implements AssetReader {
this.internetAddress, this.internetAddress,
this._modules, this._modules,
this._digests, this._digests,
this._nullSafetyMode, { this._nullSafetyMode,
this._ddcModuleSystem, {
required this.webRenderer, required this.webRenderer,
}) : basePath = _getIndexHtml().getBaseHref(); }) : basePath = _getIndexHtml().getBaseHref();
...@@ -181,6 +182,8 @@ class WebAssetServer implements AssetReader { ...@@ -181,6 +182,8 @@ class WebAssetServer implements AssetReader {
required WebRendererMode webRenderer, required WebRendererMode webRenderer,
bool testMode = false, bool testMode = false,
DwdsLauncher dwdsLauncher = Dwds.start, DwdsLauncher dwdsLauncher = Dwds.start,
// TODO(markzipan): Make sure this default value aligns with that in the debugger options.
bool ddcModuleSystem = false,
}) async { }) async {
InternetAddress address; InternetAddress address;
if (hostname == 'any') { if (hostname == 'any') {
...@@ -227,6 +230,7 @@ class WebAssetServer implements AssetReader { ...@@ -227,6 +230,7 @@ class WebAssetServer implements AssetReader {
modules, modules,
digests, digests,
nullSafetyMode, nullSafetyMode,
ddcModuleSystem,
webRenderer: webRenderer, webRenderer: webRenderer,
); );
if (testMode) { if (testMode) {
...@@ -285,7 +289,8 @@ class WebAssetServer implements AssetReader { ...@@ -285,7 +289,8 @@ class WebAssetServer implements AssetReader {
return chromium.chromeConnection; return chromium.chromeConnection;
}, },
toolConfiguration: ToolConfiguration( toolConfiguration: ToolConfiguration(
loadStrategy: FrontendServerRequireStrategyProvider( loadStrategy: ddcModuleSystem
? FrontendServerLegacyStrategyProvider(
ReloadConfiguration.none, ReloadConfiguration.none,
server, server,
PackageUriMapper(packageConfig), PackageUriMapper(packageConfig),
...@@ -293,8 +298,17 @@ class WebAssetServer implements AssetReader { ...@@ -293,8 +298,17 @@ class WebAssetServer implements AssetReader {
BuildSettings( BuildSettings(
appEntrypoint: packageConfig.toPackageUri( appEntrypoint: packageConfig.toPackageUri(
globals.fs.file(entrypoint).absolute.uri, 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, ).strategy,
debugSettings: DebugSettings( debugSettings: DebugSettings(
enableDebugExtension: true, enableDebugExtension: true,
...@@ -328,6 +342,7 @@ class WebAssetServer implements AssetReader { ...@@ -328,6 +342,7 @@ class WebAssetServer implements AssetReader {
} }
final NullSafetyMode _nullSafetyMode; final NullSafetyMode _nullSafetyMode;
final bool _ddcModuleSystem;
final HttpServer _httpServer; final HttpServer _httpServer;
final WebMemoryFS _webMemoryFS = WebMemoryFS(); final WebMemoryFS _webMemoryFS = WebMemoryFS();
final PackageConfig _packages; final PackageConfig _packages;
...@@ -510,9 +525,7 @@ class WebAssetServer implements AssetReader { ...@@ -510,9 +525,7 @@ class WebAssetServer implements AssetReader {
final WebRendererMode webRenderer; final WebRendererMode webRenderer;
shelf.Response _serveIndex() { shelf.Response _serveIndex() {
final IndexHtml indexHtml = _getIndexHtml(); final IndexHtml indexHtml = _getIndexHtml();
final Map<String, dynamic> buildConfig = <String, dynamic>{ final Map<String, dynamic> buildConfig = <String, dynamic>{
'engineRevision': globals.flutterVersion.engineRevision, 'engineRevision': globals.flutterVersion.engineRevision,
'builds': <dynamic>[ 'builds': <dynamic>[
...@@ -597,15 +610,22 @@ class WebAssetServer implements AssetReader { ...@@ -597,15 +610,22 @@ class WebAssetServer implements AssetReader {
return webSdkFile; return webSdkFile;
} }
File get _resolveDartSdkJsFile => File get _resolveDartSdkJsFile {
globals.fs.file(globals.artifacts!.getHostArtifact( final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>>
kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]! dartSdkArtifactMap =
)); _ddcModuleSystem ? kDdcDartSdkJsArtifactMap : kAmdDartSdkJsArtifactMap;
return globals.fs.file(globals.artifacts!
.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
}
File get _resolveDartSdkJsMapFile => File get _resolveDartSdkJsMapFile {
globals.fs.file(globals.artifacts!.getHostArtifact( final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>>
kDartSdkJsMapArtifactMap[webRenderer]![_nullSafetyMode]! dartSdkArtifactMap = _ddcModuleSystem
)); ? kDdcDartSdkJsMapArtifactMap
: kAmdDartSdkJsMapArtifactMap;
return globals.fs.file(globals.artifacts!
.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
}
@override @override
Future<String?> dartSourceContents(String serverPath) async { Future<String?> dartSourceContents(String serverPath) async {
...@@ -679,6 +699,7 @@ class WebDevFS implements DevFS { ...@@ -679,6 +699,7 @@ class WebDevFS implements DevFS {
required this.nullAssertions, required this.nullAssertions,
required this.nativeNullAssertions, required this.nativeNullAssertions,
required this.nullSafetyMode, required this.nullSafetyMode,
required this.ddcModuleSystem,
required this.webRenderer, required this.webRenderer,
this.testMode = false, this.testMode = false,
}) : _port = port; }) : _port = port;
...@@ -695,6 +716,7 @@ class WebDevFS implements DevFS { ...@@ -695,6 +716,7 @@ class WebDevFS implements DevFS {
final bool enableDds; final bool enableDds;
final Map<String, String> extraHeaders; final Map<String, String> extraHeaders;
final bool testMode; final bool testMode;
final bool ddcModuleSystem;
final ExpressionCompiler? expressionCompiler; final ExpressionCompiler? expressionCompiler;
final ChromiumLauncher? chromiumLauncher; final ChromiumLauncher? chromiumLauncher;
final bool nullAssertions; final bool nullAssertions;
...@@ -805,15 +827,16 @@ class WebDevFS implements DevFS { ...@@ -805,15 +827,16 @@ class WebDevFS implements DevFS {
nullSafetyMode, nullSafetyMode,
webRenderer: webRenderer, webRenderer: webRenderer,
testMode: testMode, testMode: testMode,
ddcModuleSystem: ddcModuleSystem,
); );
final int selectedPort = webAssetServer.selectedPort; final int selectedPort = webAssetServer.selectedPort;
String url = '$hostname:$selectedPort'; String url = '$hostname:$selectedPort';
if (hostname == 'any') { if (hostname == 'any') {
url ='localhost:$selectedPort'; url = 'localhost:$selectedPort';
} }
_baseUri = Uri.http(url, webAssetServer.basePath); _baseUri = Uri.http(url, webAssetServer.basePath);
if (tlsCertPath != null && tlsCertKeyPath!= null) { if (tlsCertPath != null && tlsCertKeyPath != null) {
_baseUri = Uri.https(url, webAssetServer.basePath); _baseUri = Uri.https(url, webAssetServer.basePath);
} }
return _baseUri!; return _baseUri!;
...@@ -866,7 +889,12 @@ class WebDevFS implements DevFS { ...@@ -866,7 +889,12 @@ class WebDevFS implements DevFS {
generator.addFileSystemRoot(outputDirectoryPath); generator.addFileSystemRoot(outputDirectoryPath);
final String entrypoint = globals.fs.path.basename(mainFile.path); final String entrypoint = globals.fs.path.basename(mainFile.path);
webAssetServer.writeBytes(entrypoint, mainFile.readAsBytesSync()); 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('flutter.js', flutterJs.readAsBytesSync());
webAssetServer.writeBytes( webAssetServer.writeBytes(
'stack_trace_mapper.js', stackTraceMapper.readAsBytesSync()); 'stack_trace_mapper.js', stackTraceMapper.readAsBytesSync());
...@@ -878,7 +906,14 @@ class WebDevFS implements DevFS { ...@@ -878,7 +906,14 @@ class WebDevFS implements DevFS {
'version.json', FlutterProject.current().getVersionInfo()); 'version.json', FlutterProject.current().getVersionInfo());
webAssetServer.writeFile( webAssetServer.writeFile(
'main.dart.js', 'main.dart.js',
generateBootstrapScript( ddcModuleSystem
? generateDDCBootstrapScript(
entrypoint: entrypoint,
ddcModuleLoaderUrl: 'ddc_module_loader.js',
mapperUrl: 'stack_trace_mapper.js',
generateLoadingIndicator: enableDwds,
)
: generateBootstrapScript(
requireUrl: 'require.js', requireUrl: 'require.js',
mapperUrl: 'stack_trace_mapper.js', mapperUrl: 'stack_trace_mapper.js',
generateLoadingIndicator: enableDwds, generateLoadingIndicator: enableDwds,
...@@ -886,7 +921,14 @@ class WebDevFS implements DevFS { ...@@ -886,7 +921,14 @@ class WebDevFS implements DevFS {
); );
webAssetServer.writeFile( webAssetServer.writeFile(
'main_module.bootstrap.js', 'main_module.bootstrap.js',
generateMainModule( ddcModuleSystem
? generateDDCMainModule(
entrypoint: entrypoint,
nullAssertions: nullAssertions,
nativeNullAssertions: nativeNullAssertions,
exportedMain: pathToJSIdentifier(entrypoint.split('.')[0]),
)
: generateMainModule(
entrypoint: entrypoint, entrypoint: entrypoint,
nullAssertions: nullAssertions, nullAssertions: nullAssertions,
nativeNullAssertions: nativeNullAssertions, nativeNullAssertions: nativeNullAssertions,
...@@ -968,6 +1010,16 @@ class WebDevFS implements DevFS { ...@@ -968,6 +1010,16 @@ class WebDevFS implements DevFS {
'require.js', '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 @visibleForTesting
final File flutterJs = globals.fs.file(globals.fs.path.join( final File flutterJs = globals.fs.file(globals.fs.path.join(
globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path, globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,
......
...@@ -166,11 +166,12 @@ class ResidentWebRunner extends ResidentRunner { ...@@ -166,11 +166,12 @@ class ResidentWebRunner extends ResidentRunner {
if (_instance != null) { if (_instance != null) {
return _instance!; return _instance!;
} }
final vmservice.VmService? service =_connectionResult?.vmService; final vmservice.VmService? service = _connectionResult?.vmService;
final Uri websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri); final Uri websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
final Uri httpUri = _httpUriFromWebsocketUri(websocketUri); final Uri httpUri = _httpUriFromWebsocketUri(websocketUri);
return _instance ??= FlutterVmService(service!, wsAddress: websocketUri, httpAddress: httpUri); return _instance ??= FlutterVmService(service!, wsAddress: websocketUri, httpAddress: httpUri);
} }
FlutterVmService? _instance; FlutterVmService? _instance;
@override @override
...@@ -289,6 +290,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). ...@@ -289,6 +290,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
debuggingOptions.webEnableExpressionEvaluation debuggingOptions.webEnableExpressionEvaluation
? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem) ? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem)
: null; : null;
device!.devFS = WebDevFS( device!.devFS = WebDevFS(
hostname: debuggingOptions.hostname ?? 'localhost', hostname: debuggingOptions.hostname ?? 'localhost',
port: await getPort(), port: await getPort(),
...@@ -309,6 +311,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). ...@@ -309,6 +311,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
nullAssertions: debuggingOptions.nullAssertions, nullAssertions: debuggingOptions.nullAssertions,
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode, nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
nativeNullAssertions: debuggingOptions.nativeNullAssertions, nativeNullAssertions: debuggingOptions.nativeNullAssertions,
ddcModuleSystem: debuggingOptions.buildInfo.ddcModuleFormat == DdcModuleFormat.ddc,
webRenderer: debuggingOptions.webRenderer, webRenderer: debuggingOptions.webRenderer,
); );
Uri url = await device!.devFS!.create(); Uri url = await device!.devFS!.create();
...@@ -605,7 +608,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). ...@@ -605,7 +608,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
_connectionResult = await webDevFS.connect(useDebugExtension); _connectionResult = await webDevFS.connect(useDebugExtension);
unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit)); unawaited(_connectionResult!.debugConnection!.onDone.whenComplete(_cleanupAndExit));
void onLogEvent(vmservice.Event event) { void onLogEvent(vmservice.Event event) {
final String message = processVmServiceMessage(event); final String message = processVmServiceMessage(event);
_logger.printStatus(message); _logger.printStatus(message);
} }
...@@ -640,7 +643,6 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). ...@@ -640,7 +643,6 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
vmService: _vmService.service, vmService: _vmService.service,
); );
websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri); websocketUri = Uri.parse(_connectionResult!.debugConnection!.uri);
device!.vmService = _vmService; device!.vmService = _vmService;
...@@ -651,8 +653,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). ...@@ -651,8 +653,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
_connectionResult!.appConnection!.runMain(); _connectionResult!.appConnection!.runMain();
} else { } else {
late StreamSubscription<void> resumeSub; late StreamSubscription<void> resumeSub;
resumeSub = _vmService.service.onDebugEvent resumeSub = _vmService.service.onDebugEvent.listen((vmservice.Event event) {
.listen((vmservice.Event event) {
if (event.type == vmservice.EventKind.kResume) { if (event.type == vmservice.EventKind.kResume) {
_connectionResult!.appConnection!.runMain(); _connectionResult!.appConnection!.runMain();
resumeSub.cancel(); resumeSub.cancel();
...@@ -674,7 +675,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). ...@@ -674,7 +675,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
..writeAsStringSync(websocketUri.toString()); ..writeAsStringSync(websocketUri.toString());
} }
_logger.printStatus('Debug service listening on $websocketUri'); _logger.printStatus('Debug service listening on $websocketUri');
if (debuggingOptions.buildInfo.nullSafetyMode != NullSafetyMode.sound) { if (debuggingOptions.buildInfo.nullSafetyMode != NullSafetyMode.sound) {
_logger.printStatus(''); _logger.printStatus('');
_logger.printStatus( _logger.printStatus(
'Running without sound null safety ⚠️', 'Running without sound null safety ⚠️',
......
...@@ -236,6 +236,15 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -236,6 +236,15 @@ class FlutterWebPlatform extends PlatformPlugin {
'require.js', '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. /// The ddc to dart stack trace mapper.
File get _stackTraceMapper => _fileSystem.file(_fileSystem.path.join( File get _stackTraceMapper => _fileSystem.file(_fileSystem.path.join(
_artifacts!.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript), _artifacts!.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript),
...@@ -245,11 +254,15 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -245,11 +254,15 @@ class FlutterWebPlatform extends PlatformPlugin {
'dart_stack_trace_mapper.js', 'dart_stack_trace_mapper.js',
)); ));
File get _dartSdk => _fileSystem.file( File get _dartSdk {
_artifacts!.getHostArtifact(kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]!)); final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> dartSdkArtifactMap = buildInfo.ddcModuleFormat == DdcModuleFormat.ddc ? kDdcDartSdkJsArtifactMap : kAmdDartSdkJsArtifactMap;
return _fileSystem.file(_artifacts!.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
}
File get _dartSdkSourcemaps => _fileSystem.file( File get _dartSdkSourcemaps {
_artifacts!.getHostArtifact(kDartSdkJsMapArtifactMap[webRenderer]![_nullSafetyMode]!)); final Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> dartSdkArtifactMap = buildInfo.ddcModuleFormat == DdcModuleFormat.ddc ? kDdcDartSdkJsMapArtifactMap : kAmdDartSdkJsMapArtifactMap;
return _fileSystem.file(_artifacts!.getHostArtifact(dartSdkArtifactMap[webRenderer]![_nullSafetyMode]!));
}
File _canvasKitFile(String relativePath) { File _canvasKitFile(String relativePath) {
final String canvasKitPath = _fileSystem.path.join( final String canvasKitPath = _fileSystem.path.join(
...@@ -303,6 +316,11 @@ class FlutterWebPlatform extends PlatformPlugin { ...@@ -303,6 +316,11 @@ class FlutterWebPlatform extends PlatformPlugin {
_requireJs.openRead(), _requireJs.openRead(),
headers: <String, String>{'Content-Type': 'text/javascript'}, headers: <String, String>{'Content-Type': 'text/javascript'},
); );
} else if (request.requestedUri.path.contains('ddc_module_loader.js')) {
return shelf.Response.ok(
_ddcModuleLoaderJs.openRead(),
headers: <String, String>{'Content-Type': 'text/javascript'},
);
} else if (request.requestedUri.path.contains('ahem.ttf')) { } else if (request.requestedUri.path.contains('ahem.ttf')) {
return shelf.Response.ok(_ahem.openRead()); return shelf.Response.ok(_ahem.openRead());
} else if (request.requestedUri.path.contains('dart_sdk.js')) { } else if (request.requestedUri.path.contains('dart_sdk.js')) {
......
...@@ -4,6 +4,136 @@ ...@@ -4,6 +4,136 @@
import 'package:package_config/package_config.dart'; 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 JavaScript bootstrap script to support in-browser hot restart.
/// ///
/// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl] /// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl]
...@@ -157,6 +287,39 @@ document.addEventListener('dart-app-ready', function (e) { ...@@ -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 /// Generate a synthetic main module which captures the application's main
/// method. /// method.
/// ///
......
...@@ -219,35 +219,69 @@ enum WebRendererMode implements CliEnum { ...@@ -219,35 +219,69 @@ enum WebRendererMode implements CliEnum {
} }
} }
/// The correct precompiled artifact to use for each build and render mode. /// The correct precompiled artifact to use for each build and render mode for DDC with AMD modules.
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDartSdkJsArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{ // TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kAmdDartSdkJsArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{
WebRendererMode.auto: <NullSafetyMode, HostArtifact> { WebRendererMode.auto: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk, NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk,
NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitAndHtmlSdk, NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk,
}, },
WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> { WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitSoundSdk, NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitSoundSdk,
NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitSdk, NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitSdk,
}, },
WebRendererMode.html: <NullSafetyMode, HostArtifact> { WebRendererMode.html: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledSoundSdk, NullSafetyMode.sound: HostArtifact.webPrecompiledAmdSoundSdk,
NullSafetyMode.unsound: HostArtifact.webPrecompiledSdk, NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdSdk,
}, },
}; };
/// The correct source map artifact to use for each build and render mode. /// The correct source map artifact to use for each build and render mode for DDC with AMD modules.
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDartSdkJsMapArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{ // TODO(markzipan): delete this when DDC's AMD module system is deprecated, https://github.com/flutter/flutter/issues/142060.
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kAmdDartSdkJsMapArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{
WebRendererMode.auto: <NullSafetyMode, HostArtifact> { WebRendererMode.auto: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps, NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdkSourcemaps,
NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps, NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdkSourcemaps,
}, },
WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> { WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps, NullSafetyMode.sound: HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps,
NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitSdkSourcemaps, NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps,
}, },
WebRendererMode.html: <NullSafetyMode, HostArtifact> { WebRendererMode.html: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledSoundSdkSourcemaps, NullSafetyMode.sound: HostArtifact.webPrecompiledAmdSoundSdkSourcemaps,
NullSafetyMode.unsound: HostArtifact.webPrecompiledSdkSourcemaps, NullSafetyMode.unsound: HostArtifact.webPrecompiledAmdSdkSourcemaps,
},
};
/// The correct precompiled artifact to use for each build and render mode for DDC with DDC modules.
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDdcDartSdkJsArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{
WebRendererMode.auto: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk,
NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk,
},
WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitSoundSdk,
NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitSdk,
},
WebRendererMode.html: <NullSafetyMode, HostArtifact> {
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<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDdcDartSdkJsMapArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{
WebRendererMode.auto: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdkSourcemaps,
NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdkSourcemaps,
},
WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledDdcCanvaskitSoundSdkSourcemaps,
NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcCanvaskitSdkSourcemaps,
},
WebRendererMode.html: <NullSafetyMode, HostArtifact> {
NullSafetyMode.sound: HostArtifact.webPrecompiledDdcSoundSdkSourcemaps,
NullSafetyMode.unsound: HostArtifact.webPrecompiledDdcSdkSourcemaps,
}, },
}; };
......
...@@ -50,12 +50,18 @@ void main() { ...@@ -50,12 +50,18 @@ void main() {
operatingSystemUtils = FakeOperatingSystemUtils(); operatingSystemUtils = FakeOperatingSystemUtils();
for (final HostArtifact artifact in <HostArtifact>[ for (final HostArtifact artifact in <HostArtifact>[
HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk, HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSoundSdk,
HostArtifact.webPrecompiledCanvaskitAndHtmlSdk, HostArtifact.webPrecompiledAmdCanvaskitAndHtmlSdk,
HostArtifact.webPrecompiledCanvaskitSoundSdk, HostArtifact.webPrecompiledAmdCanvaskitSoundSdk,
HostArtifact.webPrecompiledCanvaskitSdk, HostArtifact.webPrecompiledAmdCanvaskitSdk,
HostArtifact.webPrecompiledSoundSdk, HostArtifact.webPrecompiledAmdSoundSdk,
HostArtifact.webPrecompiledSdk, HostArtifact.webPrecompiledAmdSdk,
HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSoundSdk,
HostArtifact.webPrecompiledDdcCanvaskitAndHtmlSdk,
HostArtifact.webPrecompiledDdcCanvaskitSoundSdk,
HostArtifact.webPrecompiledDdcCanvaskitSdk,
HostArtifact.webPrecompiledDdcSoundSdk,
HostArtifact.webPrecompiledDdcSdk,
]) { ]) {
final File artifactFile = artifacts.getHostArtifact(artifact) as File; final File artifactFile = artifacts.getHostArtifact(artifact) as File;
artifactFile.createSync(); artifactFile.createSync();
...@@ -63,7 +69,51 @@ void main() { ...@@ -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: <Type, Generator>{
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( final ChromiumLauncher chromiumLauncher = ChromiumLauncher(
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
...@@ -79,7 +129,8 @@ void main() { ...@@ -79,7 +129,8 @@ void main() {
buildInfo: const BuildInfo( buildInfo: const BuildInfo(
BuildMode.debug, BuildMode.debug,
'', '',
treeShakeIcons: false treeShakeIcons: false,
extraFrontEndOptions: <String>['--dartdevc-module-format=ddc'],
), ),
webMemoryFS: WebMemoryFS(), webMemoryFS: WebMemoryFS(),
fileSystem: fileSystem, fileSystem: fileSystem,
...@@ -99,7 +150,7 @@ void main() { ...@@ -99,7 +150,7 @@ void main() {
Uri.parse('http://localhost/dart_sdk.js'), Uri.parse('http://localhost/dart_sdk.js'),
)); ));
final String contents = await response.readAsString(); final String contents = await response.readAsString();
expect(contents, HostArtifact.webPrecompiledCanvaskitSoundSdk.name); expect(contents, HostArtifact.webPrecompiledDdcCanvaskitSoundSdk.name);
await webPlatform.close(); await webPlatform.close();
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
......
...@@ -148,41 +148,76 @@ void main() { ...@@ -148,41 +148,76 @@ void main() {
); );
}); });
testWithoutContext('precompiled web artifact paths are correct', () { testWithoutContext('Precompiled web AMD module system artifact paths are correct', () {
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledSdk).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSdk).path,
'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js', 'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js',
); );
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledSdkSourcemaps).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSdkSourcemaps).path,
'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js.map', 'root/bin/cache/flutter_web_sdk/kernel/amd/dart_sdk.js.map',
); );
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdk).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdk).path,
'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js', 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js',
); );
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdkSourcemaps).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps).path,
'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js.map', 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit/dart_sdk.js.map',
); );
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledSoundSdk).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdk).path,
'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js', 'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js',
); );
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledSoundSdkSourcemaps).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdkSourcemaps).path,
'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js.map', 'root/bin/cache/flutter_web_sdk/kernel/amd-sound/dart_sdk.js.map',
); );
expect( expect(
artifacts.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdk).path, artifacts.getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdk).path,
'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit-sound/dart_sdk.js', 'root/bin/cache/flutter_web_sdk/kernel/amd-canvaskit-sound/dart_sdk.js',
); );
expect( 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', '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', () { testWithoutContext('getEngineType', () {
expect( expect(
artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug),
......
...@@ -170,4 +170,167 @@ void main() { ...@@ -170,4 +170,167 @@ void main() {
expect(result, contains("Uri.parse('file:///test/absolute_path.dart')")); 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')"));
});
});
} }
// 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<int> kTransparentImage = <int>[
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>[
Package('flutter_tools', Uri.file('/flutter_tools/lib/').normalizePath()),
]);
});
setUp(() {
httpServer = FakeHttpServer();
linux = FakePlatform(environment: <String, String>{});
windows = FakePlatform(
operatingSystem: 'windows', environment: <String, String>{});
logger = BufferLogger.test();
testbed = Testbed(
setup: () {
webAssetServer = WebAssetServer(
httpServer,
packages,
InternetAddress.loopbackIPv4,
<String, String>{},
<String, String>{},
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: <Type, Generator>{
Logger: () => logger,
});
});
test(
'.log() reports warnings',
() => testbed.run(() {
const String unresolvedUriMessage = 'Unresolved uri:';
const String otherMessage = 'Something bad happened';
final List<logging.LogRecord> events = <logging.LogRecord>[
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(<String, Object>{
'/foo.js': <String, Object>{
'code': <int>[0],
'sourcemap': <int>[0],
'metadata': <int>[0],
},
}));
final File manifestOutOfBounds = globals.fs.file('manifest')
..writeAsStringSync(json.encode(<String, Object>{
'/foo.js': <String, Object>{
'code': <int>[0, 100],
'sourcemap': <int>[0],
'metadata': <int>[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(<String, Object>{
'/foo.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, 2],
'metadata': <int>[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(<Matcher>[
containsPair(HttpHeaders.contentLengthHeader,
source.lengthSync().toString()),
containsPair(
HttpHeaders.contentTypeHeader, 'application/javascript'),
containsPair(HttpHeaders.etagHeader, isNotNull),
]));
expect((await response.read().toList()).first,
source.readAsBytesSync());
}, overrides: <Type, Generator>{
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(<String, Object>{
'/foo.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, sourcemap.lengthSync()],
'metadata': <int>[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: <Type, Generator>{
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(<Matcher>[
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(<Matcher>[
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 =
'<html><head></head><body id="test"></body></html>';
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 =
'<html><head><base href ="$kBaseHrefPlaceholder"></head><body id="test"></body></html>';
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 =
'<html><head></head><body id="test"></body></html>';
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 =
'<html><head><base href="/foo/bar/"></head><body id="test"></body></html>';
final Directory webDir =
globals.fs.currentDirectory.childDirectory('web')..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
final WebAssetServer webAssetServer = WebAssetServer(
httpServer,
packages,
InternetAddress.loopbackIPv4,
<String, String>{},
<String, String>{},
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 =
'<html><head></head><body id="test"></body></html>';
final Directory webDir =
globals.fs.currentDirectory.childDirectory('web')..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
final WebAssetServer webAssetServer = WebAssetServer(
httpServer,
packages,
InternetAddress.loopbackIPv4,
<String, String>{},
<String, String>{},
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 =
'<html><head><base href="foo/bar/"></head><body id="test"></body></html>';
final Directory webDir =
globals.fs.currentDirectory.childDirectory('web')..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
expect(
() => WebAssetServer(
httpServer,
packages,
InternetAddress.loopbackIPv4,
<String, String>{},
<String, String>{},
NullSafetyMode.unsound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit,
),
throwsToolExit(),
);
}));
test(
'throws if base path does not end with slash',
() => testbed.run(() async {
const String htmlContent =
'<html><head><base href="/foo/bar"></head><body id="test"></body></html>';
final Directory webDir =
globals.fs.currentDirectory.childDirectory('web')..createSync();
webDir.childFile('index.html').writeAsStringSync(htmlContent);
expect(
() => WebAssetServer(
httpServer,
packages,
InternetAddress.loopbackIPv4,
<String, String>{},
<String, String>{},
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(<Matcher>[
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: <String, String>{
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 =
'<html><head></head><body id="test"></body></html>';
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 =
'<html><head></head><body id="test"></body></html>';
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 =
'<html><head></head><body id="test"></body></html>';
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('<html>')));
}));
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(<String, Object>{
'/foo.dart.lib.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, 2],
'metadata': <int>[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(<String, Object>{
'/foo.js': <String, Object>{
'code': <int>[0, source.lengthSync()],
'sourcemap': <int>[0, 2],
'metadata': <int>[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(<Matcher>[
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: <Type, Generator>{
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(<Matcher>[
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(<Matcher>[
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(<Matcher>[
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: <Type, Generator>{
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: <Type, Generator>{
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(<Matcher>[
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(<Matcher>[
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<int>.filled(100, 0));
final Response response = await webAssetServer.handleRequest(
Request('GET', Uri.parse('http://foobar/assets/foo')));
expect(
response.headers,
allOf(<Matcher>[
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(<int>[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/<package>/<path> files as if they were '
'package:<package>/<path> 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(<int>[1, 2, 3]);
final Response response = await webAssetServer.handleRequest(
Request('GET',
Uri.parse('http:///packages/flutter_tools/foo.dart')));
expect(
response.headers,
allOf(<Matcher>[
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, <Uri>[]);
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 <String, String>{},
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: <Uri>[],
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: <Type, Generator>{
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, <Uri>[]);
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 <String, String>{},
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: <Uri>[],
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: <Type, Generator>{
Artifacts: () => Artifacts.test(),
}));
test(
'.connect() will never call vmServiceFactory twice',
() => testbed.run(() async {
await FakeAsync().run<Future<void>>((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 <String, String>{},
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<void> done = webDevFS.create().then<void>((Uri _) {
// In non-test mode, webDevFS.create() would have initialized DWDS
webDevFS.webAssetServer.dwds = FakeDwds(
<AppConnection>[firstConnection, secondConnection]);
int vmServiceFactoryInvocationCount = 0;
Future<vm_service.VmService> vmServiceFactory(Uri uri,
{CompressionOptions? compression, required Logger logger}) {
if (vmServiceFactoryInvocationCount > 0) {
fail('Called vmServiceFactory twice!');
}
vmServiceFactoryInvocationCount += 1;
return Future<vm_service.VmService>.delayed(
const Duration(seconds: 2),
() => FakeVmService(),
);
}
return webDevFS
.connect(false, vmServiceFactory: vmServiceFactory)
.then<void>((ConnectionResult? firstConnectionResult) {
return webDevFS.destroy();
});
});
time.elapse(const Duration(seconds: 1));
time.elapse(const Duration(seconds: 2));
return done;
});
}, overrides: <Type, Generator>{
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 <String, String>{},
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: <String>[
'FLUTTER_WEB_USE_SKIA=true',
]),
enableDwds: false,
enableDds: false,
entrypoint: Uri.base,
testMode: true,
expressionCompiler:
null, // ignore: avoid_redundant_argument_values
extraHeaders: const <String, String>{},
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: <String>[
'FLUTTER_WEB_AUTO_DETECT=true',
]),
enableDwds: false,
enableDds: false,
entrypoint: Uri.base,
testMode: true,
expressionCompiler:
null, // ignore: avoid_redundant_argument_values
extraHeaders: const <String, String>{},
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 <String, String>{},
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: <Type, Generator>{
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 <String, String>{},
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 <String, String>{
extraHeaderKey: extraHeaderValue,
},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
testMode: true);
expect(webAssetServer.defaultResponseHeaders[extraHeaderKey],
<String>[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 =
'<html><head><base href="/foo/"></head><body id="test"></body></html>';
globals.fs.currentDirectory
.childDirectory('web')
.childFile('index.html')
..createSync(recursive: true)
..writeAsStringSync(htmlContent);
final WebAssetServer webAssetServer = WebAssetServer(
FakeHttpServer(),
PackageConfig.empty,
InternetAddress.anyIPv4,
<String, String>{},
<String, String>{},
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 =
'<html><head><base href="/foo/"></head><body id="test"></body></html>';
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 <String, String>{},
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: <Type, Generator>{
Artifacts: () => Artifacts.test(),
}));
}
class FakeHttpServer extends Fake implements HttpServer {
bool closed = false;
@override
Future<void> close({bool force = false}) async {
closed = true;
}
}
class FakeResidentCompiler extends Fake implements ResidentCompiler {
CompilerOutput? output;
@override
void addFileSystemRoot(String root) {}
@override
Future<CompilerOutput?> recompile(
Uri mainUri,
List<Uri>? 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<DevFSContent> recompileShader(DevFSContent inputShader) {
throw UnimplementedError();
}
}
class FakeDwds extends Fake implements Dwds {
FakeDwds(Iterable<AppConnection> connectedAppsIterable)
: connectedApps =
Stream<AppConnection>.fromIterable(connectedAppsIterable);
@override
final Stream<AppConnection> connectedApps;
@override
Future<DebugConnection> debugConnection(AppConnection appConnection) =>
Future<DebugConnection>.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 {}
...@@ -46,6 +46,7 @@ void main() { ...@@ -46,6 +46,7 @@ void main() {
late Platform windows; late Platform windows;
late FakeHttpServer httpServer; late FakeHttpServer httpServer;
late BufferLogger logger; late BufferLogger logger;
const bool usesDdcModuleSystem = false;
setUpAll(() async { setUpAll(() async {
packages = PackageConfig(<Package>[ packages = PackageConfig(<Package>[
...@@ -66,6 +67,7 @@ void main() { ...@@ -66,6 +67,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
releaseAssetServer = ReleaseAssetServer( releaseAssetServer = ReleaseAssetServer(
...@@ -292,6 +294,7 @@ void main() { ...@@ -292,6 +294,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
...@@ -312,6 +315,7 @@ void main() { ...@@ -312,6 +315,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
...@@ -334,6 +338,7 @@ void main() { ...@@ -334,6 +338,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
), ),
throwsToolExit(), throwsToolExit(),
...@@ -355,6 +360,7 @@ void main() { ...@@ -355,6 +360,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
), ),
throwsToolExit(), throwsToolExit(),
...@@ -652,7 +658,7 @@ void main() { ...@@ -652,7 +658,7 @@ void main() {
expect(httpServer.closed, true); 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')) final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
..createSync(recursive: true); ..createSync(recursive: true);
outputFile.parent.childFile('a.sources').writeAsStringSync(''); outputFile.parent.childFile('a.sources').writeAsStringSync('');
...@@ -689,6 +695,7 @@ void main() { ...@@ -689,6 +695,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html, webRenderer: WebRendererMode.html,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -698,13 +705,13 @@ void main() { ...@@ -698,13 +705,13 @@ void main() {
final Uri uri = await webDevFS.create(); final Uri uri = await webDevFS.create();
webDevFS.webAssetServer.entrypointCacheDirectory = globals.fs.currentDirectory; webDevFS.webAssetServer.entrypointCacheDirectory = globals.fs.currentDirectory;
final String webPrecompiledSdk = globals.artifacts! final String webPrecompiledSdk = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledSdk).path; .getHostArtifact(HostArtifact.webPrecompiledAmdSdk).path;
final String webPrecompiledSdkSourcemaps = globals.artifacts! final String webPrecompiledSdkSourcemaps = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledSdkSourcemaps).path; .getHostArtifact(HostArtifact.webPrecompiledAmdSdkSourcemaps).path;
final String webPrecompiledCanvaskitSdk = globals.artifacts! final String webPrecompiledCanvaskitSdk = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdk).path; .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdk).path;
final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts! final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSdkSourcemaps).path; .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSdkSourcemaps).path;
globals.fs.currentDirectory globals.fs.currentDirectory
.childDirectory('lib') .childDirectory('lib')
.childFile('web_entrypoint.dart') .childFile('web_entrypoint.dart')
...@@ -799,6 +806,7 @@ void main() { ...@@ -799,6 +806,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.html, webRenderer: WebRendererMode.html,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -813,13 +821,13 @@ void main() { ...@@ -813,13 +821,13 @@ void main() {
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('GENERATED'); ..writeAsStringSync('GENERATED');
final String webPrecompiledSdk = globals.artifacts! final String webPrecompiledSdk = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledSoundSdk).path; .getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdk).path;
final String webPrecompiledSdkSourcemaps = globals.artifacts! final String webPrecompiledSdkSourcemaps = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledSoundSdkSourcemaps).path; .getHostArtifact(HostArtifact.webPrecompiledAmdSoundSdkSourcemaps).path;
final String webPrecompiledCanvaskitSdk = globals.artifacts! final String webPrecompiledCanvaskitSdk = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdk).path; .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdk).path;
final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts! final String webPrecompiledCanvaskitSdkSourcemaps = globals.artifacts!
.getHostArtifact(HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps).path; .getHostArtifact(HostArtifact.webPrecompiledAmdCanvaskitSoundSdkSourcemaps).path;
globals.fs.file(webPrecompiledSdk) globals.fs.file(webPrecompiledSdk)
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('HELLO'); ..writeAsStringSync('HELLO');
...@@ -908,6 +916,7 @@ void main() { ...@@ -908,6 +916,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -970,6 +979,7 @@ void main() { ...@@ -970,6 +979,7 @@ void main() {
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true, nativeNullAssertions: true,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -1016,6 +1026,7 @@ void main() { ...@@ -1016,6 +1026,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -1063,6 +1074,7 @@ void main() { ...@@ -1063,6 +1074,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.auto, webRenderer: WebRendererMode.auto,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -1111,6 +1123,7 @@ void main() { ...@@ -1111,6 +1123,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
...@@ -1219,6 +1232,7 @@ void main() { ...@@ -1219,6 +1232,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.sound, NullSafetyMode.sound,
usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
...@@ -1262,6 +1276,7 @@ void main() { ...@@ -1262,6 +1276,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
ddcModuleSystem: usesDdcModuleSystem,
webRenderer: WebRendererMode.canvaskit, webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment