Plumbing flags required for running tests with the DDC module system. (#2295)

* Updating the legacy loader to use the new DDC module loader API

* Adding tests and plumbing for the DDC module system.

* updating changelog

* Restructuring asset bundling and flags

* Restructuring asset bundling and flags

* Updating test sdk paths

* Moving the module format specifier to an enum and unioning static assets across module formats

* Fixing analysis errors

* Exposing utilities/ddc_names.dart

* removing extraneous export

* Fixing race condition without extraneous plumbing.

* Extending tests and restructuring logic into helpers

* Extending sdk config tests and moving autorun functionality into the test context.

* Fixing analysis errors

* adding missing DDC sound sdks to asset generator test

* Fixing ddc and amd paths

* Keeping the first instance of an app connection and extending timeouts

* formatting files

* Exposing utilities/ddc_names.dart

* Loosen `vm_service` constraints and prepare DWDS for release to 23.1.1 (#2329)

* Reset DWDS to version `23.2.0-wip` after release (#2334)

* Adding changelog

---------

Co-authored-by: Elliott Brooks <21270878+elliette@users.noreply.github.com>
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index bedef0c..f0922af 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## 23.2.0-wip
 
 - Send untruncated `dart:developer` logs to debugging clients. - [#2333](https://github.com/dart-lang/webdev/pull/2333)
+- Enabling tests that run with the DDC module system and exposing `utilities/ddc_names.dart` - [#2295](https://github.com/dart-lang/webdev/pull/2295)
 
 ## 23.1.1
 
diff --git a/dwds/lib/dwds.dart b/dwds/lib/dwds.dart
index a901639..d0898d7 100644
--- a/dwds/lib/dwds.dart
+++ b/dwds/lib/dwds.dart
@@ -41,5 +41,6 @@
         CompilerOptions;
 export 'src/services/expression_compiler_service.dart'
     show ExpressionCompilerService;
+export 'src/utilities/ddc_names.dart';
 export 'src/utilities/sdk_configuration.dart'
     show SdkLayout, SdkConfiguration, SdkConfigurationProvider;
diff --git a/dwds/lib/expression_compiler.dart b/dwds/lib/expression_compiler.dart
index 19283a7..6748414 100644
--- a/dwds/lib/expression_compiler.dart
+++ b/dwds/lib/expression_compiler.dart
@@ -7,4 +7,5 @@
         ExpressionCompilationResult,
         ExpressionCompiler,
         CompilerOptions,
+        ModuleFormat,
         ModuleInfo;
diff --git a/dwds/lib/src/loaders/legacy.dart b/dwds/lib/src/loaders/legacy.dart
index bfaa503..fb35c09 100644
--- a/dwds/lib/src/loaders/legacy.dart
+++ b/dwds/lib/src/loaders/legacy.dart
@@ -181,7 +181,11 @@
     return '''
 $_baseUrlScript
 var scripts = ${const JsonEncoder.withIndent(" ").convert(scripts)};
-window.\$dartLoader.loadScripts(scripts);
+window.\$dartLoader.loadConfig.loadScriptFn = function(loader) {
+  loader.addScriptsToQueue(scripts, null);
+  loader.loadEnqueuedModules();
+};
+window.\$dartLoader.loader.nextAttempt();
 ''';
   }
 
diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart
index 3f9f458..9a6051c 100644
--- a/dwds/lib/src/services/chrome_proxy_service.dart
+++ b/dwds/lib/src/services/chrome_proxy_service.dart
@@ -187,7 +187,7 @@
         'with sound null safety: $soundNullSafety');
 
     final compilerOptions = CompilerOptions(
-      moduleFormat: moduleFormat,
+      moduleFormat: ModuleFormat.values.byName(moduleFormat),
       soundNullSafety: soundNullSafety,
       canaryFeatures: canaryFeatures,
       experiments: experiments,
diff --git a/dwds/lib/src/services/expression_compiler.dart b/dwds/lib/src/services/expression_compiler.dart
index 6135a70..c1b7306 100644
--- a/dwds/lib/src/services/expression_compiler.dart
+++ b/dwds/lib/src/services/expression_compiler.dart
@@ -4,7 +4,7 @@
 
 /// Options passed to DDC and the expression compiler.
 class CompilerOptions {
-  final String moduleFormat;
+  final ModuleFormat moduleFormat;
   final bool soundNullSafety;
   final bool canaryFeatures;
   final List<String> experiments;
@@ -17,6 +17,9 @@
   });
 }
 
+/// Indicates the module system DDC is targeting.
+enum ModuleFormat { amd, ddc, es6 }
+
 /// Result of compilation of dart expression to JavaScript
 class ExpressionCompilationResult {
   final bool isError;
diff --git a/dwds/lib/src/services/expression_compiler_service.dart b/dwds/lib/src/services/expression_compiler_service.dart
index 36a51f4..4865cb1 100644
--- a/dwds/lib/src/services/expression_compiler_service.dart
+++ b/dwds/lib/src/services/expression_compiler_service.dart
@@ -89,7 +89,7 @@
       '--asset-server-port',
       '$port',
       '--module-format',
-      compilerOptions.moduleFormat,
+      compilerOptions.moduleFormat.name,
       if (verbose) '--verbose',
       compilerOptions.soundNullSafety
           ? '--sound-null-safety'
diff --git a/dwds/lib/utilities.dart b/dwds/lib/utilities.dart
new file mode 100644
index 0000000..02e84d8
--- /dev/null
+++ b/dwds/lib/utilities.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+export 'src/utilities/ddc_names.dart';
diff --git a/dwds/test/devtools_test.dart b/dwds/test/devtools_test.dart
index b7e1ddf..8e9fb48 100644
--- a/dwds/test/devtools_test.dart
+++ b/dwds/test/devtools_test.dart
@@ -32,110 +32,115 @@
 
   final context = TestContext(TestProject.testWithSoundNullSafety, provider);
 
-  group('Injected client', () {
-    setUp(() async {
-      await context.setUp(
-        debugSettings: TestDebugSettings.withDevTools(context),
+  group(
+    'Injected client',
+    () {
+      setUp(() async {
+        await context.setUp(
+          debugSettings: TestDebugSettings.withDevTools(context),
+        );
+        await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
+        // Wait for DevTools to actually open.
+        await Future.delayed(const Duration(seconds: 2));
+      });
+
+      tearDown(() async {
+        await context.tearDown();
+      });
+
+      test(
+        'can launch devtools',
+        () async {
+          final windows = await context.webDriver.windows.toList();
+          await context.webDriver.driver.switchTo.window(windows.last);
+          expect(await context.webDriver.pageSource, contains('DevTools'));
+          expect(await context.webDriver.currentUrl, contains('ide=Dwds'));
+          // TODO(https://github.com/dart-lang/webdev/issues/1888): Re-enable.
+        },
+        skip: Platform.isWindows,
       );
-      await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
-      // Wait for DevTools to actually open.
-      await Future.delayed(const Duration(seconds: 2));
-    });
 
-    tearDown(() async {
-      await context.tearDown();
-    });
+      test('can not launch devtools for the same app in multiple tabs',
+          () async {
+        final appUrl = await context.webDriver.currentUrl;
+        // Open a new tab, select it, and navigate to the app
+        await context.webDriver.driver
+            .execute("window.open('$appUrl', '_blank');", []);
+        await Future.delayed(const Duration(seconds: 2));
+        final newAppWindow = await context.webDriver.windows.last;
+        await newAppWindow.setAsActive();
 
-    test(
-      'can launch devtools',
-      () async {
-        final windows = await context.webDriver.windows.toList();
-        await context.webDriver.driver.switchTo.window(windows.last);
-        expect(await context.webDriver.pageSource, contains('DevTools'));
-        expect(await context.webDriver.currentUrl, contains('ide=Dwds'));
-        // TODO(https://github.com/dart-lang/webdev/issues/1888): Re-enable.
-      },
-      skip: Platform.isWindows,
-    );
+        // Wait for the page to be ready before trying to open DevTools again.
+        await _waitForPageReady(context);
 
-    test('can not launch devtools for the same app in multiple tabs', () async {
-      final appUrl = await context.webDriver.currentUrl;
-      // Open a new tab, select it, and navigate to the app
-      await context.webDriver.driver
-          .execute("window.open('$appUrl', '_blank');", []);
-      await Future.delayed(const Duration(seconds: 2));
-      final newAppWindow = await context.webDriver.windows.last;
-      await newAppWindow.setAsActive();
+        // Try to open devtools and check for the alert.
+        await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
+        await Future.delayed(const Duration(seconds: 2));
+        final alert = context.webDriver.driver.switchTo.alert;
+        expect(alert, isNotNull);
+        expect(
+          await alert.text,
+          contains('This app is already being debugged in a different tab'),
+        );
+        await alert.accept();
 
-      // Wait for the page to be ready before trying to open DevTools again.
-      await _waitForPageReady(context);
-
-      // Try to open devtools and check for the alert.
-      await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
-      await Future.delayed(const Duration(seconds: 2));
-      final alert = context.webDriver.driver.switchTo.alert;
-      expect(alert, isNotNull);
-      expect(
-        await alert.text,
-        contains('This app is already being debugged in a different tab'),
-      );
-      await alert.accept();
-
-      var windows = await context.webDriver.windows.toList();
-      for (final window in windows) {
-        if (window.id != newAppWindow.id) {
-          await window.setAsActive();
-          await window.close();
+        var windows = await context.webDriver.windows.toList();
+        for (final window in windows) {
+          if (window.id != newAppWindow.id) {
+            await window.setAsActive();
+            await window.close();
+          }
         }
-      }
 
-      await newAppWindow.setAsActive();
-      await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
-      await Future.delayed(const Duration(seconds: 2));
-      windows = await context.webDriver.windows.toList();
-      final devToolsWindow =
-          windows.firstWhere((window) => window != newAppWindow);
-      await devToolsWindow.setAsActive();
-      expect(await context.webDriver.pageSource, contains('DevTools'));
-    });
+        await newAppWindow.setAsActive();
+        await context.webDriver.driver.keyboard.sendChord([Keyboard.alt, 'd']);
+        await Future.delayed(const Duration(seconds: 2));
+        windows = await context.webDriver.windows.toList();
+        final devToolsWindow =
+            windows.firstWhere((window) => window != newAppWindow);
+        await devToolsWindow.setAsActive();
+        expect(await context.webDriver.pageSource, contains('DevTools'));
+      });
 
-    test(
-      'destroys and recreates the isolate during a page refresh',
-      () async {
-        // This test is the same as one in reload_test, but runs here when there
-        // is a connected client (DevTools) since it can behave differently.
-        // https://github.com/dart-lang/webdev/pull/901#issuecomment-586438132
-        final client = context.debugConnection.vmService;
-        await client.streamListen('Isolate');
-        context.makeEditToDartEntryFile(
-          toReplace: 'Hello World!',
-          replaceWith: 'Bonjour le monde!',
-        );
-        await context.waitForSuccessfulBuild(propagateToBrowser: true);
+      test(
+        'destroys and recreates the isolate during a page refresh',
+        () async {
+          // This test is the same as one in reload_test, but runs here when there
+          // is a connected client (DevTools) since it can behave differently.
+          // https://github.com/dart-lang/webdev/pull/901#issuecomment-586438132
+          final client = context.debugConnection.vmService;
+          await client.streamListen('Isolate');
+          context.makeEditToDartEntryFile(
+            toReplace: 'Hello World!',
+            replaceWith: 'Bonjour le monde!',
+          );
+          await context.waitForSuccessfulBuild(propagateToBrowser: true);
 
-        final eventsDone = expectLater(
-          client.onIsolateEvent,
-          emitsThrough(
-            emitsInOrder([
-              _hasKind(EventKind.kIsolateExit),
-              _hasKind(EventKind.kIsolateStart),
-              _hasKind(EventKind.kIsolateRunnable),
-            ]),
-          ),
-        );
+          final eventsDone = expectLater(
+            client.onIsolateEvent,
+            emitsThrough(
+              emitsInOrder([
+                _hasKind(EventKind.kIsolateExit),
+                _hasKind(EventKind.kIsolateStart),
+                _hasKind(EventKind.kIsolateRunnable),
+              ]),
+            ),
+          );
 
-        await context.webDriver.driver.refresh();
+          await context.webDriver.driver.refresh();
 
-        await eventsDone;
-        // Re-set the edited file:
-        context.makeEditToDartEntryFile(
-          toReplace: 'Bonjour le monde!',
-          replaceWith: 'Hello World!',
-        );
-      },
-      skip: 'https://github.com/dart-lang/webdev/issues/1888',
-    );
-  });
+          await eventsDone;
+          // Re-set the edited file:
+          context.makeEditToDartEntryFile(
+            toReplace: 'Bonjour le monde!',
+            replaceWith: 'Hello World!',
+          );
+        },
+        skip: 'https://github.com/dart-lang/webdev/issues/1888',
+      );
+    },
+    timeout: Timeout.factor(2),
+  );
 
   group('Injected client without a DevTools server', () {
     setUp(() async {
@@ -193,6 +198,7 @@
     },
     tags: ['extension'],
     skip: 'https://github.com/dart-lang/webdev/issues/2114',
+    timeout: Timeout.factor(2),
   );
 }
 
diff --git a/dwds/test/evaluate_common.dart b/dwds/test/evaluate_common.dart
index 61025b5..7c3c9c9 100644
--- a/dwds/test/evaluate_common.dart
+++ b/dwds/test/evaluate_common.dart
@@ -71,6 +71,7 @@
         await context.setUp(
           testSettings: TestSettings(
             compilationMode: compilationMode,
+            moduleFormat: provider.ddcModuleFormat,
             enableExpressionEvaluation: true,
             useDebuggerModuleNames: useDebuggerModuleNames,
             verboseCompiler: debug,
@@ -821,6 +822,7 @@
       await context.setUp(
         testSettings: TestSettings(
           compilationMode: compilationMode,
+          moduleFormat: provider.ddcModuleFormat,
           enableExpressionEvaluation: false,
           verboseCompiler: debug,
         ),
diff --git a/dwds/test/expression_compiler_service_test.dart b/dwds/test/expression_compiler_service_test.dart
index d791aeb..0531764 100644
--- a/dwds/test/expression_compiler_service_test.dart
+++ b/dwds/test/expression_compiler_service_test.dart
@@ -80,7 +80,7 @@
       );
 
       final compilerOptions = CompilerOptions(
-        moduleFormat: 'amd',
+        moduleFormat: ModuleFormat.amd,
         soundNullSafety: true,
         canaryFeatures: false,
         experiments: const <String>[],
diff --git a/dwds/test/fixtures/context.dart b/dwds/test/fixtures/context.dart
index 3842aa0..34a45e7 100644
--- a/dwds/test/fixtures/context.dart
+++ b/dwds/test/fixtures/context.dart
@@ -2,6 +2,7 @@
 // for details. 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:convert';
 import 'dart:io';
 
@@ -9,15 +10,16 @@
 import 'package:build_daemon/data/build_status.dart';
 import 'package:build_daemon/data/build_target.dart';
 import 'package:dwds/asset_reader.dart';
-import 'package:dwds/expression_compiler.dart';
 import 'package:dwds/src/connections/app_connection.dart';
 import 'package:dwds/src/connections/debug_connection.dart';
 import 'package:dwds/src/debugging/webkit_debugger.dart';
 import 'package:dwds/src/loaders/build_runner_require.dart';
+import 'package:dwds/src/loaders/frontend_server_legacy.dart';
 import 'package:dwds/src/loaders/frontend_server_require.dart';
-import 'package:dwds/src/loaders/require.dart';
+import 'package:dwds/src/loaders/strategy.dart';
 import 'package:dwds/src/readers/proxy_server_asset_reader.dart';
 import 'package:dwds/src/services/chrome_proxy_service.dart';
+import 'package:dwds/src/services/expression_compiler.dart';
 import 'package:dwds/src/services/expression_compiler_service.dart';
 import 'package:dwds/src/utilities/dart_uri.dart';
 import 'package:dwds/src/utilities/server.dart';
@@ -204,7 +206,7 @@
       ExpressionCompiler? expressionCompiler;
       AssetReader assetReader;
       Stream<BuildResults> buildResults;
-      RequireStrategy requireStrategy;
+      LoadStrategy loadStrategy;
       String basePath = '';
       String filePathToServe = project.filePathToServe;
 
@@ -268,7 +270,7 @@
               expressionCompiler = ddcService;
             }
 
-            requireStrategy = BuildRunnerRequireStrategyProvider(
+            loadStrategy = BuildRunnerRequireStrategyProvider(
               assetHandler,
               testSettings.reloadConfiguration,
               assetReader,
@@ -301,6 +303,7 @@
               nullSafety: project.nullSafety,
               experiments: buildSettings.experiments,
               canaryFeatures: buildSettings.canaryFeatures,
+              moduleFormat: testSettings.moduleFormat,
             );
 
             _webRunner = ResidentWebRunner(
@@ -332,14 +335,25 @@
             basePath = webRunner.devFS.assetServer.basePath;
             assetReader = webRunner.devFS.assetServer;
             _assetHandler = webRunner.devFS.assetServer.handleRequest;
-            requireStrategy = FrontendServerRequireStrategyProvider(
-              testSettings.reloadConfiguration,
-              assetReader,
-              packageUriMapper,
-              () async => {},
-              buildSettings,
-            ).strategy;
-
+            loadStrategy = switch (testSettings.moduleFormat) {
+              ModuleFormat.amd => FrontendServerRequireStrategyProvider(
+                  testSettings.reloadConfiguration,
+                  assetReader,
+                  packageUriMapper,
+                  () async => {},
+                  buildSettings,
+                ).strategy,
+              ModuleFormat.ddc => FrontendServerLegacyStrategyProvider(
+                  testSettings.reloadConfiguration,
+                  assetReader,
+                  packageUriMapper,
+                  () async => {},
+                  buildSettings,
+                ).strategy,
+              _ => throw Exception(
+                  'Unsupported DDC module format ${testSettings.moduleFormat.name}.',
+                )
+            };
             buildResults = const Stream<BuildResults>.empty();
           }
           break;
@@ -380,6 +394,11 @@
           ),
         );
       }
+
+      // The debugger tab must be enabled and connected before certain
+      // listeners in DWDS or `main` is run.
+      final tabConnectionCompleter = Completer();
+      final appConnectionCompleter = Completer();
       final connection = ChromeConnection('localhost', debugPort);
 
       _testServer = await TestServer.start(
@@ -389,13 +408,27 @@
         port: port,
         assetHandler: assetHandler,
         assetReader: assetReader,
-        strategy: requireStrategy,
+        strategy: loadStrategy,
         target: project.directoryToServe,
         buildResults: buildResults,
         chromeConnection: () async => connection,
-        autoRun: testSettings.autoRun,
       );
 
+      _testServer!.dwds.connectedApps.listen((connection) async {
+        // Ensure that we've established a tab connection before running main.
+        await tabConnectionCompleter.future;
+        if (testSettings.autoRun) {
+          connection.runMain();
+        }
+
+        // We may reuse the app connection, so only save it the first time
+        // it's encountered.
+        if (!appConnectionCompleter.isCompleted) {
+          appConnection = connection;
+          appConnectionCompleter.complete();
+        }
+      });
+
       _appUrl = basePath.isEmpty
           ? 'http://localhost:$port/$filePathToServe'
           : 'http://localhost:$port/$basePath/$filePathToServe';
@@ -406,7 +439,9 @@
         if (tab != null) {
           _tabConnection = await tab.connect();
           await tabConnection.runtime.enable();
-          await tabConnection.debugger.enable();
+          await tabConnection.debugger
+              .enable()
+              .then((_) => tabConnectionCompleter.complete());
         } else {
           throw StateError('Unable to connect to tab.');
         }
@@ -417,10 +452,13 @@
           await extensionConnection.runtime.enable();
         }
 
-        appConnection = await testServer.dwds.connectedApps.first;
+        await appConnectionCompleter.future;
         if (debugSettings.enableDebugging && !testSettings.waitToDebug) {
           await startDebugging();
         }
+      } else {
+        // No tab needs to be dicovered, so fulfill the relevant completer.
+        tabConnectionCompleter.complete();
       }
     } catch (e, s) {
       _logger.severe('Failed to setup the service, $e:$s');
diff --git a/dwds/test/fixtures/server.dart b/dwds/test/fixtures/server.dart
index 88562e7..9de66c3 100644
--- a/dwds/test/fixtures/server.dart
+++ b/dwds/test/fixtures/server.dart
@@ -9,7 +9,7 @@
 import 'package:dwds/dart_web_debug_service.dart';
 import 'package:dwds/data/build_result.dart';
 import 'package:dwds/src/config/tool_configuration.dart';
-import 'package:dwds/src/loaders/require.dart';
+import 'package:dwds/src/loaders/strategy.dart';
 import 'package:dwds/src/utilities/server.dart';
 import 'package:logging/logging.dart';
 import 'package:shelf/shelf.dart';
@@ -39,15 +39,8 @@
     this._server,
     this.dwds,
     this.buildResults,
-    bool autoRun,
     this.assetReader,
-  ) {
-    if (autoRun) {
-      dwds.connectedApps.listen((connection) {
-        connection.runMain();
-      });
-    }
-  }
+  );
 
   String get host => _server.address.host;
   int get port => _server.port;
@@ -62,11 +55,10 @@
     required AppMetadata appMetadata,
     required Handler assetHandler,
     required AssetReader assetReader,
-    required RequireStrategy strategy,
+    required LoadStrategy strategy,
     required String target,
     required Stream<daemon.BuildResults> buildResults,
     required Future<ChromeConnection> Function() chromeConnection,
-    required bool autoRun,
     int? port,
   }) async {
     var pipeline = const Pipeline();
@@ -121,7 +113,6 @@
       server,
       dwds,
       filteredBuildResults,
-      autoRun,
       assetReader,
     );
   }
diff --git a/dwds/test/fixtures/utilities.dart b/dwds/test/fixtures/utilities.dart
index 5c722e0..45c508a 100644
--- a/dwds/test/fixtures/utilities.dart
+++ b/dwds/test/fixtures/utilities.dart
@@ -242,6 +242,7 @@
 
   // Build settings.
   final CompilationMode compilationMode;
+  final ModuleFormat moduleFormat;
   final bool canaryFeatures;
   final bool isFlutterApp;
   final List<String> experiments;
@@ -255,6 +256,7 @@
     this.verboseCompiler = false,
     this.launchChrome = true,
     this.compilationMode = CompilationMode.buildDaemon,
+    this.moduleFormat = ModuleFormat.amd,
     this.canaryFeatures = false,
     this.isFlutterApp = false,
     this.experiments = const <String>[],
@@ -296,8 +298,8 @@
     required NullSafety nullSafety,
     required super.canaryFeatures,
     required List<String> experiments,
+    super.moduleFormat = ModuleFormat.amd,
   }) : super(
-          moduleFormat: 'amd',
           soundNullSafety: nullSafety == NullSafety.sound,
           experiments: const <String>[],
         );
diff --git a/dwds/test/frontend_server_ddc_evaluate_sound_test.dart b/dwds/test/frontend_server_ddc_evaluate_sound_test.dart
new file mode 100644
index 0000000..8506896
--- /dev/null
+++ b/dwds/test/frontend_server_ddc_evaluate_sound_test.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@Tags(['daily'])
+@TestOn('vm')
+@Timeout(Duration(minutes: 5))
+
+import 'dart:io';
+
+import 'package:dwds/expression_compiler.dart';
+import 'package:test/test.dart';
+import 'package:test_common/test_sdk_configuration.dart';
+
+import 'evaluate_common.dart';
+import 'fixtures/context.dart';
+import 'fixtures/project.dart';
+
+void main() async {
+  // Enable verbose logging for debugging.
+  final debug = false;
+
+  final provider = TestSdkConfigurationProvider(
+    verbose: debug,
+    ddcModuleFormat: ModuleFormat.ddc,
+  );
+  tearDownAll(provider.dispose);
+
+  for (var useDebuggerModuleNames in [false, true]) {
+    group('Debugger module names: $useDebuggerModuleNames |', () {
+      final nullSafety = NullSafety.sound;
+      group('${nullSafety.name} null safety | DDC module system |', () {
+        for (var indexBaseMode in IndexBaseMode.values) {
+          group(
+            'with ${indexBaseMode.name} |',
+            () {
+              testAll(
+                provider: provider,
+                compilationMode: CompilationMode.frontendServer,
+                indexBaseMode: indexBaseMode,
+                nullSafety: nullSafety,
+                useDebuggerModuleNames: useDebuggerModuleNames,
+                debug: debug,
+              );
+            },
+            // https://github.com/dart-lang/sdk/issues/49277
+            skip: indexBaseMode == IndexBaseMode.base && Platform.isWindows,
+          );
+        }
+      });
+    });
+  }
+}
diff --git a/dwds/test/reload_test.dart b/dwds/test/reload_test.dart
index 6c425ea..8a0d8eb 100644
--- a/dwds/test/reload_test.dart
+++ b/dwds/test/reload_test.dart
@@ -42,71 +42,104 @@
     );
   }
 
-  group('Injected client with live reload', () {
-    group('and with debugging', () {
+  group(
+    'Injected client with live reload',
+    () {
+      group('and with debugging', () {
+        setUp(() async {
+          setCurrentLogWriter(debug: debug);
+          await context.setUp(
+            testSettings: TestSettings(
+              reloadConfiguration: ReloadConfiguration.liveReload,
+            ),
+          );
+        });
+
+        tearDown(() async {
+          undoEdit();
+          await context.tearDown();
+        });
+
+        test('can live reload changes ', () async {
+          await makeEditAndWaitForRebuild();
+          final source = await context.webDriver.pageSource;
+
+          // A full reload should clear the state.
+          expect(source.contains(originalString), isFalse);
+          expect(source.contains(newString), isTrue);
+        });
+      });
+
+      group('and without debugging', () {
+        setUp(() async {
+          setCurrentLogWriter(debug: debug);
+          await context.setUp(
+            testSettings: TestSettings(
+              reloadConfiguration: ReloadConfiguration.liveReload,
+            ),
+            debugSettings: TestDebugSettings.noDevTools().copyWith(
+              enableDebugging: false,
+            ),
+          );
+        });
+
+        tearDown(() async {
+          undoEdit();
+          await context.tearDown();
+        });
+
+        test('can live reload changes ', () async {
+          await makeEditAndWaitForRebuild();
+
+          final source = await context.webDriver.pageSource;
+
+          // A full reload should clear the state.
+          expect(source.contains(originalString), isFalse);
+          expect(source.contains(newString), isTrue);
+        });
+      });
+
+      group('and without debugging using WebSockets', () {
+        setUp(() async {
+          setCurrentLogWriter(debug: debug);
+          await context.setUp(
+            testSettings: TestSettings(
+              reloadConfiguration: ReloadConfiguration.liveReload,
+            ),
+            debugSettings: TestDebugSettings.noDevTools().copyWith(
+              enableDebugging: false,
+              useSse: false,
+            ),
+          );
+        });
+
+        tearDown(() async {
+          await context.tearDown();
+          undoEdit();
+        });
+
+        test('can live reload changes ', () async {
+          await makeEditAndWaitForRebuild();
+
+          final source = await context.webDriver.pageSource;
+
+          // A full reload should clear the state.
+          expect(source.contains(originalString), isFalse);
+          expect(source.contains(newString), isTrue);
+        });
+      });
+    },
+    timeout: Timeout.factor(2),
+  );
+
+  group(
+    'Injected client',
+    () {
       setUp(() async {
         setCurrentLogWriter(debug: debug);
         await context.setUp(
           testSettings: TestSettings(
-            reloadConfiguration: ReloadConfiguration.liveReload,
-          ),
-        );
-      });
-
-      tearDown(() async {
-        undoEdit();
-        await context.tearDown();
-      });
-
-      test('can live reload changes ', () async {
-        await makeEditAndWaitForRebuild();
-        final source = await context.webDriver.pageSource;
-
-        // A full reload should clear the state.
-        expect(source.contains(originalString), isFalse);
-        expect(source.contains(newString), isTrue);
-      });
-    });
-
-    group('and without debugging', () {
-      setUp(() async {
-        setCurrentLogWriter(debug: debug);
-        await context.setUp(
-          testSettings: TestSettings(
-            reloadConfiguration: ReloadConfiguration.liveReload,
-          ),
-          debugSettings: TestDebugSettings.noDevTools().copyWith(
-            enableDebugging: false,
-          ),
-        );
-      });
-
-      tearDown(() async {
-        undoEdit();
-        await context.tearDown();
-      });
-
-      test('can live reload changes ', () async {
-        await makeEditAndWaitForRebuild();
-
-        final source = await context.webDriver.pageSource;
-
-        // A full reload should clear the state.
-        expect(source.contains(originalString), isFalse);
-        expect(source.contains(newString), isTrue);
-      });
-    });
-
-    group('and without debugging using WebSockets', () {
-      setUp(() async {
-        setCurrentLogWriter(debug: debug);
-        await context.setUp(
-          testSettings: TestSettings(
-            reloadConfiguration: ReloadConfiguration.liveReload,
-          ),
-          debugSettings: TestDebugSettings.noDevTools().copyWith(
-            enableDebugging: false,
-            useSse: false,
+            enableExpressionEvaluation: true,
           ),
         );
       });
@@ -116,364 +149,10 @@
         undoEdit();
       });
 
-      test('can live reload changes ', () async {
-        await makeEditAndWaitForRebuild();
-
-        final source = await context.webDriver.pageSource;
-
-        // A full reload should clear the state.
-        expect(source.contains(originalString), isFalse);
-        expect(source.contains(newString), isTrue);
-      });
-    });
-  });
-
-  group('Injected client', () {
-    setUp(() async {
-      setCurrentLogWriter(debug: debug);
-      await context.setUp(
-        testSettings: TestSettings(
-          enableExpressionEvaluation: true,
-        ),
-      );
-    });
-
-    tearDown(() async {
-      await context.tearDown();
-      undoEdit();
-    });
-
-    test('destroys and recreates the isolate during a hot restart', () async {
-      final client = context.debugConnection.vmService;
-      await client.streamListen('Isolate');
-      await makeEditAndWaitForRebuild();
-
-      final eventsDone = expectLater(
-        client.onIsolateEvent,
-        emitsThrough(
-          emitsInOrder([
-            _hasKind(EventKind.kIsolateExit),
-            _hasKind(EventKind.kIsolateStart),
-            _hasKind(EventKind.kIsolateRunnable),
-          ]),
-        ),
-      );
-
-      expect(
-        await client.callServiceExtension('hotRestart'),
-        const TypeMatcher<Success>(),
-      );
-
-      await eventsDone;
-    });
-
-    test('can execute simultaneous hot restarts', () async {
-      final client = context.debugConnection.vmService;
-      await client.streamListen('Isolate');
-      await makeEditAndWaitForRebuild();
-
-      final eventsDone = expectLater(
-        client.onIsolateEvent,
-        emitsThrough(
-          emitsInOrder([
-            _hasKind(EventKind.kIsolateExit),
-            _hasKind(EventKind.kIsolateStart),
-            _hasKind(EventKind.kIsolateRunnable),
-          ]),
-        ),
-      );
-
-      // Execute two hot restart calls in parallel.
-      final done = Future.wait([
-        client.callServiceExtension('hotRestart'),
-        client.callServiceExtension('hotRestart'),
-      ]);
-      expect(
-        await done,
-        [const TypeMatcher<Success>(), const TypeMatcher<Success>()],
-      );
-
-      // The debugger is still working.
-      final vm = await client.getVM();
-      final isolateId = vm.isolates!.first.id!;
-      final isolate = await client.getIsolate(isolateId);
-      final library = isolate.rootLib!.uri!;
-
-      final result = await client.evaluate(isolateId, library, 'true');
-      expect(
-        result,
-        isA<InstanceRef>().having(
-          (instance) => instance.valueAsString,
-          'valueAsString',
-          'true',
-        ),
-      );
-
-      await eventsDone;
-    });
-
-    test('destroys and recreates the isolate during a page refresh', () async {
-      final client = context.debugConnection.vmService;
-      await client.streamListen('Isolate');
-      await makeEditAndWaitForRebuild();
-
-      final eventsDone = expectLater(
-        client.onIsolateEvent,
-        emitsThrough(
-          emitsInOrder([
-            _hasKind(EventKind.kIsolateExit),
-            _hasKind(EventKind.kIsolateStart),
-            _hasKind(EventKind.kIsolateRunnable),
-          ]),
-        ),
-      );
-
-      await context.webDriver.driver.refresh();
-
-      await eventsDone;
-    });
-
-    test('can hot restart via the service extension', () async {
-      final client = context.debugConnection.vmService;
-      await client.streamListen('Isolate');
-      await makeEditAndWaitForRebuild();
-
-      final eventsDone = expectLater(
-        client.onIsolateEvent,
-        emitsThrough(
-          emitsInOrder([
-            _hasKind(EventKind.kIsolateExit),
-            _hasKind(EventKind.kIsolateStart),
-            _hasKind(EventKind.kIsolateRunnable),
-          ]),
-        ),
-      );
-
-      expect(
-        await client.callServiceExtension('hotRestart'),
-        const TypeMatcher<Success>(),
-      );
-
-      await eventsDone;
-
-      final source = await context.webDriver.pageSource;
-      // Main is re-invoked which shouldn't clear the state.
-      expect(source, contains(originalString));
-      expect(source, contains(newString));
-    });
-
-    test('can send events before and after hot restart', () async {
-      final client = context.debugConnection.vmService;
-      await client.streamListen('Isolate');
-
-      // The event just before hot restart might never be received,
-      // but the injected client continues to work and send events
-      // after hot restart.
-      final eventsDone = expectLater(
-        client.onIsolateEvent,
-        emitsThrough(
-          _hasKind(EventKind.kServiceExtensionAdded)
-              .having((e) => e.extensionRPC, 'service', 'ext.bar'),
-        ),
-      );
-
-      var vm = await client.getVM();
-      var isolateId = vm.isolates!.first.id!;
-      var isolate = await client.getIsolate(isolateId);
-      var library = isolate.rootLib!.uri!;
-
-      final String callback =
-          '(_, __) async => ServiceExtensionResponse.result("")';
-
-      await client.evaluate(
-        isolateId,
-        library,
-        "registerExtension('ext.foo', $callback)",
-      );
-
-      expect(
-        await client.callServiceExtension('hotRestart'),
-        const TypeMatcher<Success>(),
-      );
-
-      vm = await client.getVM();
-      isolateId = vm.isolates!.first.id!;
-      isolate = await client.getIsolate(isolateId);
-      library = isolate.rootLib!.uri!;
-
-      await client.evaluate(
-        isolateId,
-        library,
-        "registerExtension('ext.bar', $callback)",
-      );
-
-      await eventsDone;
-
-      final source = await context.webDriver.pageSource;
-      // Main is re-invoked which shouldn't clear the state.
-      expect(source, contains('Hello World!'));
-    });
-
-    test('can refresh the page via the fullReload service extension', () async {
-      final client = context.debugConnection.vmService;
-      await client.streamListen('Isolate');
-      await makeEditAndWaitForRebuild();
-
-      final eventsDone = expectLater(
-        client.onIsolateEvent,
-        emitsThrough(
-          emitsInOrder([
-            _hasKind(EventKind.kIsolateExit),
-            _hasKind(EventKind.kIsolateStart),
-            _hasKind(EventKind.kIsolateRunnable),
-          ]),
-        ),
-      );
-
-      expect(await client.callServiceExtension('fullReload'), isA<Success>());
-
-      await eventsDone;
-
-      final source = await context.webDriver.pageSource;
-      // Should see only the new text
-      expect(source.contains(originalString), isFalse);
-      expect(source.contains(newString), isTrue);
-    });
-
-    test('can hot restart while paused', () async {
-      final client = context.debugConnection.vmService;
-      var vm = await client.getVM();
-      var isolateId = vm.isolates!.first.id!;
-      await client.streamListen('Debug');
-      final stream = client.onEvent('Debug');
-      final scriptList = await client.getScripts(isolateId);
-      final main = scriptList.scripts!
-          .firstWhere((script) => script.uri!.contains('main.dart'));
-      final bpLine =
-          await context.findBreakpointLine('printCount', isolateId, main);
-      await client.addBreakpoint(isolateId, main.id!, bpLine);
-      await stream
-          .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
-
-      await makeEditAndWaitForRebuild();
-      await client.callServiceExtension('hotRestart');
-      final source = await context.webDriver.pageSource;
-
-      // Main is re-invoked which shouldn't clear the state.
-      expect(source.contains(originalString), isTrue);
-      expect(source.contains(newString), isTrue);
-
-      vm = await client.getVM();
-      isolateId = vm.isolates!.first.id!;
-      final isolate = await client.getIsolate(isolateId);
-
-      // Previous breakpoint should still exist.
-      expect(isolate.breakpoints!.isNotEmpty, isTrue);
-      final bp = isolate.breakpoints!.first;
-
-      // Should pause eventually.
-      await stream
-          .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
-
-      expect(
-        await client.removeBreakpoint(isolate.id!, bp.id!),
-        isA<Success>(),
-      );
-      expect(await client.resume(isolate.id!), isA<Success>());
-    });
-
-    test('can evaluate expressions after hot restart ', () async {
-      final client = context.debugConnection.vmService;
-      var vm = await client.getVM();
-      var isolateId = vm.isolates!.first.id!;
-      await client.streamListen('Debug');
-      final stream = client.onEvent('Debug');
-      final scriptList = await client.getScripts(isolateId);
-      final main = scriptList.scripts!
-          .firstWhere((script) => script.uri!.contains('main.dart'));
-      final bpLine =
-          await context.findBreakpointLine('printCount', isolateId, main);
-      await client.addBreakpoint(isolateId, main.id!, bpLine);
-      await stream
-          .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
-
-      await client.callServiceExtension('hotRestart');
-
-      vm = await client.getVM();
-      isolateId = vm.isolates!.first.id!;
-      final isolate = await client.getIsolate(isolateId);
-      final library = isolate.rootLib!.uri!;
-      final bp = isolate.breakpoints!.first;
-
-      // Should pause eventually.
-      final event = await stream
-          .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
-
-      // Expression evaluation while paused on a breakpoint should work.
-      var result = await client.evaluateInFrame(
-        isolate.id!,
-        event.topFrame!.index!,
-        'count',
-      );
-      expect(
-        result,
-        isA<InstanceRef>().having(
-          (instance) => instance.valueAsString,
-          'valueAsString',
-          greaterThanOrEqualTo('0'),
-        ),
-      );
-
-      await client.removeBreakpoint(isolateId, bp.id!);
-      await client.resume(isolateId);
-
-      // Expression evaluation while running should work.
-      result = await client.evaluate(isolateId, library, 'true');
-      expect(
-        result,
-        isA<InstanceRef>().having(
-          (instance) => instance.valueAsString,
-          'valueAsString',
-          'true',
-        ),
-      );
-    });
-  });
-
-  group('Injected client with hot restart', () {
-    group('and with debugging', () {
-      setUp(() async {
-        setCurrentLogWriter(debug: debug);
-        await context.setUp(
-          testSettings: TestSettings(
-            reloadConfiguration: ReloadConfiguration.hotRestart,
-          ),
-        );
-      });
-
-      tearDown(() async {
-        await context.tearDown();
-        undoEdit();
-      });
-
-      test('can hot restart changes ', () async {
-        await makeEditAndWaitForRebuild();
-
-        final source = await context.webDriver.pageSource;
-
-        // Main is re-invoked which shouldn't clear the state.
-        expect(source.contains(originalString), isTrue);
-        expect(source.contains(newString), isTrue);
-        // The ext.flutter.disassemble callback is invoked and waited for.
-        expect(
-          source,
-          contains('start disassemble end disassemble $newString'),
-        );
-      });
-
-      test('fires isolate create/destroy events during hot restart', () async {
+      test('destroys and recreates the isolate during a hot restart', () async {
         final client = context.debugConnection.vmService;
         await client.streamListen('Isolate');
+        await makeEditAndWaitForRebuild();
 
         final eventsDone = expectLater(
           client.onIsolateEvent,
@@ -486,45 +165,381 @@
           ),
         );
 
-        await makeEditAndWaitForRebuild();
+        expect(
+          await client.callServiceExtension('hotRestart'),
+          const TypeMatcher<Success>(),
+        );
 
         await eventsDone;
       });
-    });
 
-    group('and without debugging', () {
-      setUp(() async {
-        setCurrentLogWriter(debug: debug);
-        await context.setUp(
-          testSettings: TestSettings(
-            reloadConfiguration: ReloadConfiguration.hotRestart,
-          ),
-          debugSettings:
-              TestDebugSettings.noDevTools().copyWith(enableDebugging: false),
-        );
-      });
-
-      tearDown(() async {
-        await context.tearDown();
-        undoEdit();
-      });
-
-      test('can hot restart changes ', () async {
+      test('can execute simultaneous hot restarts', () async {
+        final client = context.debugConnection.vmService;
+        await client.streamListen('Isolate');
         await makeEditAndWaitForRebuild();
 
+        final eventsDone = expectLater(
+          client.onIsolateEvent,
+          emitsThrough(
+            emitsInOrder([
+              _hasKind(EventKind.kIsolateExit),
+              _hasKind(EventKind.kIsolateStart),
+              _hasKind(EventKind.kIsolateRunnable),
+            ]),
+          ),
+        );
+
+        // Execute two hot restart calls in parallel.
+        final done = Future.wait([
+          client.callServiceExtension('hotRestart'),
+          client.callServiceExtension('hotRestart'),
+        ]);
+        expect(
+          await done,
+          [const TypeMatcher<Success>(), const TypeMatcher<Success>()],
+        );
+
+        // The debugger is still working.
+        final vm = await client.getVM();
+        final isolateId = vm.isolates!.first.id!;
+        final isolate = await client.getIsolate(isolateId);
+        final library = isolate.rootLib!.uri!;
+
+        final result = await client.evaluate(isolateId, library, 'true');
+        expect(
+          result,
+          isA<InstanceRef>().having(
+            (instance) => instance.valueAsString,
+            'valueAsString',
+            'true',
+          ),
+        );
+
+        await eventsDone;
+      });
+
+      test('destroys and recreates the isolate during a page refresh',
+          () async {
+        final client = context.debugConnection.vmService;
+        await client.streamListen('Isolate');
+        await makeEditAndWaitForRebuild();
+
+        final eventsDone = expectLater(
+          client.onIsolateEvent,
+          emitsThrough(
+            emitsInOrder([
+              _hasKind(EventKind.kIsolateExit),
+              _hasKind(EventKind.kIsolateStart),
+              _hasKind(EventKind.kIsolateRunnable),
+            ]),
+          ),
+        );
+
+        await context.webDriver.driver.refresh();
+
+        await eventsDone;
+      });
+
+      test('can hot restart via the service extension', () async {
+        final client = context.debugConnection.vmService;
+        await client.streamListen('Isolate');
+        await makeEditAndWaitForRebuild();
+
+        final eventsDone = expectLater(
+          client.onIsolateEvent,
+          emitsThrough(
+            emitsInOrder([
+              _hasKind(EventKind.kIsolateExit),
+              _hasKind(EventKind.kIsolateStart),
+              _hasKind(EventKind.kIsolateRunnable),
+            ]),
+          ),
+        );
+
+        expect(
+          await client.callServiceExtension('hotRestart'),
+          const TypeMatcher<Success>(),
+        );
+
+        await eventsDone;
+
+        final source = await context.webDriver.pageSource;
+        // Main is re-invoked which shouldn't clear the state.
+        expect(source, contains(originalString));
+        expect(source, contains(newString));
+      });
+
+      test('can send events before and after hot restart', () async {
+        final client = context.debugConnection.vmService;
+        await client.streamListen('Isolate');
+
+        // The event just before hot restart might never be received,
+        // but the injected client continues to work and send events
+        // after hot restart.
+        final eventsDone = expectLater(
+          client.onIsolateEvent,
+          emitsThrough(
+            _hasKind(EventKind.kServiceExtensionAdded)
+                .having((e) => e.extensionRPC, 'service', 'ext.bar'),
+          ),
+        );
+
+        var vm = await client.getVM();
+        var isolateId = vm.isolates!.first.id!;
+        var isolate = await client.getIsolate(isolateId);
+        var library = isolate.rootLib!.uri!;
+
+        final String callback =
+            '(_, __) async => ServiceExtensionResponse.result("")';
+
+        await client.evaluate(
+          isolateId,
+          library,
+          "registerExtension('ext.foo', $callback)",
+        );
+
+        expect(
+          await client.callServiceExtension('hotRestart'),
+          const TypeMatcher<Success>(),
+        );
+
+        vm = await client.getVM();
+        isolateId = vm.isolates!.first.id!;
+        isolate = await client.getIsolate(isolateId);
+        library = isolate.rootLib!.uri!;
+
+        await client.evaluate(
+          isolateId,
+          library,
+          "registerExtension('ext.bar', $callback)",
+        );
+
+        await eventsDone;
+
+        final source = await context.webDriver.pageSource;
+        // Main is re-invoked which shouldn't clear the state.
+        expect(source, contains('Hello World!'));
+      });
+
+      test('can refresh the page via the fullReload service extension',
+          () async {
+        final client = context.debugConnection.vmService;
+        await client.streamListen('Isolate');
+        await makeEditAndWaitForRebuild();
+
+        final eventsDone = expectLater(
+          client.onIsolateEvent,
+          emitsThrough(
+            emitsInOrder([
+              _hasKind(EventKind.kIsolateExit),
+              _hasKind(EventKind.kIsolateStart),
+              _hasKind(EventKind.kIsolateRunnable),
+            ]),
+          ),
+        );
+
+        expect(await client.callServiceExtension('fullReload'), isA<Success>());
+
+        await eventsDone;
+
+        final source = await context.webDriver.pageSource;
+        // Should see only the new text
+        expect(source.contains(originalString), isFalse);
+        expect(source.contains(newString), isTrue);
+      });
+
+      test('can hot restart while paused', () async {
+        final client = context.debugConnection.vmService;
+        var vm = await client.getVM();
+        var isolateId = vm.isolates!.first.id!;
+        await client.streamListen('Debug');
+        final stream = client.onEvent('Debug');
+        final scriptList = await client.getScripts(isolateId);
+        final main = scriptList.scripts!
+            .firstWhere((script) => script.uri!.contains('main.dart'));
+        final bpLine =
+            await context.findBreakpointLine('printCount', isolateId, main);
+        await client.addBreakpoint(isolateId, main.id!, bpLine);
+        await stream
+            .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+
+        await makeEditAndWaitForRebuild();
+        await client.callServiceExtension('hotRestart');
         final source = await context.webDriver.pageSource;
 
         // Main is re-invoked which shouldn't clear the state.
         expect(source.contains(originalString), isTrue);
         expect(source.contains(newString), isTrue);
-        // The ext.flutter.disassemble callback is invoked and waited for.
+
+        vm = await client.getVM();
+        isolateId = vm.isolates!.first.id!;
+        final isolate = await client.getIsolate(isolateId);
+
+        // Previous breakpoint should still exist.
+        expect(isolate.breakpoints!.isNotEmpty, isTrue);
+        final bp = isolate.breakpoints!.first;
+
+        // Should pause eventually.
+        await stream
+            .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+
         expect(
-          source,
-          contains('start disassemble end disassemble $newString'),
+          await client.removeBreakpoint(isolate.id!, bp.id!),
+          isA<Success>(),
+        );
+        expect(await client.resume(isolate.id!), isA<Success>());
+      });
+
+      test('can evaluate expressions after hot restart ', () async {
+        final client = context.debugConnection.vmService;
+        var vm = await client.getVM();
+        var isolateId = vm.isolates!.first.id!;
+        await client.streamListen('Debug');
+        final stream = client.onEvent('Debug');
+        final scriptList = await client.getScripts(isolateId);
+        final main = scriptList.scripts!
+            .firstWhere((script) => script.uri!.contains('main.dart'));
+        final bpLine =
+            await context.findBreakpointLine('printCount', isolateId, main);
+        await client.addBreakpoint(isolateId, main.id!, bpLine);
+        await stream
+            .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+
+        await client.callServiceExtension('hotRestart');
+
+        vm = await client.getVM();
+        isolateId = vm.isolates!.first.id!;
+        final isolate = await client.getIsolate(isolateId);
+        final library = isolate.rootLib!.uri!;
+        final bp = isolate.breakpoints!.first;
+
+        // Should pause eventually.
+        final event = await stream
+            .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
+
+        // Expression evaluation while paused on a breakpoint should work.
+        var result = await client.evaluateInFrame(
+          isolate.id!,
+          event.topFrame!.index!,
+          'count',
+        );
+        expect(
+          result,
+          isA<InstanceRef>().having(
+            (instance) => instance.valueAsString,
+            'valueAsString',
+            greaterThanOrEqualTo('0'),
+          ),
+        );
+
+        await client.removeBreakpoint(isolateId, bp.id!);
+        await client.resume(isolateId);
+
+        // Expression evaluation while running should work.
+        result = await client.evaluate(isolateId, library, 'true');
+        expect(
+          result,
+          isA<InstanceRef>().having(
+            (instance) => instance.valueAsString,
+            'valueAsString',
+            'true',
+          ),
         );
       });
-    });
-  });
+    },
+    timeout: Timeout.factor(2),
+  );
+
+  group(
+    'Injected client with hot restart',
+    () {
+      group('and with debugging', () {
+        setUp(() async {
+          setCurrentLogWriter(debug: debug);
+          await context.setUp(
+            testSettings: TestSettings(
+              reloadConfiguration: ReloadConfiguration.hotRestart,
+            ),
+          );
+        });
+
+        tearDown(() async {
+          await context.tearDown();
+          undoEdit();
+        });
+
+        test('can hot restart changes ', () async {
+          await makeEditAndWaitForRebuild();
+
+          final source = await context.webDriver.pageSource;
+
+          // Main is re-invoked which shouldn't clear the state.
+          expect(source.contains(originalString), isTrue);
+          expect(source.contains(newString), isTrue);
+          // The ext.flutter.disassemble callback is invoked and waited for.
+          expect(
+            source,
+            contains('start disassemble end disassemble $newString'),
+          );
+        });
+
+        test('fires isolate create/destroy events during hot restart',
+            () async {
+          final client = context.debugConnection.vmService;
+          await client.streamListen('Isolate');
+
+          final eventsDone = expectLater(
+            client.onIsolateEvent,
+            emitsThrough(
+              emitsInOrder([
+                _hasKind(EventKind.kIsolateExit),
+                _hasKind(EventKind.kIsolateStart),
+                _hasKind(EventKind.kIsolateRunnable),
+              ]),
+            ),
+          );
+
+          await makeEditAndWaitForRebuild();
+
+          await eventsDone;
+        });
+      });
+
+      group('and without debugging', () {
+        setUp(() async {
+          setCurrentLogWriter(debug: debug);
+          await context.setUp(
+            testSettings: TestSettings(
+              reloadConfiguration: ReloadConfiguration.hotRestart,
+            ),
+            debugSettings:
+                TestDebugSettings.noDevTools().copyWith(enableDebugging: false),
+          );
+        });
+
+        tearDown(() async {
+          await context.tearDown();
+          undoEdit();
+        });
+
+        test('can hot restart changes ', () async {
+          await makeEditAndWaitForRebuild();
+
+          final source = await context.webDriver.pageSource;
+
+          // Main is re-invoked which shouldn't clear the state.
+          expect(source.contains(originalString), isTrue);
+          expect(source.contains(newString), isTrue);
+          // The ext.flutter.disassemble callback is invoked and waited for.
+          expect(
+            source,
+            contains('start disassemble end disassemble $newString'),
+          );
+        });
+      });
+    },
+    timeout: Timeout.factor(2),
+  );
 }
 
 TypeMatcher<Event> _hasKind(String kind) =>
diff --git a/dwds/test/run_request_test.dart b/dwds/test/run_request_test.dart
index 89db646..8efd4f0 100644
--- a/dwds/test/run_request_test.dart
+++ b/dwds/test/run_request_test.dart
@@ -24,51 +24,57 @@
 
   final context = TestContext(TestProject.testWithSoundNullSafety, provider);
 
-  group('while debugger is attached', () {
-    late VmServiceInterface service;
-    setUp(() async {
-      setCurrentLogWriter(debug: debug);
-      await context.setUp(
-        testSettings: TestSettings(
-          autoRun: false,
-          verboseCompiler: debug,
-        ),
-      );
-      service = context.service;
-    });
+  group(
+    'while debugger is attached',
+    () {
+      late VmServiceInterface service;
+      setUp(() async {
+        setCurrentLogWriter(debug: debug);
+        await context.setUp(
+          testSettings: TestSettings(
+            autoRun: false,
+            verboseCompiler: debug,
+          ),
+        );
+        service = context.service;
+      });
 
-    tearDown(() async {
-      await context.tearDown();
-    });
+      tearDown(() async {
+        await context.tearDown();
+      });
 
-    test('can resume while paused at the start', () async {
-      final vm = await service.getVM();
-      final isolate = await service.getIsolate(vm.isolates!.first.id!);
-      expect(isolate.pauseEvent!.kind, EventKind.kPauseStart);
-      final stream = service.onEvent('Debug');
-      final resumeCompleter = Completer();
-      // The underlying stream is a broadcast stream so we need to add a
-      // listener before calling resume so that we don't miss events.
-      unawaited(
-        stream.firstWhere((event) => event.kind == EventKind.kResume).then((_) {
-          resumeCompleter.complete();
-        }),
-      );
-      await service.resume(isolate.id!);
-      await resumeCompleter.future;
-      expect(isolate.pauseEvent!.kind, EventKind.kResume);
-    });
+      test('can resume while paused at the start', () async {
+        final vm = await service.getVM();
+        final isolate = await service.getIsolate(vm.isolates!.first.id!);
+        expect(isolate.pauseEvent!.kind, EventKind.kPauseStart);
+        final stream = service.onEvent('Debug');
+        final resumeCompleter = Completer();
+        // The underlying stream is a broadcast stream so we need to add a
+        // listener before calling resume so that we don't miss events.
+        unawaited(
+          stream
+              .firstWhere((event) => event.kind == EventKind.kResume)
+              .then((_) {
+            resumeCompleter.complete();
+          }),
+        );
+        await service.resume(isolate.id!);
+        await resumeCompleter.future;
+        expect(isolate.pauseEvent!.kind, EventKind.kResume);
+      });
 
-    test('correctly sets the isolate pauseEvent', () async {
-      final vm = await service.getVM();
-      final isolate = await service.getIsolate(vm.isolates!.first.id!);
-      expect(isolate.pauseEvent!.kind, EventKind.kPauseStart);
-      final stream = service.onEvent('Debug');
-      context.appConnection.runMain();
-      await stream.firstWhere((event) => event.kind == EventKind.kResume);
-      expect(isolate.pauseEvent!.kind, EventKind.kResume);
-    });
-  });
+      test('correctly sets the isolate pauseEvent', () async {
+        final vm = await service.getVM();
+        final isolate = await service.getIsolate(vm.isolates!.first.id!);
+        expect(isolate.pauseEvent!.kind, EventKind.kPauseStart);
+        final stream = service.onEvent('Debug');
+        context.appConnection.runMain();
+        await stream.firstWhere((event) => event.kind == EventKind.kResume);
+        expect(isolate.pauseEvent!.kind, EventKind.kResume);
+      });
+    },
+    timeout: Timeout.factor(2),
+  );
 
   group('while debugger is not attached', () {
     setUp(() async {
diff --git a/frontend_server_common/lib/src/bootstrap.dart b/frontend_server_common/lib/src/bootstrap.dart
index a5d1ba0..206e73e 100644
--- a/frontend_server_common/lib/src/bootstrap.dart
+++ b/frontend_server_common/lib/src/bootstrap.dart
@@ -4,6 +4,78 @@
 
 // Note: this is a copy from flutter tools, updated to work with dwds tests
 
+/// JavaScript snippet to determine the base URL of the current path.
+const String _baseUrlScript = '''
+var baseUrl = (function () {
+  // Attempt to detect --precompiled mode for tests, and set the base url
+  // appropriately, otherwise set it to '/'.
+  var pathParts = location.pathname.split("/");
+  if (pathParts[0] == "") {
+    pathParts.shift();
+  }
+  if (pathParts.length > 1 && pathParts[1] == "test") {
+    return "/" + pathParts.slice(0, 2).join("/") + "/";
+  }
+  // Attempt to detect base url using <base href> html tag
+  // base href should start and end with "/"
+  if (typeof document !== 'undefined') {
+    var el = document.getElementsByTagName('base');
+    if (el && el[0] && el[0].getAttribute("href") && el[0].getAttribute
+    ("href").startsWith("/") && el[0].getAttribute("href").endsWith("/")){
+      return el[0].getAttribute("href");
+    }
+  }
+  // return default value
+  return "/";
+}());
+var _trimmedBaseUrl = baseUrl.endsWith('/') ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
+var _currentDirectory = window.location.origin + _trimmedBaseUrl;
+''';
+
+/// Used to load prerequisite scripts such as ddc_module_loader.js
+const String _simpleLoaderScript = r'''
+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;
+  var trimmedRoot = actualRoot.endsWith('/') ? actualRoot.substring(0, actualRoot.length - 1) : actualRoot;
+  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(trimmedRoot + "/" + relativeUrl);
+    document.head.appendChild(script);
+  });
+};
+''';
+
 /// The JavaScript bootstrap script to support in-browser hot restart.
 ///
 /// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl]
@@ -68,3 +140,244 @@
 });
 ''';
 }
+
+String generateDDCBootstrapScript({
+  required String ddcModuleLoaderUrl,
+  required String mapperUrl,
+  required String entrypoint,
+  required String bootstrapUrl,
+}) {
+  return '''
+$_baseUrlScript
+$_simpleLoaderScript
+
+(function() {
+  let appName = "$entrypoint";
+
+  // A uuid that identifies a subapp.
+  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": "dart_library \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": "$bootstrapUrl",
+        "id": "data-main"
+      }
+    ];
+    let loadConfig = new window.\$dartLoader.LoadConfiguration();
+    loadConfig.root = _currentDirectory;
+    loadConfig.bootstrapScript = scripts[scripts.length - 1];
+
+    if (window.\$dartJITModules) {
+      loadConfig.loadScriptFn = function(loader) {
+        // Loads just the entrypoint module and required SDK modules.
+        let moduleSet = new Set();
+        // This cache is populated by ddc_module_loader.js
+        let libraryCache = JSON.parse(window.localStorage.getItem(`dartLibraryCache:\${appName}`));
+        if (libraryCache) {
+          // TODO(b/165021238) - when should this be invalidated?
+          moduleSet = new Set(libraryCache["modules"])
+        }
+        loader.addScriptsToQueue(scripts, function(script) {
+            // Preemptively load the ddc module loader and previously executed modules.
+            return moduleSet.size == 0
+                  || script.id.includes("dart_library")
+                  // We preemptively load the stack_trace_mapper module so that we can
+                  // translate JS errors to Dart.
+                  || script.id.includes("stack_trace_mapper")
+                  || moduleSet.has(script.id);
+        });
+        loader.loadEnqueuedModules();
+      }
+      loadConfig.ddcEventForLoadStart = /* LOAD_ENTRYPOINT_MODULES_START */ 4;
+      loadConfig.ddcEventForLoadedOk = /* LOAD_ENTRYPOINT_MODULES_END_OK */ 5;
+      loadConfig.ddcEventForLoadedError = /* LOAD_ENTRYPOINT_MODULES_END_ERROR */ 6;
+    } else {
+      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();
+
+    let currentUri = _currentScript.src;
+    let fetchEtagsUri;
+    if (currentUri.indexOf("?") == -1) {
+      fetchEtagsUri = currentUri + "?fetch-etags=true";
+    } else {
+      fetchEtagsUri = currentUri + "&fetch-etags=true";
+    }
+
+    if (!window.\$dartAppNameToMetadata) {
+      window.\$dartAppNameToMetadata = new Map();
+    }
+    window.\$dartAppNameToMetadata.set(appName, {
+        currentDirectory: _currentDirectory,
+        currentUri: currentUri,
+        fetchEtagsUri: fetchEtagsUri,
+    });
+
+    if (!window.\$dartReloadModifiedModules) {
+      window.\$dartReloadModifiedModules = (function(appName, callback) {
+        function cb() {
+          window.postMessage(
+              {
+                type: "DDC_STATE_CHANGE",
+                state: "restart_end",
+                targetUuid: uuid,
+              },
+              "*");
+          callback();
+        }
+        window.postMessage(
+            {
+              type: "DDC_STATE_CHANGE",
+              state: "restart_begin",
+              targetUuid: uuid,
+            },
+            "*");
+        var xhttp = new XMLHttpRequest();
+        xhttp.withCredentials = true;
+        xhttp.onreadystatechange = function() {
+          // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
+          if (this.readyState == 4 && this.status == 200 || this.status == 304) {
+            var scripts = JSON.parse(this.responseText);
+            var numToLoad = 0;
+            var numLoaded = 0;
+            for (var i = 0; i < scripts.length; i++) {
+              var script = scripts[i];
+              if (script.id == null) continue;
+              var src =
+                  window.\$dartAppNameToMetadata.get(appName).currentDirectory +
+                  script.src.toString();
+              var oldSrc = window.\$dartLoader.moduleIdToUrl.get(script.id);
+              // Only compare the search parameters which contain the cache
+              // busting portion of the uri. The path might be different if the
+              // script is loaded from a different application on the page.
+              if (new URL(oldSrc).search == new URL(src).search) continue;
+
+              // We might actually load from a different uri, delete the old one
+              // just to be sure.
+              window.\$dartLoader.urlToModuleId.delete(oldSrc);
+
+              window.\$dartLoader.moduleIdToUrl.set(script.id, src);
+              window.\$dartLoader.urlToModuleId.set(src, script.id);
+
+              if (window.\$dartJITModules) {
+              // Simply invalidate the import and the corresponding module will
+              // be lazily loaded.
+              dart_library.invalidateImport(script.id);
+              continue;
+              } else {
+                numToLoad++;
+              }
+
+              var el = document.getElementById(script.id);
+              if (el) el.remove();
+              el = window.\$dartCreateScript();
+              el.src = policy.createScriptURL(src);
+              el.async = false;
+              el.defer = true;
+              el.id = script.id;
+              el.onload = function() {
+                numLoaded++;
+                if (numToLoad == numLoaded) cb();
+              };
+              document.head.appendChild(el);
+            }
+            // Call `cb` right away if we found no updated scripts.
+            if (numToLoad == 0) cb();
+          }
+        };
+        xhttp.open("GET",
+          window.\$dartAppNameToMetadata.get(appName).fetchEtagsUri, true);
+        let sdk = dart_library.import("dart_sdk", appName);
+        let developer = sdk.developer;
+        if (developer._extensions.containsKey("ext.flutter.disassemble")) {
+          developer.invokeExtension("ext.flutter.disassemble", "{}").then(() => {
+            // TODO(b/204210914): we should really be clearing all statics for all
+            // apps, but for now we just do it for flutter apps which we recognize
+            // based on this extension.
+            sdk.dart.hotRestart();
+            xhttp.send();
+          });
+        } else {
+          xhttp.send();
+        }
+      });
+    }
+  }
+})();
+''';
+}
+
+String generateDDCMainModule(
+    {required String entrypoint, String? exportedMain}) {
+  final exportedMainName = exportedMain ?? entrypoint.split('.')[0];
+  return '''/* ENTRYPOINT_EXTENTION_MARKER */
+
+(function() {
+  let appName = "$entrypoint";
+
+  // A uuid that identifies a subapp.
+  let uuid = "00000000-0000-0000-0000-000000000000";
+
+  let dart_sdk = dart_library.import('dart_sdk', appName);
+
+  dart_sdk.dart.setStartAsyncSynchronously(true);
+  dart_sdk._debugger.registerDevtoolsFormatter();
+  dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
+
+  let child = {};
+  child.main = function() {
+    dart_library.start(appName, uuid, "$entrypoint", "$exportedMainName");
+  }
+
+  /* MAIN_EXTENSION_MARKER */
+  child.main();
+})();
+''';
+}
diff --git a/frontend_server_common/lib/src/devfs.dart b/frontend_server_common/lib/src/devfs.dart
index 64e3e45..51a8fed 100644
--- a/frontend_server_common/lib/src/devfs.dart
+++ b/frontend_server_common/lib/src/devfs.dart
@@ -6,6 +6,8 @@
 
 import 'package:dwds/asset_reader.dart';
 import 'package:dwds/config.dart';
+import 'package:dwds/expression_compiler.dart';
+import 'package:dwds/utilities.dart';
 import 'package:file/file.dart';
 import 'package:path/path.dart' as p;
 import 'package:test_common/test_sdk_layout.dart';
@@ -25,6 +27,7 @@
     required this.soundNullSafety,
     this.urlTunneler,
     required this.sdkLayout,
+    required this.ddcModuleFormat,
   });
 
   final FileSystem fileSystem;
@@ -37,6 +40,7 @@
   final UrlEncoder? urlTunneler;
   final bool soundNullSafety;
   final TestSdkLayout sdkLayout;
+  final ModuleFormat ddcModuleFormat;
   late final Directory _savedCurrentDirectory;
 
   Future<Uri> create() async {
@@ -44,8 +48,15 @@
 
     fileSystem.currentDirectory = projectDirectory.toFilePath();
 
-    assetServer = await TestAssetServer.start(sdkLayout.sdkDirectory,
-        fileSystem, index, hostname, port, urlTunneler, packageUriMapper);
+    assetServer = await TestAssetServer.start(
+      sdkLayout.sdkDirectory,
+      fileSystem,
+      index,
+      hostname,
+      port,
+      urlTunneler,
+      packageUriMapper,
+    );
     return Uri.parse('http://$hostname:$port');
   }
 
@@ -64,6 +75,7 @@
     final outputDirectoryPath = fileSystem.file(mainPath).parent.path;
     final entryPoint = mainUri.toString();
 
+    var ddcModuleLoader = 'ddc_module_loader.js';
     var require = 'require.js';
     var stackMapper = 'stack_trace_mapper.js';
     var main = 'main.dart.js';
@@ -73,6 +85,7 @@
     // to store all files, so the paths match the requests.
     if (assetServer.basePath.isEmpty) {
       final directory = p.dirname(entryPoint);
+      ddcModuleLoader = '$directory/ddc_module_loader.js';
       require = '$directory/require.js';
       stackMapper = '$directory/stack_trace_mapper.js';
       main = '$directory/main.dart.js';
@@ -81,22 +94,51 @@
 
     assetServer.writeFile(
         entryPoint, fileSystem.file(mainPath).readAsStringSync());
-    assetServer.writeFile(require, requireJS.readAsStringSync());
     assetServer.writeFile(stackMapper, stackTraceMapper.readAsStringSync());
-    assetServer.writeFile(
-      main,
-      generateBootstrapScript(
-        requireUrl: 'require.js',
-        mapperUrl: 'stack_trace_mapper.js',
-        entrypoint: entryPoint,
-      ),
-    );
-    assetServer.writeFile(
-      bootstrap,
-      generateMainModule(
-        entrypoint: entryPoint,
-      ),
-    );
+
+    switch (ddcModuleFormat) {
+      case ModuleFormat.amd:
+        assetServer.writeFile(require, requireJS.readAsStringSync());
+        assetServer.writeFile(
+          main,
+          generateBootstrapScript(
+            requireUrl: 'require.js',
+            mapperUrl: 'stack_trace_mapper.js',
+            entrypoint: entryPoint,
+          ),
+        );
+        assetServer.writeFile(
+          bootstrap,
+          generateMainModule(
+            entrypoint: entryPoint,
+          ),
+        );
+        break;
+      case ModuleFormat.ddc:
+        assetServer.writeFile(
+            ddcModuleLoader, ddcModuleLoaderJS.readAsStringSync());
+        assetServer.writeFile(
+          main,
+          generateDDCBootstrapScript(
+            ddcModuleLoaderUrl: ddcModuleLoader,
+            mapperUrl: stackMapper,
+            entrypoint: entryPoint,
+            bootstrapUrl: bootstrap,
+          ),
+        );
+        // DDC uses a simple heuristic to determine exported identifier names.
+        // The module name (entrypoint name here) has its extension removed, and
+        // special path elements like '/', '\', and '..' are replaced with '__'.
+        final exportedMainName = pathToJSIdentifier(entryPoint.split('.')[0]);
+        assetServer.writeFile(
+          bootstrap,
+          generateDDCMainModule(
+              entrypoint: entryPoint, exportedMain: exportedMainName),
+        );
+        break;
+      default:
+        throw Exception('Unsupported DDC module format $ddcModuleFormat.');
+    }
 
     assetServer.writeFile('main_module.digests', '{}');
 
@@ -144,11 +186,29 @@
     )..invalidatedModules = modules;
   }
 
+  File get ddcModuleLoaderJS =>
+      fileSystem.file(sdkLayout.ddcModuleLoaderJsPath);
   File get requireJS => fileSystem.file(sdkLayout.requireJsPath);
-  File get dartSdkWeak => fileSystem.file(sdkLayout.weakJsPath);
-  File get dartSdk => fileSystem.file(sdkLayout.soundJsPath);
-  File get dartSdkSourcemapWeak => fileSystem.file(sdkLayout.weakJsMapPath);
-  File get dartSdkSourcemap => fileSystem.file(sdkLayout.soundJsMapPath);
+  File get dartSdkWeak => fileSystem.file(switch (ddcModuleFormat) {
+        ModuleFormat.amd => sdkLayout.weakAmdJsPath,
+        ModuleFormat.ddc => sdkLayout.weakDdcJsPath,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      });
+  File get dartSdk => fileSystem.file(switch (ddcModuleFormat) {
+        ModuleFormat.amd => sdkLayout.soundAmdJsPath,
+        ModuleFormat.ddc => sdkLayout.soundDdcJsPath,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      });
+  File get dartSdkSourcemapWeak => fileSystem.file(switch (ddcModuleFormat) {
+        ModuleFormat.amd => sdkLayout.weakAmdJsMapPath,
+        ModuleFormat.ddc => sdkLayout.weakDdcJsMapPath,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      });
+  File get dartSdkSourcemap => fileSystem.file(switch (ddcModuleFormat) {
+        ModuleFormat.amd => sdkLayout.soundAmdJsMapPath,
+        ModuleFormat.ddc => sdkLayout.soundDdcJsMapPath,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      });
   File get stackTraceMapper => fileSystem.file(sdkLayout.stackTraceMapperPath);
 }
 
diff --git a/frontend_server_common/lib/src/frontend_server_client.dart b/frontend_server_common/lib/src/frontend_server_client.dart
index 18b15b5..6576a0b 100644
--- a/frontend_server_common/lib/src/frontend_server_client.dart
+++ b/frontend_server_common/lib/src/frontend_server_client.dart
@@ -394,6 +394,8 @@
         '--enable-experiment=$experiment',
       if (compilerOptions.canaryFeatures) '--dartdevc-canary',
       if (verbose) '--verbose',
+      if (compilerOptions.moduleFormat == ModuleFormat.ddc)
+        '--dartdevc-module-format=ddc'
     ];
 
     _logger.info(args.join(' '));
diff --git a/frontend_server_common/lib/src/resident_runner.dart b/frontend_server_common/lib/src/resident_runner.dart
index 86f1427..f57954b 100644
--- a/frontend_server_common/lib/src/resident_runner.dart
+++ b/frontend_server_common/lib/src/resident_runner.dart
@@ -81,6 +81,7 @@
       urlTunneler: urlTunneler,
       soundNullSafety: compilerOptions.soundNullSafety,
       sdkLayout: sdkLayout,
+      ddcModuleFormat: compilerOptions.moduleFormat,
     );
     uri = await devFS.create();
 
diff --git a/test_common/lib/sdk_asset_generator.dart b/test_common/lib/sdk_asset_generator.dart
index 16dc7be..bcf6d79 100644
--- a/test_common/lib/sdk_asset_generator.dart
+++ b/test_common/lib/sdk_asset_generator.dart
@@ -1,6 +1,7 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dwds/expression_compiler.dart';
 import 'package:file/file.dart';
 import 'package:file/local.dart';
 import 'package:logging/logging.dart';
@@ -18,6 +19,7 @@
 
   final FileSystem fileSystem;
   final bool canaryFeatures;
+  final ModuleFormat ddcModuleFormat;
   final bool verbose;
 
   late final TestSdkLayout sdkLayout;
@@ -26,6 +28,7 @@
     this.fileSystem = const LocalFileSystem(),
     required this.sdkLayout,
     required this.canaryFeatures,
+    required this.ddcModuleFormat,
     this.verbose = false,
   });
 
@@ -52,6 +55,42 @@
     }
   }
 
+  String resolveSdkJsPath({
+    required bool soundNullSafety,
+    required bool canaryFeatures,
+  }) =>
+      switch ((soundNullSafety, ddcModuleFormat)) {
+        (true, ModuleFormat.amd) => sdkLayout.soundAmdJsPath,
+        (false, ModuleFormat.amd) => sdkLayout.weakAmdJsPath,
+        (true, ModuleFormat.ddc) => sdkLayout.soundDdcJsPath,
+        (false, ModuleFormat.ddc) => sdkLayout.weakDdcJsPath,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      };
+
+  String resolveSdkSourcemapPath({
+    required bool soundNullSafety,
+    required bool canaryFeatures,
+  }) =>
+      switch ((soundNullSafety, ddcModuleFormat)) {
+        (true, ModuleFormat.amd) => sdkLayout.soundAmdJsMapPath,
+        (false, ModuleFormat.amd) => sdkLayout.weakAmdJsMapPath,
+        (true, ModuleFormat.ddc) => sdkLayout.soundDdcJsMapPath,
+        (false, ModuleFormat.ddc) => sdkLayout.weakDdcJsMapPath,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      };
+
+  String resolveSdkJsFilename({
+    required bool soundNullSafety,
+    required bool canaryFeatures,
+  }) =>
+      switch ((soundNullSafety, ddcModuleFormat)) {
+        (true, ModuleFormat.amd) => sdkLayout.soundAmdJsFileName,
+        (false, ModuleFormat.amd) => sdkLayout.weakAmdJsFileName,
+        (true, ModuleFormat.ddc) => sdkLayout.soundDdcJsFileName,
+        (false, ModuleFormat.ddc) => sdkLayout.weakDdcJsFileName,
+        _ => throw Exception('Unsupported DDC module format $ddcModuleFormat.')
+      };
+
   Future<void> _generateSdkJavaScript({
     required bool soundNullSafety,
     required bool canaryFeatures,
@@ -59,10 +98,10 @@
     Directory? outputDir;
     try {
       // Files to copy generated files to.
-      final outputJsPath =
-          soundNullSafety ? sdkLayout.soundJsPath : sdkLayout.weakJsPath;
-      final outputJsMapPath =
-          soundNullSafety ? sdkLayout.soundJsMapPath : sdkLayout.weakJsMapPath;
+      final outputJsPath = resolveSdkJsPath(
+          soundNullSafety: soundNullSafety, canaryFeatures: canaryFeatures);
+      final outputJsMapPath = resolveSdkSourcemapPath(
+          soundNullSafety: soundNullSafety, canaryFeatures: canaryFeatures);
       final outputFullDillPath = soundNullSafety
           ? sdkLayout.soundFullDillPath
           : sdkLayout.weakFullDillPath;
@@ -79,9 +118,11 @@
       outputDir = fileSystem.systemTempDirectory.createTempSync();
 
       // Files to generate
-      final jsPath = soundNullSafety
-          ? p.join(outputDir.path, sdkLayout.soundJsFileName)
-          : p.join(outputDir.path, sdkLayout.weakJsFileName);
+      final jsPath = p.join(
+          outputDir.path,
+          resolveSdkJsFilename(
+              soundNullSafety: soundNullSafety,
+              canaryFeatures: canaryFeatures));
       final jsMapPath = p.setExtension(jsPath, '.js.map');
       final fullDillPath = p.setExtension(jsPath, '.dill');
 
@@ -98,7 +139,7 @@
         '--libraries-file',
         'org-dartlang-sdk:///lib/libraries.json',
         '--modules',
-        'amd',
+        ddcModuleFormat.name,
         soundNullSafety ? '--sound-null-safety' : '--no-sound-null-safety',
         'dart:core',
         '-o',
@@ -241,12 +282,17 @@
   Future<void> _moveAndValidate(String from, String to) async {
     _logger.fine('Renaming $from to $to');
 
+    if (!_exists(from)) {
+      _logger.severe('Failed to generate SDK asset at $to');
+      throw Exception('File "$from" does not exist.');
+    }
+
     if (_exists(to)) _delete(to);
     await fileSystem.file(from).rename(to);
 
     if (!_exists(to)) {
       _logger.severe('Failed to generate SDK asset at $to');
-      throw Exception('File does not exist.');
+      throw Exception('File "$to" does not exist.');
     }
   }
 }
diff --git a/test_common/lib/test_sdk_configuration.dart b/test_common/lib/test_sdk_configuration.dart
index dbc6918..1fb9ec0 100644
--- a/test_common/lib/test_sdk_configuration.dart
+++ b/test_common/lib/test_sdk_configuration.dart
@@ -4,6 +4,7 @@
 
 import 'dart:io';
 
+import 'package:dwds/expression_compiler.dart';
 import 'package:dwds/sdk_configuration.dart';
 import 'package:logging/logging.dart';
 
@@ -25,6 +26,7 @@
 
   final bool _verbose;
   final bool canaryFeatures;
+  final ModuleFormat ddcModuleFormat;
   late final Directory _sdkDirectory;
   SdkConfiguration? _configuration;
 
@@ -33,6 +35,7 @@
   TestSdkConfigurationProvider({
     this.canaryFeatures = false,
     bool verbose = false,
+    this.ddcModuleFormat = ModuleFormat.amd,
   }) : _verbose = verbose {
     _sdkDirectory = Directory.systemTemp.createTempSync('sdk copy');
     sdkLayout = TestSdkLayout.createDefault(_sdkDirectory.path);
@@ -62,6 +65,7 @@
         sdkLayout: sdkLayout,
         canaryFeatures: canaryFeatures,
         verbose: _verbose,
+        ddcModuleFormat: ddcModuleFormat,
       );
 
       await assetGenerator.generateSdkAssets();
diff --git a/test_common/lib/test_sdk_layout.dart b/test_common/lib/test_sdk_layout.dart
index 9064ce8..8d90c48 100644
--- a/test_common/lib/test_sdk_layout.dart
+++ b/test_common/lib/test_sdk_layout.dart
@@ -13,8 +13,10 @@
 /// We keep all the path constants in one place for ease of update.
 class TestSdkLayout {
   static final defaultSdkDirectory = SdkLayout.defaultSdkDirectory;
+
   static TestSdkLayout defaultSdkLayout =
       TestSdkLayout.createDefault(defaultSdkDirectory);
+
   static SdkConfiguration defaultSdkConfiguration =
       createConfiguration(defaultSdkLayout);
 
@@ -32,7 +34,7 @@
           '_internal',
           'ddc_platform.dill',
         ),
-        soundJsPath: p.join(
+        soundAmdJsPath: p.join(
           sdkLayout.sdkDirectory,
           'lib',
           'dev_compiler',
@@ -40,7 +42,7 @@
           'amd',
           'dart_sdk.js',
         ),
-        soundJsMapPath: p.join(
+        soundAmdJsMapPath: p.join(
           sdkLayout.sdkDirectory,
           'lib',
           'dev_compiler',
@@ -48,6 +50,22 @@
           'amd',
           'dart_sdk.js.map',
         ),
+        soundDdcJsPath: p.join(
+          sdkLayout.sdkDirectory,
+          'lib',
+          'dev_compiler',
+          'kernel',
+          'ddc',
+          'dart_sdk.js',
+        ),
+        soundDdcJsMapPath: p.join(
+          sdkLayout.sdkDirectory,
+          'lib',
+          'dev_compiler',
+          'kernel',
+          'ddc',
+          'dart_sdk.js.map',
+        ),
         weakSummaryPath: sdkLayout.weakSummaryPath,
         weakFullDillPath: p.join(
           sdkLayout.sdkDirectory,
@@ -55,7 +73,7 @@
           '_internal',
           'ddc_platform_unsound.dill',
         ),
-        weakJsPath: p.join(
+        weakAmdJsPath: p.join(
           sdkLayout.sdkDirectory,
           'lib',
           'dev_compiler',
@@ -63,7 +81,7 @@
           'amd',
           'dart_sdk_unsound.js',
         ),
-        weakJsMapPath: p.join(
+        weakAmdJsMapPath: p.join(
           sdkLayout.sdkDirectory,
           'lib',
           'dev_compiler',
@@ -71,6 +89,29 @@
           'amd',
           'dart_sdk_unsound.js.map',
         ),
+        weakDdcJsPath: p.join(
+          sdkLayout.sdkDirectory,
+          'lib',
+          'dev_compiler',
+          'kernel',
+          'ddc',
+          'dart_sdk_unsound.js',
+        ),
+        weakDdcJsMapPath: p.join(
+          sdkLayout.sdkDirectory,
+          'lib',
+          'dev_compiler',
+          'kernel',
+          'ddc',
+          'dart_sdk_unsound.js.map',
+        ),
+        ddcModuleLoaderJsPath: p.join(
+          sdkLayout.sdkDirectory,
+          'lib',
+          'dev_compiler',
+          'ddc',
+          'ddc_module_loader.js',
+        ),
         requireJsPath: p.join(
           sdkLayout.sdkDirectory,
           'lib',
@@ -118,26 +159,35 @@
 
   final String sdkDirectory;
 
-  String get soundJsFileName => p.basename(soundJsPath);
-  String get soundJsMapFileName => p.basename(soundJsMapPath);
+  String get soundAmdJsFileName => p.basename(soundAmdJsPath);
+  String get soundAmdJsMapFileName => p.basename(soundAmdJsMapPath);
+  String get soundDdcJsFileName => p.basename(soundDdcJsPath);
+  String get soundDdcJsMapFileName => p.basename(soundDdcJsMapPath);
   String get soundSummaryFileName => p.basename(soundSummaryPath);
   String get soundFullDillFileName => p.basename(soundFullDillPath);
 
-  final String soundJsPath;
-  final String soundJsMapPath;
+  final String soundAmdJsPath;
+  final String soundAmdJsMapPath;
+  final String soundDdcJsPath;
+  final String soundDdcJsMapPath;
   final String soundSummaryPath;
   final String soundFullDillPath;
 
-  String get weakJsFileName => p.basename(weakJsPath);
-  String get weakJsMapFileName => p.basename(weakJsMapPath);
+  String get weakAmdJsFileName => p.basename(weakAmdJsPath);
+  String get weakAmdJsMapFileName => p.basename(weakAmdJsMapPath);
+  String get weakDdcJsFileName => p.basename(weakDdcJsPath);
+  String get weakDdcJsMapFileName => p.basename(weakDdcJsMapPath);
   String get weakSummaryFileName => p.basename(weakSummaryPath);
   String get weakFullDillFileName => p.basename(weakFullDillPath);
 
-  final String weakJsPath;
-  final String weakJsMapPath;
+  final String weakAmdJsPath;
+  final String weakAmdJsMapPath;
+  final String weakDdcJsPath;
+  final String weakDdcJsMapPath;
   final String weakSummaryPath;
   final String weakFullDillPath;
 
+  final String ddcModuleLoaderJsPath;
   final String requireJsPath;
   final String stackTraceMapperPath;
 
@@ -150,14 +200,19 @@
 
   const TestSdkLayout({
     required this.sdkDirectory,
-    required this.soundJsPath,
-    required this.soundJsMapPath,
+    required this.soundAmdJsPath,
+    required this.soundAmdJsMapPath,
+    required this.soundDdcJsPath,
+    required this.soundDdcJsMapPath,
     required this.soundSummaryPath,
     required this.soundFullDillPath,
-    required this.weakJsPath,
-    required this.weakJsMapPath,
+    required this.weakAmdJsPath,
+    required this.weakAmdJsMapPath,
+    required this.weakDdcJsPath,
+    required this.weakDdcJsMapPath,
     required this.weakSummaryPath,
     required this.weakFullDillPath,
+    required this.ddcModuleLoaderJsPath,
     required this.requireJsPath,
     required this.stackTraceMapperPath,
     required this.dartPath,
diff --git a/test_common/test/sdk_asset_generator_test.dart b/test_common/test/sdk_asset_generator_test.dart
index fcbcfa3..2d45cd0 100644
--- a/test_common/test/sdk_asset_generator_test.dart
+++ b/test_common/test/sdk_asset_generator_test.dart
@@ -7,6 +7,7 @@
 
 import 'dart:io';
 
+import 'package:dwds/expression_compiler.dart';
 import 'package:test/test.dart';
 import 'package:test_common/logging.dart';
 import 'package:test_common/sdk_asset_generator.dart';
@@ -23,14 +24,18 @@
 
     // Missing sound assets
     late String soundSdkFullDillPath;
-    late String soundSdkJsPath;
-    late String soundSdkJsMapPath;
+    late String soundAmdSdkJsPath;
+    late String soundAmdSdkJsMapPath;
+    late String soundDdcSdkJsPath;
+    late String soundDdcSdkJsMapPath;
 
     // Missing weak assets
     late String weakSdkSummaryPath;
     late String weakSdkFullDillPath;
-    late String weakSdkJsPath;
-    late String weakSdkJsMapPath;
+    late String weakAmdSdkJsPath;
+    late String weakAmdSdkJsMapPath;
+    late String weakDdcSdkJsPath;
+    late String weakDdcSdkJsMapPath;
 
     setUp(() async {
       setCurrentLogWriter(debug: debug);
@@ -47,30 +52,39 @@
 
       // Simulate missing sound assets.
       soundSdkFullDillPath = copySdkLayout.soundFullDillPath;
-      soundSdkJsPath = copySdkLayout.soundJsPath;
-      soundSdkJsMapPath = copySdkLayout.soundJsMapPath;
+      soundAmdSdkJsPath = copySdkLayout.soundAmdJsPath;
+      soundAmdSdkJsMapPath = copySdkLayout.soundAmdJsMapPath;
+      soundDdcSdkJsPath = copySdkLayout.soundDdcJsPath;
+      soundDdcSdkJsMapPath = copySdkLayout.soundDdcJsMapPath;
 
       _deleteIfExists(soundSdkFullDillPath);
-      _deleteIfExists(soundSdkJsPath);
-      _deleteIfExists(soundSdkJsMapPath);
+      _deleteIfExists(soundAmdSdkJsPath);
+      _deleteIfExists(soundAmdSdkJsMapPath);
+      _deleteIfExists(soundDdcSdkJsPath);
+      _deleteIfExists(soundDdcSdkJsMapPath);
 
       // Simulate missing weak assets.
       weakSdkSummaryPath = copySdkLayout.weakSummaryPath;
       weakSdkFullDillPath = copySdkLayout.weakFullDillPath;
-      weakSdkJsPath = copySdkLayout.weakJsPath;
-      weakSdkJsMapPath = copySdkLayout.weakJsMapPath;
+      weakAmdSdkJsPath = copySdkLayout.weakAmdJsPath;
+      weakAmdSdkJsMapPath = copySdkLayout.weakAmdJsMapPath;
+      weakDdcSdkJsPath = copySdkLayout.weakDdcJsPath;
+      weakDdcSdkJsMapPath = copySdkLayout.weakDdcJsMapPath;
 
       _deleteIfExists(weakSdkSummaryPath);
       _deleteIfExists(weakSdkFullDillPath);
-      _deleteIfExists(weakSdkJsPath);
-      _deleteIfExists(weakSdkJsMapPath);
+      _deleteIfExists(weakAmdSdkJsPath);
+      _deleteIfExists(weakAmdSdkJsMapPath);
+      _deleteIfExists(weakDdcSdkJsPath);
+      _deleteIfExists(weakDdcSdkJsMapPath);
     });
 
     tearDown(() {
       tempDir.deleteSync(recursive: true);
     });
 
-    test('Can generate missing SDK assets and validate SDK configuration',
+    test(
+        'Can generate missing SDK assets and validate SDK configuration for the AMD module system',
         () async {
       final sdkLayout = TestSdkLayout.createDefault(sdkDirectory);
       final configuration = TestSdkLayout.createConfiguration(sdkLayout);
@@ -79,6 +93,7 @@
         sdkLayout: sdkLayout,
         verbose: true,
         canaryFeatures: false,
+        ddcModuleFormat: ModuleFormat.amd,
       );
       await assetGenerator.generateSdkAssets();
 
@@ -88,13 +103,13 @@
 
       expect(sdkLayout.soundSummaryPath, equals(soundSdkSummaryPath));
       expect(sdkLayout.soundFullDillPath, equals(soundSdkFullDillPath));
-      expect(sdkLayout.soundJsPath, equals(soundSdkJsPath));
-      expect(sdkLayout.soundJsMapPath, equals(soundSdkJsMapPath));
+      expect(sdkLayout.soundAmdJsPath, equals(soundAmdSdkJsPath));
+      expect(sdkLayout.soundAmdJsMapPath, equals(soundAmdSdkJsMapPath));
 
       expect(sdkLayout.weakSummaryPath, equals(weakSdkSummaryPath));
       expect(sdkLayout.weakFullDillPath, equals(weakSdkFullDillPath));
-      expect(sdkLayout.weakJsPath, equals(weakSdkJsPath));
-      expect(sdkLayout.weakJsMapPath, equals(weakSdkJsMapPath));
+      expect(sdkLayout.weakAmdJsPath, equals(weakAmdSdkJsPath));
+      expect(sdkLayout.weakAmdJsMapPath, equals(weakAmdSdkJsMapPath));
 
       // Validate that configuration files exist.
       configuration.validateSdkDir();
@@ -103,13 +118,57 @@
       // Validate all assets exist.
       expect(sdkLayout.soundSummaryPath, _exists);
       expect(sdkLayout.soundFullDillPath, _exists);
-      expect(sdkLayout.soundJsPath, _exists);
-      expect(sdkLayout.soundJsMapPath, _exists);
+      expect(sdkLayout.soundAmdJsPath, _exists);
+      expect(sdkLayout.soundAmdJsMapPath, _exists);
 
       expect(sdkLayout.weakSummaryPath, _exists);
       expect(sdkLayout.weakFullDillPath, _exists);
-      expect(sdkLayout.weakJsPath, _exists);
-      expect(sdkLayout.weakJsMapPath, _exists);
+      expect(sdkLayout.weakAmdJsPath, _exists);
+      expect(sdkLayout.weakAmdJsMapPath, _exists);
+    });
+
+    test(
+        'Can generate missing SDK assets and validate SDK configuration for the DDC module system',
+        () async {
+      final sdkLayout = TestSdkLayout.createDefault(sdkDirectory);
+      final configuration = TestSdkLayout.createConfiguration(sdkLayout);
+
+      final assetGenerator = SdkAssetGenerator(
+        sdkLayout: sdkLayout,
+        verbose: true,
+        canaryFeatures: false,
+        ddcModuleFormat: ModuleFormat.ddc,
+      );
+      await assetGenerator.generateSdkAssets();
+
+      // Make sure SDK configuration and asset generator agree on the file paths.
+      expect(configuration.sdkDirectory, equals(sdkDirectory));
+      expect(configuration.compilerWorkerPath, equals(compilerWorkerPath));
+
+      expect(sdkLayout.soundSummaryPath, equals(soundSdkSummaryPath));
+      expect(sdkLayout.soundFullDillPath, equals(soundSdkFullDillPath));
+      expect(sdkLayout.soundDdcJsPath, equals(soundDdcSdkJsPath));
+      expect(sdkLayout.soundDdcJsMapPath, equals(soundDdcSdkJsMapPath));
+
+      expect(sdkLayout.weakSummaryPath, equals(weakSdkSummaryPath));
+      expect(sdkLayout.weakFullDillPath, equals(weakSdkFullDillPath));
+      expect(sdkLayout.weakDdcJsPath, equals(weakDdcSdkJsPath));
+      expect(sdkLayout.weakDdcJsMapPath, equals(weakDdcSdkJsMapPath));
+
+      // Validate that configuration files exist.
+      configuration.validateSdkDir();
+      configuration.validate();
+
+      // Validate all assets exist.
+      expect(sdkLayout.soundSummaryPath, _exists);
+      expect(sdkLayout.soundFullDillPath, _exists);
+      expect(sdkLayout.soundDdcJsPath, _exists);
+      expect(sdkLayout.soundDdcJsMapPath, _exists);
+
+      expect(sdkLayout.weakSummaryPath, _exists);
+      expect(sdkLayout.weakFullDillPath, _exists);
+      expect(sdkLayout.weakDdcJsPath, _exists);
+      expect(sdkLayout.weakDdcJsMapPath, _exists);
     });
 
     test('Can generate missing SDK assets with canary features enabled',
@@ -120,13 +179,34 @@
         sdkLayout: sdkLayout,
         verbose: true,
         canaryFeatures: true,
+        ddcModuleFormat: ModuleFormat.amd,
       );
       await assetGenerator.generateSdkAssets();
 
-      final soundSdk = File(soundSdkJsPath).readAsStringSync();
+      final soundSdk = File(soundAmdSdkJsPath).readAsStringSync();
       expect(soundSdk, contains('canary'));
 
-      final weakSdk = File(weakSdkJsPath).readAsStringSync();
+      final weakSdk = File(weakAmdSdkJsPath).readAsStringSync();
+      expect(weakSdk, contains('canary'));
+    });
+
+    test(
+        'Can generate missing SDK assets with canary features enabled for the DDC module system',
+        () async {
+      final sdkLayout = TestSdkLayout.createDefault(sdkDirectory);
+
+      final assetGenerator = SdkAssetGenerator(
+        sdkLayout: sdkLayout,
+        verbose: true,
+        canaryFeatures: true,
+        ddcModuleFormat: ModuleFormat.ddc,
+      );
+      await assetGenerator.generateSdkAssets();
+
+      final soundSdk = File(soundDdcSdkJsPath).readAsStringSync();
+      expect(soundSdk, contains('canary'));
+
+      final weakSdk = File(weakDdcSdkJsPath).readAsStringSync();
       expect(weakSdk, contains('canary'));
     });
   });
diff --git a/test_common/test/test_sdk_configuration_test.dart b/test_common/test/test_sdk_configuration_test.dart
index 1ff60dd..3e95ac6 100644
--- a/test_common/test/test_sdk_configuration_test.dart
+++ b/test_common/test/test_sdk_configuration_test.dart
@@ -7,6 +7,7 @@
 
 import 'dart:io';
 
+import 'package:dwds/expression_compiler.dart';
 import 'package:test/test.dart';
 import 'package:test_common/logging.dart';
 import 'package:test_common/test_sdk_configuration.dart';
@@ -40,7 +41,45 @@
     });
   });
 
-  group('Test SDK configuration |', () {
+  group('Test SDK configuration | DDC with DDC modules |', () {
+    setCurrentLogWriter(debug: debug);
+    final provider = TestSdkConfigurationProvider(
+        verbose: debug, ddcModuleFormat: ModuleFormat.ddc);
+    tearDownAll(provider.dispose);
+
+    test('Can validate configuration with generated assets', () async {
+      final sdkConfiguration = await provider.configuration;
+      sdkConfiguration.validateSdkDir();
+      sdkConfiguration.validate();
+    });
+
+    test('SDK layout exists', () async {
+      await provider.configuration;
+      final sdkLayout = provider.sdkLayout;
+
+      expect(sdkLayout.sdkDirectory, _directoryExists);
+      expect(sdkLayout.soundDdcJsPath, _fileExists);
+      expect(sdkLayout.soundDdcJsMapPath, _fileExists);
+      expect(sdkLayout.soundSummaryPath, _fileExists);
+      expect(sdkLayout.soundFullDillPath, _fileExists);
+
+      expect(sdkLayout.weakDdcJsPath, _fileExists);
+      expect(sdkLayout.weakDdcJsMapPath, _fileExists);
+      expect(sdkLayout.weakSummaryPath, _fileExists);
+      expect(sdkLayout.weakFullDillPath, _fileExists);
+
+      expect(sdkLayout.ddcModuleLoaderJsPath, _fileExists);
+      expect(sdkLayout.stackTraceMapperPath, _fileExists);
+
+      expect(sdkLayout.dartPath, _fileExists);
+      expect(sdkLayout.frontendServerSnapshotPath, _fileExists);
+      expect(sdkLayout.dartdevcSnapshotPath, _fileExists);
+      expect(sdkLayout.kernelWorkerSnapshotPath, _fileExists);
+      expect(sdkLayout.devToolsDirectory, _directoryExists);
+    });
+  });
+
+  group('Test SDK configuration | DDC with AMD modules |', () {
     setCurrentLogWriter(debug: debug);
     final provider = TestSdkConfigurationProvider(verbose: debug);
     tearDownAll(provider.dispose);
@@ -56,13 +95,13 @@
       final sdkLayout = provider.sdkLayout;
 
       expect(sdkLayout.sdkDirectory, _directoryExists);
-      expect(sdkLayout.soundJsPath, _fileExists);
-      expect(sdkLayout.soundJsMapPath, _fileExists);
+      expect(sdkLayout.soundAmdJsPath, _fileExists);
+      expect(sdkLayout.soundAmdJsMapPath, _fileExists);
       expect(sdkLayout.soundSummaryPath, _fileExists);
       expect(sdkLayout.soundFullDillPath, _fileExists);
 
-      expect(sdkLayout.weakJsPath, _fileExists);
-      expect(sdkLayout.weakJsMapPath, _fileExists);
+      expect(sdkLayout.weakAmdJsPath, _fileExists);
+      expect(sdkLayout.weakAmdJsMapPath, _fileExists);
       expect(sdkLayout.weakSummaryPath, _fileExists);
       expect(sdkLayout.weakFullDillPath, _fileExists);