diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 534f36f510..e4a7abce6e 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,8 +1,10 @@ ## 28.0.0 - -- Support expression evaluation in Frontend Server + Build Daemon mode via `DaemonExpressionCompiler`. -- Add `WebPathTranslator` to support path translation between Frontend Server and Build Daemon. -- Replace `PackageUriMapper` with `PathResolver` strategy. +- **Breaking Change**: Removed `PackageUriMapper` in favor of **`PathResolver`** - with `BuildRunnerPathResolver`, `FrontendServerPathResolver`, and `FlutterPathResolver` impls. +- **Breaking Change**: **`LoadStrategy`** implementors must now implement the **`assetScheme`** getter. +- Introduce **`AssetScheme`** to generalize asset naming schemes. +- Add support for Frontend Server + Build Daemon configuration with hot reload via **`FrontendServerBuildDaemonStrategyProvider`**. +- Expose **`DaemonExpressionCompiler`** for handling expression compilation directly via `build_daemon`. +- **`BuildSettings`** now supports **`useDebuggerModuleNames`** to distinguish between debugger-friendly paths and raw server paths. ## Unreleased diff --git a/dwds/test/integration/breakpoint_ddc_library_bundle_test.dart b/dwds/test/integration/breakpoint_ddc_library_bundle_test.dart index 27c96ac293..fc8e82e21f 100644 --- a/dwds/test/integration/breakpoint_ddc_library_bundle_test.dart +++ b/dwds/test/integration/breakpoint_ddc_library_bundle_test.dart @@ -30,6 +30,13 @@ void main() { ); }); + group('Build Daemon and Frontend Server |', () { + testBreakpoint( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); + group('Frontend Server |', () { testBreakpoint( provider: provider, diff --git a/dwds/test/integration/callstack_ddc_library_bundle_test.dart b/dwds/test/integration/callstack_ddc_library_bundle_test.dart index e0f7b67a4a..342e3582f4 100644 --- a/dwds/test/integration/callstack_ddc_library_bundle_test.dart +++ b/dwds/test/integration/callstack_ddc_library_bundle_test.dart @@ -30,6 +30,13 @@ void main() { ); }); + group('Build Daemon and Frontend Server |', () { + testCallStack( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); + group('Frontend Server |', () { testCallStack( provider: provider, diff --git a/dwds/test/integration/chrome_proxy_service_ddc_library_bundle_test.dart b/dwds/test/integration/chrome_proxy_service_ddc_library_bundle_test.dart index 3a2d401f92..7fde9b316e 100644 --- a/dwds/test/integration/chrome_proxy_service_ddc_library_bundle_test.dart +++ b/dwds/test/integration/chrome_proxy_service_ddc_library_bundle_test.dart @@ -52,4 +52,21 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: $canaryFeatures | Build Daemon and Frontend Server |', () { + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: moduleFormat, + ); + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + tearDownAll(provider.dispose); + + runTests( + provider: provider, + moduleFormat: moduleFormat, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/circular_evaluate_ddc_library_bundle_test.dart b/dwds/test/integration/circular_evaluate_ddc_library_bundle_test.dart index 19b8289fa8..b62337df92 100644 --- a/dwds/test/integration/circular_evaluate_ddc_library_bundle_test.dart +++ b/dwds/test/integration/circular_evaluate_ddc_library_bundle_test.dart @@ -31,6 +31,13 @@ void main() async { testAll(provider: provider, compilationMode: CompilationMode.buildDaemon); }); + group('Build Daemon and Frontend Server |', () { + testAll( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); + group('Frontend Server |', () { group('Context with circular dependencies |', () { for (final indexBaseMode in IndexBaseMode.values) { diff --git a/dwds/test/integration/dart_uri_test.dart b/dwds/test/integration/dart_uri_test.dart index d436181ea2..f8e66ad84b 100644 --- a/dwds/test/integration/dart_uri_test.dart +++ b/dwds/test/integration/dart_uri_test.dart @@ -60,12 +60,12 @@ void main() { test('parses org-dartlang-app paths', () { final uri = DartUri('org-dartlang-app:///blah/main.dart'); - expect(uri.serverPath, '/blah/main.dart'); + expect(uri.serverPath, 'blah/main.dart'); }); test('parses google3 paths', () { final uri = DartUri('google3:///blah/main.dart'); - expect(uri.serverPath, '/blah/main.dart'); + expect(uri.serverPath, 'blah/main.dart'); }); test('parses packages paths', () { diff --git a/dwds/test/integration/evaluate_ddc_library_bundle_test.dart b/dwds/test/integration/evaluate_ddc_library_bundle_test.dart index 0205237f3b..50dc307a4d 100644 --- a/dwds/test/integration/evaluate_ddc_library_bundle_test.dart +++ b/dwds/test/integration/evaluate_ddc_library_bundle_test.dart @@ -32,6 +32,13 @@ void main() async { testAll(provider: provider, compilationMode: CompilationMode.buildDaemon); }); + group('Build Daemon and Frontend Server |', () { + testAll( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); + group('Frontend Server |', () { for (final useDebuggerModuleNames in [false, true]) { group('Debugger module names: $useDebuggerModuleNames |', () { diff --git a/dwds/test/integration/hot_reload_breakpoints_ddc_library_bundle_test.dart b/dwds/test/integration/hot_reload_breakpoints_ddc_library_bundle_test.dart index 986d8f3409..894c14ea2d 100644 --- a/dwds/test/integration/hot_reload_breakpoints_ddc_library_bundle_test.dart +++ b/dwds/test/integration/hot_reload_breakpoints_ddc_library_bundle_test.dart @@ -30,4 +30,11 @@ void main() { compilationMode: CompilationMode.frontendServer, ); }); + + group('Build Daemon and Frontend Server', () { + runTests( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); } diff --git a/dwds/test/integration/hot_reload_ddc_library_bundle_test.dart b/dwds/test/integration/hot_reload_ddc_library_bundle_test.dart index 452dad1a27..b86a5a03d1 100644 --- a/dwds/test/integration/hot_reload_ddc_library_bundle_test.dart +++ b/dwds/test/integration/hot_reload_ddc_library_bundle_test.dart @@ -30,4 +30,11 @@ void main() { compilationMode: CompilationMode.frontendServer, ); }); + + group('Build Daemon and Frontend Server', () { + runTests( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); } diff --git a/dwds/test/integration/hot_restart_breakpoints_amd_test.dart b/dwds/test/integration/hot_restart_breakpoints_amd_test.dart deleted file mode 100644 index a83e86170c..0000000000 --- a/dwds/test/integration/hot_restart_breakpoints_amd_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2026, 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. - -@TestOn('vm') -@Timeout(Duration(minutes: 5)) -library; - -import 'package:dwds/expression_compiler.dart'; -import 'package:dwds_test_common/fixtures/context.dart'; -import 'package:dwds_test_common/integration/hot_restart_breakpoints.dart'; -import 'package:dwds_test_common/test_sdk_configuration.dart'; -import 'package:test/test.dart'; - -void main() { - // Enable verbose logging for debugging. - const debug = false; - - final provider = TestSdkConfigurationProvider( - verbose: debug, - ddcModuleFormat: ModuleFormat.amd, - ); - tearDownAll(provider.dispose); - - group('Build Daemon |', () { - runTests(provider: provider, compilationMode: CompilationMode.buildDaemon); - }); - - group('Frontend Server |', () { - runTests( - provider: provider, - compilationMode: CompilationMode.frontendServer, - ); - }); -} diff --git a/dwds/test/integration/hot_restart_breakpoints_ddc_library_bundle_test.dart b/dwds/test/integration/hot_restart_breakpoints_ddc_library_bundle_test.dart index 449aa0a9c9..5f1b3dc9a3 100644 --- a/dwds/test/integration/hot_restart_breakpoints_ddc_library_bundle_test.dart +++ b/dwds/test/integration/hot_restart_breakpoints_ddc_library_bundle_test.dart @@ -34,4 +34,11 @@ void main() { group('Build Daemon', () { runTests(provider: provider, compilationMode: CompilationMode.buildDaemon); }); + + group('Build Daemon and Frontend Server', () { + runTests( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); } diff --git a/dwds/test/integration/hot_restart_correctness_ddc_library_bundle_test.dart b/dwds/test/integration/hot_restart_correctness_ddc_library_bundle_test.dart index 9441a82a70..3a996b6f7a 100644 --- a/dwds/test/integration/hot_restart_correctness_ddc_library_bundle_test.dart +++ b/dwds/test/integration/hot_restart_correctness_ddc_library_bundle_test.dart @@ -48,4 +48,19 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: $canaryFeatures | Build Daemon and Frontend Server |', () { + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: moduleFormat, + ); + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + runTests( + provider: provider, + moduleFormat: moduleFormat, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/hot_restart_ddc_library_bundle_test.dart b/dwds/test/integration/hot_restart_ddc_library_bundle_test.dart index 3befd9be67..06324aad57 100644 --- a/dwds/test/integration/hot_restart_ddc_library_bundle_test.dart +++ b/dwds/test/integration/hot_restart_ddc_library_bundle_test.dart @@ -50,4 +50,19 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: $canaryFeatures | Build Daemon and Frontend Server |', () { + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: moduleFormat, + ); + runTests( + provider: provider, + moduleFormat: moduleFormat, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/class_inspection_ddc_library_bundle_test.dart b/dwds/test/integration/instances/class_inspection_ddc_library_bundle_test.dart index db9e365a6c..fb60d7d92b 100644 --- a/dwds/test/integration/instances/class_inspection_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/class_inspection_ddc_library_bundle_test.dart @@ -50,4 +50,21 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final canaryFeatures = true; + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/dot_shorthands_ddc_library_bundle_test.dart b/dwds/test/integration/instances/dot_shorthands_ddc_library_bundle_test.dart index 261a85f509..8b50cda34d 100644 --- a/dwds/test/integration/instances/dot_shorthands_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/dot_shorthands_ddc_library_bundle_test.dart @@ -50,4 +50,21 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final canaryFeatures = true; + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/instance_ddc_library_bundle_test.dart b/dwds/test/integration/instances/instance_ddc_library_bundle_test.dart index 19f244a3e7..cf7b25f619 100644 --- a/dwds/test/integration/instances/instance_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/instance_ddc_library_bundle_test.dart @@ -49,4 +49,20 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + canaryFeatures: canaryFeatures, + verbose: debug, + ddcModuleFormat: moduleFormat, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/instance_inspection_ddc_library_bundle_test.dart b/dwds/test/integration/instances/instance_inspection_ddc_library_bundle_test.dart index 5d127ea3c4..7093744fb6 100644 --- a/dwds/test/integration/instances/instance_inspection_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/instance_inspection_ddc_library_bundle_test.dart @@ -33,4 +33,38 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon |', () { + final canaryFeatures = true; + final compilationMode = CompilationMode.buildDaemon; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final canaryFeatures = true; + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/patterns_inspection_ddc_library_bundle_test.dart b/dwds/test/integration/instances/patterns_inspection_ddc_library_bundle_test.dart index d6f796f123..2fe82c5d50 100644 --- a/dwds/test/integration/instances/patterns_inspection_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/patterns_inspection_ddc_library_bundle_test.dart @@ -50,4 +50,21 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final canaryFeatures = true; + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/record_inspection_ddc_library_bundle_test.dart b/dwds/test/integration/instances/record_inspection_ddc_library_bundle_test.dart index 2a44f82183..6c00ffa722 100644 --- a/dwds/test/integration/instances/record_inspection_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/record_inspection_ddc_library_bundle_test.dart @@ -47,4 +47,19 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/record_type_inspection_ddc_library_bundle_test.dart b/dwds/test/integration/instances/record_type_inspection_ddc_library_bundle_test.dart index 1ed9aec598..8f3faf9af8 100644 --- a/dwds/test/integration/instances/record_type_inspection_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/record_type_inspection_ddc_library_bundle_test.dart @@ -47,4 +47,19 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/instances/type_inspection_ddc_library_bundle_test.dart b/dwds/test/integration/instances/type_inspection_ddc_library_bundle_test.dart index 9a809e87fd..ac1a761da2 100644 --- a/dwds/test/integration/instances/type_inspection_ddc_library_bundle_test.dart +++ b/dwds/test/integration/instances/type_inspection_ddc_library_bundle_test.dart @@ -50,4 +50,21 @@ void main() { canaryFeatures: canaryFeatures, ); }); + + group('canary: true | Build Daemon and Frontend Server |', () { + final canaryFeatures = true; + final compilationMode = CompilationMode.buildDaemonAndFrontendServer; + final provider = TestSdkConfigurationProvider( + verbose: debug, + canaryFeatures: canaryFeatures, + ddcModuleFormat: ModuleFormat.ddc, + ); + tearDownAll(provider.dispose); + + runTests( + provider: provider, + compilationMode: compilationMode, + canaryFeatures: canaryFeatures, + ); + }); } diff --git a/dwds/test/integration/package_uri_mapper_test.dart b/dwds/test/integration/package_uri_mapper_test.dart index f362797497..f104ae1c47 100644 --- a/dwds/test/integration/package_uri_mapper_test.dart +++ b/dwds/test/integration/package_uri_mapper_test.dart @@ -33,7 +33,7 @@ void main() { final resolvedPath = '${project.packageDirectory}/lib/test_library.dart'; - late final BuildRunnerPathResolver pathResolver; + late final PathResolver packageUriMapper; setUpAll(() async { await project.setUp(); // Note: Run `dart pub upgrade` before the test cases to fix @@ -51,25 +51,31 @@ void main() { ), ); - pathResolver = await BuildRunnerPathResolver.create( - fileSystem, - packageConfigFile, - useDebuggerModuleNames: useDebuggerModuleNames, - ); + packageUriMapper = useDebuggerModuleNames + ? await FrontendServerPathResolver.create( + fileSystem, + packageConfigFile, + useDebuggerModuleNames: useDebuggerModuleNames, + ) + : await BuildRunnerPathResolver.create( + fileSystem, + packageConfigFile, + useDebuggerModuleNames: useDebuggerModuleNames, + ); }); tearDownAll(project.tearDown); test('Can convert package urls to server paths', () { expect( - pathResolver.appUriToServerPath(packageUri.toString()), + packageUriMapper.appUriToServerPath(packageUri.toString()), serverPath, ); }); test('Can convert server paths to file paths', () { expect( - pathResolver.serverPathToResolvedUri(serverPath), + packageUriMapper.serverPathToResolvedUri(serverPath), isA() .having((uri) => uri.scheme, 'scheme', 'file') .having((uri) => uri.path, 'path', endsWith(resolvedPath)), diff --git a/dwds/test/integration/parts_evaluate_ddc_library_bundle_test.dart b/dwds/test/integration/parts_evaluate_ddc_library_bundle_test.dart index 001132c4b4..59e94b3bf1 100644 --- a/dwds/test/integration/parts_evaluate_ddc_library_bundle_test.dart +++ b/dwds/test/integration/parts_evaluate_ddc_library_bundle_test.dart @@ -31,6 +31,13 @@ void main() async { testAll(provider: provider, compilationMode: CompilationMode.buildDaemon); }); + group('Build Daemon and Frontend Server |', () { + testAll( + provider: provider, + compilationMode: CompilationMode.buildDaemonAndFrontendServer, + ); + }); + group('Frontend Server |', () { group('Context with parts |', () { for (final indexBaseMode in IndexBaseMode.values) { diff --git a/dwds/test/utilities/web_path_translator_test.dart b/dwds/test/utilities/web_path_translator_test.dart new file mode 100644 index 0000000000..cb6434e916 --- /dev/null +++ b/dwds/test/utilities/web_path_translator_test.dart @@ -0,0 +1,308 @@ +// Copyright (c) 2026, 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. + +import 'package:dwds/src/loaders/asset_scheme.dart'; +import 'package:dwds/src/utilities/web_path_translator.dart'; +import 'package:test/test.dart'; + +const fesAssetScheme = FrontendServerAssetScheme(); +const buildAssetScheme = BuildRunnerAssetScheme(); + +void main() { + group('WebPathTranslator', () { + group('addLibSegment', () { + test('adds lib/ to packages paths', () { + // Checks: packages/foo/bar.dart -> packages/foo/lib/bar.dart + expect( + WebPathTranslator.addLibSegment('packages/foo/bar.dart'), + 'packages/foo/lib/bar.dart', + ); + }); + + test('is no-op if lib/ is already present', () { + // Checks: packages/foo/lib/bar.dart -> packages/foo/lib/bar.dart + expect( + WebPathTranslator.addLibSegment('packages/foo/lib/bar.dart'), + 'packages/foo/lib/bar.dart', + ); + }); + + test('is no-op for non-packages paths', () { + // Checks: web/main.dart -> web/main.dart + expect( + WebPathTranslator.addLibSegment('web/main.dart'), + 'web/main.dart', + ); + }); + }); + + group('removeLibSegment', () { + test('removes lib/ from packages paths', () { + // Checks: packages/foo/lib/bar.dart -> packages/foo/bar.dart + expect( + WebPathTranslator.removeLibSegment('packages/foo/lib/bar.dart'), + 'packages/foo/bar.dart', + ); + }); + + test('is no-op if lib/ is not present', () { + // Checks: packages/foo/bar.dart -> packages/foo/bar.dart + expect( + WebPathTranslator.removeLibSegment('packages/foo/bar.dart'), + 'packages/foo/bar.dart', + ); + }); + + test('is no-op for non-packages paths', () { + // Checks: web/main.dart -> web/main.dart + expect( + WebPathTranslator.removeLibSegment('web/main.dart'), + 'web/main.dart', + ); + }); + }); + + group('packagePathToPackageUri', () { + test('converts packages/ paths with lib to package: URIs', () { + expect( + WebPathTranslator.packagePathToPackageUri( + 'packages/foo/lib/bar.dart', + ), + 'package:foo/bar.dart', + ); + }); + + test('converts packages/ paths without lib to package: URIs', () { + expect( + WebPathTranslator.packagePathToPackageUri('packages/foo/bar.dart'), + 'package:foo/bar.dart', + ); + }); + + test('returns null for non-package paths', () { + expect( + WebPathTranslator.packagePathToPackageUri('web/main.dart'), + isNull, + ); + }); + }); + + group('translateLibPathToPackagePath', () { + test('translates lib/ paths to packages/ paths', () { + // Checks: lib/foo.dart -> packages/my_package/foo.dart + expect( + WebPathTranslator.translateLibPathToPackagePath( + 'lib/foo.dart', + 'my_package', + ), + 'packages/my_package/foo.dart', + ); + }); + + test('translates lib/src/ paths to packages/ paths', () { + // Checks: lib/src/foo.dart -> packages/my_package/src/foo.dart + expect( + WebPathTranslator.translateLibPathToPackagePath( + 'lib/src/foo.dart', + 'my_package', + ), + 'packages/my_package/src/foo.dart', + ); + }); + + test('is no-op for non-lib paths', () { + // Checks: web/main.dart -> web/main.dart + expect( + WebPathTranslator.translateLibPathToPackagePath( + 'web/main.dart', + 'my_package', + ), + 'web/main.dart', + ); + }); + + test( + 'throws StateError if rootPackageName is null or empty for lib/ path', + () { + // Checks: lib/foo.dart (with null package) -> StateError + expect( + () => WebPathTranslator.translateLibPathToPackagePath( + 'lib/foo.dart', + null, + ), + throwsStateError, + ); + // Checks: lib/foo.dart (with empty package) -> StateError + expect( + () => WebPathTranslator.translateLibPathToPackagePath( + 'lib/foo.dart', + '', + ), + throwsStateError, + ); + }, + ); + }); + + group('translatePackagePath', () { + test('removes lib/ segment when translating FES to BuildRunner', () { + expect( + WebPathTranslator.translatePackagePath( + 'packages/foo/lib/bar.dart', + from: fesAssetScheme, + to: buildAssetScheme, + ), + 'packages/foo/bar.dart', + ); + }); + + test('adds lib/ segment when translating BuildRunner to FES', () { + expect( + WebPathTranslator.translatePackagePath( + 'packages/foo/bar.dart', + from: buildAssetScheme, + to: fesAssetScheme, + ), + 'packages/foo/lib/bar.dart', + ); + }); + + test('is no-op when schemes are identical', () { + expect( + WebPathTranslator.translatePackagePath( + 'packages/foo/lib/bar.dart', + from: fesAssetScheme, + to: fesAssetScheme, + ), + 'packages/foo/lib/bar.dart', + ); + }); + }); + + group('translateModuleExtension', () { + test( + 'translates .dart.lib to .ddc (frontendServerOnly to buildRunner)', + () { + // Checks: main.dart.lib -> main.ddc + expect( + WebPathTranslator.translateModuleExtension( + 'main.dart.lib', + from: fesAssetScheme, + to: buildAssetScheme, + ), + 'main.ddc', + ); + // Checks: main.dart.lib.js -> main.ddc.js + expect( + WebPathTranslator.translateModuleExtension( + 'main.dart.lib.js', + from: fesAssetScheme, + to: buildAssetScheme, + ), + 'main.ddc.js', + ); + }, + ); + + test( + 'translates .ddc to .dart.lib (buildRunner to frontendServerOnly)', + () { + // Checks: main.ddc -> main.dart.lib + expect( + WebPathTranslator.translateModuleExtension( + 'main.ddc', + from: buildAssetScheme, + to: fesAssetScheme, + ), + 'main.dart.lib', + ); + // Checks: main.ddc.js -> main.dart.lib.js + expect( + WebPathTranslator.translateModuleExtension( + 'main.ddc.js', + from: buildAssetScheme, + to: fesAssetScheme, + ), + 'main.dart.lib.js', + ); + }, + ); + + test('is no-op if from and to are the same', () { + // Checks: main.dart.lib -> main.dart.lib + expect( + WebPathTranslator.translateModuleExtension( + 'main.dart.lib', + from: fesAssetScheme, + to: fesAssetScheme, + ), + 'main.dart.lib', + ); + }); + }); + + group('translateFesToBuildRunnerPath', () { + test('translates .dart.lib to .ddc', () { + // Checks: main.dart.lib -> main.ddc + expect( + WebPathTranslator.translateFesToBuildRunnerPath('main.dart.lib'), + 'main.ddc', + ); + // Checks: main.dart.lib.js -> main.ddc.js + expect( + WebPathTranslator.translateFesToBuildRunnerPath('main.dart.lib.js'), + 'main.ddc.js', + ); + }); + }); + + group('reconstructAppScheme', () { + test('is no-op if org-dartlang-app scheme is already present', () { + // Checks: org-dartlang-app:///web/main.dart -> org-dartlang-app:///web/main.dart + expect( + WebPathTranslator.reconstructAppScheme( + 'org-dartlang-app:///web/main.dart', + '/', + ), + 'org-dartlang-app:///web/main.dart', + ); + }); + + test('reconstructs scheme for default web dirs', () { + // Checks: web/main.dart -> org-dartlang-app:///web/main.dart + expect( + WebPathTranslator.reconstructAppScheme('web/main.dart', '/'), + 'org-dartlang-app:///web/main.dart', + ); + // Checks: /test/foo_test.dart -> org-dartlang-app:///test/foo_test.dart + expect( + WebPathTranslator.reconstructAppScheme('/test/foo_test.dart', '/'), + 'org-dartlang-app:///test/foo_test.dart', + ); + }); + + test('reconstructs scheme for package paths', () { + // Checks: /lib/src/library.dart -> org-dartlang-app:///packages/my_package/src/library.dart + expect( + WebPathTranslator.reconstructAppScheme( + '/lib/src/library.dart', + '/packages/my_package/subdir/main.ddc.js', + ), + 'org-dartlang-app:///packages/my_package/src/library.dart', + ); + }); + + test('handles package paths without leading slash', () { + // Checks: lib/src/library.dart -> org-dartlang-app:///packages/my_package/src/library.dart + expect( + WebPathTranslator.reconstructAppScheme( + 'lib/src/library.dart', + '/packages/my_package/subdir/main.ddc.js', + ), + 'org-dartlang-app:///packages/my_package/src/library.dart', + ); + }); + }); + }); +} diff --git a/dwds_test_common/fixtures/_experiment/pubspec.yaml b/dwds_test_common/fixtures/_experiment/pubspec.yaml index b1064110b2..a471297711 100644 --- a/dwds_test_common/fixtures/_experiment/pubspec.yaml +++ b/dwds_test_common/fixtures/_experiment/pubspec.yaml @@ -13,4 +13,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test/pubspec.yaml b/dwds_test_common/fixtures/_test/pubspec.yaml index 3e86a35057..4e2fa3ec83 100644 --- a/dwds_test_common/fixtures/_test/pubspec.yaml +++ b/dwds_test_common/fixtures/_test/pubspec.yaml @@ -13,5 +13,5 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.8.1 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_circular1/pubspec.yaml b/dwds_test_common/fixtures/_test_circular1/pubspec.yaml index b81da7fd68..b85726d8f4 100644 --- a/dwds_test_common/fixtures/_test_circular1/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_circular1/pubspec.yaml @@ -15,4 +15,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_circular2/pubspec.yaml b/dwds_test_common/fixtures/_test_circular2/pubspec.yaml index e814478484..6108e4275c 100644 --- a/dwds_test_common/fixtures/_test_circular2/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_circular2/pubspec.yaml @@ -13,4 +13,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_dot_shorthands/pubspec.yaml b/dwds_test_common/fixtures/_test_dot_shorthands/pubspec.yaml index bbc2e8bb54..8c214d2a45 100644 --- a/dwds_test_common/fixtures/_test_dot_shorthands/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_dot_shorthands/pubspec.yaml @@ -9,4 +9,4 @@ environment: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_hot_reload/pubspec.yaml b/dwds_test_common/fixtures/_test_hot_reload/pubspec.yaml index 6dc814abea..9693eb2f98 100644 --- a/dwds_test_common/fixtures/_test_hot_reload/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_hot_reload/pubspec.yaml @@ -9,4 +9,4 @@ environment: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.8.1 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_hot_reload_breakpoints/pubspec.yaml b/dwds_test_common/fixtures/_test_hot_reload_breakpoints/pubspec.yaml index 916f64976a..bac3ee9f06 100644 --- a/dwds_test_common/fixtures/_test_hot_reload_breakpoints/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_hot_reload_breakpoints/pubspec.yaml @@ -9,4 +9,4 @@ environment: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.8.1 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_hot_restart1/pubspec.yaml b/dwds_test_common/fixtures/_test_hot_restart1/pubspec.yaml index 9acf0a4555..72fdaa2711 100644 --- a/dwds_test_common/fixtures/_test_hot_restart1/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_hot_restart1/pubspec.yaml @@ -13,4 +13,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.6.0 diff --git a/dwds_test_common/fixtures/_test_hot_restart2/pubspec.yaml b/dwds_test_common/fixtures/_test_hot_restart2/pubspec.yaml index 1027a22740..eb2eef0d53 100644 --- a/dwds_test_common/fixtures/_test_hot_restart2/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_hot_restart2/pubspec.yaml @@ -15,4 +15,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.8.1 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_hot_restart_breakpoints/pubspec.yaml b/dwds_test_common/fixtures/_test_hot_restart_breakpoints/pubspec.yaml index 8bbeb523ef..4bbedce520 100644 --- a/dwds_test_common/fixtures/_test_hot_restart_breakpoints/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_hot_restart_breakpoints/pubspec.yaml @@ -9,5 +9,5 @@ environment: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.8.1 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_package/pubspec.yaml b/dwds_test_common/fixtures/_test_package/pubspec.yaml index 97258123ea..b0825d3c5f 100644 --- a/dwds_test_common/fixtures/_test_package/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_package/pubspec.yaml @@ -13,4 +13,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 \ No newline at end of file + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_test_parts/pubspec.yaml b/dwds_test_common/fixtures/_test_parts/pubspec.yaml index c3129bf5ef..76ee24b689 100644 --- a/dwds_test_common/fixtures/_test_parts/pubspec.yaml +++ b/dwds_test_common/fixtures/_test_parts/pubspec.yaml @@ -13,4 +13,4 @@ dependencies: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/fixtures/_webdev_smoke/pubspec.yaml b/dwds_test_common/fixtures/_webdev_smoke/pubspec.yaml index f9bf432554..31bfa45baa 100644 --- a/dwds_test_common/fixtures/_webdev_smoke/pubspec.yaml +++ b/dwds_test_common/fixtures/_webdev_smoke/pubspec.yaml @@ -9,4 +9,4 @@ environment: dev_dependencies: build_daemon: ^4.1.4 build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/dwds_test_common/lib/integration/dart_uri_file_uri.dart b/dwds_test_common/lib/integration/dart_uri_file_uri.dart index 389318db78..557118a953 100644 --- a/dwds_test_common/lib/integration/dart_uri_file_uri.dart +++ b/dwds_test_common/lib/integration/dart_uri_file_uri.dart @@ -25,17 +25,19 @@ void runTests({ for (final useDebuggerModuleNames in [false, true]) { group('Debugger module names: $useDebuggerModuleNames |', () { - final appServerPath = compilationMode.usesFrontendServer + final appServerPath = compilationMode == CompilationMode.frontendServer ? 'web/main.dart' : 'main.dart'; final serverPath = - compilationMode.usesFrontendServer && useDebuggerModuleNames + compilationMode == CompilationMode.frontendServer && + useDebuggerModuleNames ? 'packages/${testPackageProject.packageDirectory}/lib/test_library.dart' : 'packages/${testPackageProject.packageName}/test_library.dart'; final anotherServerPath = - compilationMode.usesFrontendServer && useDebuggerModuleNames + compilationMode == CompilationMode.frontendServer && + useDebuggerModuleNames ? 'packages/${testProject.packageDirectory}/lib/library.dart' : 'packages/${testProject.packageName}/library.dart'; @@ -44,6 +46,8 @@ void runTests({ testSettings: TestSettings( compilationMode: compilationMode, useDebuggerModuleNames: useDebuggerModuleNames, + moduleFormat: provider.ddcModuleFormat, + canaryFeatures: provider.canaryFeatures, ), ); }); diff --git a/dwds_test_common/pubspec.yaml b/dwds_test_common/pubspec.yaml index 187e8aa12e..0dbc66d318 100644 --- a/dwds_test_common/pubspec.yaml +++ b/dwds_test_common/pubspec.yaml @@ -6,25 +6,26 @@ environment: sdk: ^3.12.0-0 dependencies: - build_daemon: any - dds: any - dwds: any - file: any + build_daemon: ^4.1.4 + dds: ^5.3.0 + dwds: + path: ../dwds + file: ">=6.0.0 <8.0.0" http: any - io: any - logging: any - mime: any - package_config: any - path: any - pub_semver: any - shelf: any + io: ^1.0.5 + logging: ^1.0.1 + mime: ^2.0.0 + package_config: '>=2.0.2 <4.0.0' + path: ^1.8.1 + pub_semver: ^2.1.1 + shelf: ^1.3.0 shelf_proxy: any - test: any - vm_service: any + test: ^1.21.1 + vm_service: ">=14.2.4 <16.0.0" vm_service_interface: any webdriver: any - webkit_inspection_protocol: any - yaml: any + webkit_inspection_protocol: ^1.0.1 + yaml: ^3.1.3 dev_dependencies: dart_flutter_team_lints: ^3.5.2 diff --git a/example/.vscode/launch.json b/example/.vscode/launch.json new file mode 100644 index 0000000000..b26a16f148 --- /dev/null +++ b/example/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Dart Web (Hot Reload)", + "type": "dart", + "request": "launch", + "program": "web/main.dart", // entrypoint + "args": [ + "--web-hot-reload", + ] + } + ] +} \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8e62594da2..2c02706e64 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -8,4 +8,4 @@ environment: dev_dependencies: build_runner: ^2.16.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 diff --git a/frontend_server_client/lib/src/frontend_server_client.dart b/frontend_server_client/lib/src/frontend_server_client.dart index 389f5e5c16..a29931824c 100644 --- a/frontend_server_client/lib/src/frontend_server_client.dart +++ b/frontend_server_client/lib/src/frontend_server_client.dart @@ -75,9 +75,10 @@ class FrontendServerClient { List additionalSources = const [], String? nativeAssets, }) async { + final sdk = sdkRoot ?? sdkDir; final commonArguments = [ '--sdk-root', - sdkRoot ?? sdkDir, + sdk, '--platform=$platformKernel', '--target=$target', if (target == 'dartdevc') @@ -100,7 +101,6 @@ class FrontendServerClient { if (nativeAssets != null) ...['--native-assets', nativeAssets], ]; - final sdk = sdkRoot ?? sdkDir; final dartExecutable = p.join( sdk, 'bin', diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 792eb60b33..3c99521a47 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,4 +1,10 @@ -## 4.1.0-wip +## 4.1.0 +- Enable hot reload support in Frontend Server + Build Daemon mode. +- Support expression evaluation in Frontend Server + Build Daemon mode via DaemonExpressionCompiler. +- Bump `build_web_compilers` constraint to `^4.8.8`. +- Bump `dwds` constraint to `^28.0.0`. + +## Unreleased - Internal test infrastructure refactoring: Move common test files to `dwds_test_common`. diff --git a/webdev/README.md b/webdev/README.md index ba81d1d0f9..110ce827d7 100644 --- a/webdev/README.md +++ b/webdev/README.md @@ -12,7 +12,7 @@ similar to: --- dev_dependencies: build_runner: ^2.4.0 - build_web_compilers: ^4.4.12 + build_web_compilers: ^4.8.9 ``` ## Installation diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart index eea0bee5cd..8fbbf817ac 100644 --- a/webdev/lib/src/daemon/app_domain.dart +++ b/webdev/lib/src/daemon/app_domain.dart @@ -176,7 +176,23 @@ class AppDomain extends Domain { } final fullRestart = getBoolArg(args, 'fullRestart') ?? false; if (!fullRestart) { - return {'code': 1, 'message': 'hot reload not yet supported by webdev'}; + try { + final vm = await appState.vmService!.getVM(); + final isolateId = vm.isolates!.first.id!; + final report = await appState.vmService!.reloadSources(isolateId); + + if (report.success!) { + return {'code': 0, 'message': 'Hot reload successful'}; + } else { + return { + 'code': 1, + 'message': 'Hot reload failed', + 'notices': report.toJson()['notices'], + }; + } + } catch (e) { + return {'code': 1, 'message': 'Hot reload failed: $e'}; + } } // TODO(grouma) - Support pauseAfterRestart. // var pauseAfterRestart = getBoolArg(args, 'pause') ?? false; diff --git a/webdev/lib/src/pubspec.dart b/webdev/lib/src/pubspec.dart index b82d111bb5..c2bc6ec53d 100644 --- a/webdev/lib/src/pubspec.dart +++ b/webdev/lib/src/pubspec.dart @@ -188,7 +188,7 @@ Future> _validateBuildDaemonVersion( } final buildRunnerConstraint = VersionConstraint.parse('^2.4.0'); -final buildWebCompilersConstraint = VersionConstraint.parse('^4.4.12'); +final buildWebCompilersConstraint = VersionConstraint.parse('^4.8.9'); // Note the minimum versions should never be dev versions as users will not // get them by default. diff --git a/webdev/lib/src/serve/dev_workflow.dart b/webdev/lib/src/serve/dev_workflow.dart index 17542e592f..198f21e87a 100644 --- a/webdev/lib/src/serve/dev_workflow.dart +++ b/webdev/lib/src/serve/dev_workflow.dart @@ -15,6 +15,7 @@ import 'package:path/path.dart' as p; import '../command/configuration.dart'; import '../daemon_client.dart'; import '../logging.dart'; +import '../util.dart'; import 'chrome.dart'; import 'server_manager.dart'; import 'webdev_server.dart'; @@ -108,10 +109,7 @@ Future _startServerManager( ); } logWriter(logging.Level.INFO, 'Starting resource servers...'); - final serverManager = await ServerManager.start( - serverOptions, - client.buildResults, - ); + final serverManager = await ServerManager.start(serverOptions, client); for (final server in serverManager.servers) { logWriter( @@ -179,13 +177,19 @@ class DevWorkflow { final _doneCompleter = Completer(); final BuildDaemonClient _client; final Chrome? _chrome; + final Process? _fesProcess; final ServerManager serverManager; StreamSubscription? _resultsSub; final _wrapWidth = stdout.hasTerminal ? stdout.terminalColumns - 8 : 72; - DevWorkflow._(this._client, this._chrome, this.serverManager) { + DevWorkflow._( + this._client, + this._chrome, + this.serverManager, + this._fesProcess, + ) { _resultsSub = _client.buildResults.listen((data) { if (data.results.any( (result) => @@ -206,13 +210,56 @@ class DevWorkflow { Future get done => _doneCompleter.future; + static Future _startFesManager(String workingDirectory) async { + final sdkDir = p.dirname(p.dirname(dartPath)); + final packagesFile = p.join( + workingDirectory, + '.dart_tool', + 'package_config.json', + ); + + final args = [ + 'run', + 'build_web_compilers:fes_manager', + sdkDir, + p.toUri(workingDirectory).toString(), + p.toUri(packagesFile).toString(), + ]; + + return await Process.start( + dartPath, + args, + workingDirectory: workingDirectory, + ); + } + + static Future _waitForFile(File file) async { + while (!await file.exists()) { + await Future.delayed(const Duration(milliseconds: 100)); + } + } + static Future start( Configuration configuration, List buildOptions, Map targetPorts, ) async { final workingDirectory = Directory.current.path; - final client = await _startBuildDaemon(workingDirectory, buildOptions); + + Process? fesProcess; + if (configuration.webHotReload) { + logWriter(logging.Level.INFO, 'Starting Frontend Server Manager...'); + final configFile = File( + p.join(workingDirectory, '.dart_tool', 'build', 'fes_manager_config'), + ); + if (await configFile.exists()) { + await configFile.delete(); + } + fesProcess = await _startFesManager(workingDirectory); + await _waitForFile(configFile); + } + + final client = await _startBuildDaemon(workingDirectory, [...buildOptions]); logWriter(logging.Level.INFO, 'Registering build targets...'); _registerBuildTargets(client, configuration, targetPorts); logWriter(logging.Level.INFO, 'Starting initial build...'); @@ -224,7 +271,7 @@ class DevWorkflow { client, ); final chrome = await _startChrome(configuration, serverManager, client); - return DevWorkflow._(client, chrome, serverManager); + return DevWorkflow._(client, chrome, serverManager, fesProcess); } Future shutDown() async { @@ -232,6 +279,7 @@ class DevWorkflow { await _chrome?.close(); await _client.close(); await serverManager.stop(); + _fesProcess?.kill(); if (!_doneCompleter.isCompleted) _doneCompleter.complete(); } } diff --git a/webdev/lib/src/serve/server_manager.dart b/webdev/lib/src/serve/server_manager.dart index 39e237dbd6..cd708c3d6f 100644 --- a/webdev/lib/src/serve/server_manager.dart +++ b/webdev/lib/src/serve/server_manager.dart @@ -4,7 +4,7 @@ import 'dart:async'; -import 'package:build_daemon/data/build_status.dart'; +import 'package:build_daemon/client.dart'; import 'webdev_server.dart'; @@ -16,11 +16,13 @@ class ServerManager { static Future start( Set serverOptions, - Stream buildResults, + BuildDaemonClient client, ) async { final servers = {}; for (final options in serverOptions) { - servers.add(await WebDevServer.start(options, buildResults)); + servers.add( + await WebDevServer.start(options, client.buildResults, client), + ); } return ServerManager._(servers); } diff --git a/webdev/lib/src/serve/webdev_server.dart b/webdev/lib/src/serve/webdev_server.dart index 309a4adcdd..efeff0eb78 100644 --- a/webdev/lib/src/serve/webdev_server.dart +++ b/webdev/lib/src/serve/webdev_server.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:build_daemon/client.dart'; import 'package:build_daemon/data/build_status.dart' as daemon; import 'package:dwds/data/build_result.dart'; import 'package:dwds/dwds.dart'; @@ -15,7 +16,6 @@ import 'package:http/io_client.dart'; import 'package:http_multi_server/http_multi_server.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; - import 'package:shelf/shelf.dart'; import 'package:shelf_proxy/shelf_proxy.dart'; @@ -85,7 +85,7 @@ class WebDevServer { /// Can be null if client.js injection is disabled. final Dwds? dwds; - final ExpressionCompilerService? ddcService; + final ExpressionCompiler? ddcService; final String target; @@ -123,7 +123,10 @@ class WebDevServer { Future stop() async { await dwds?.stop(); - await ddcService?.stop(); + final service = ddcService; + if (service is ExpressionCompilerService) { + await service.stop(); + } await _server.close(force: true); _client.close(); } @@ -131,6 +134,7 @@ class WebDevServer { static Future start( ServerOptions options, Stream buildResults, + BuildDaemonClient daemonClient, ) async { final basePath = ''; var pipeline = const Pipeline(); @@ -168,19 +172,22 @@ class WebDevServer { // Only provide relevant build results final filteredBuildResults = buildResults.asyncMap((results) { + // Clear reloaded sources for the new build results. + reloadedSources.clear(); if (options.configuration.usesDdcLibraryBundle) { - // Clear reloaded sources for the new build results. - reloadedSources.clear(); - results.changedAssets?.forEach((uri) { - if (uri.path.endsWith(jsLibraryBundleExtension)) { - final reloadedSource = { - 'src': ddcUriToSourceUrl(basePath, options.target, uri), - 'module': ddcUriToLibraryId(uri), - 'libraries': [ddcUriToLibraryId(uri)], - }; - reloadedSources.add(reloadedSource); + final changedAssets = results.changedAssets; + if (changedAssets != null) { + for (final uri in changedAssets) { + if (uri.path.endsWith(jsLibraryBundleExtension)) { + final reloadedSource = { + 'src': ddcUriToSourceUrl(basePath, options.target, uri), + 'module': ddcUriToLibraryId(uri), + 'libraries': [ddcUriToLibraryId(uri)], + }; + reloadedSources.add(reloadedSource); + } } - }); + } } final result = results.results.firstWhere( (result) => result.target == options.target, @@ -211,35 +218,56 @@ class WebDevServer { ); Dwds? dwds; - ExpressionCompilerService? ddcService; + ExpressionCompiler? ddcService; if (options.configuration.enableInjectedClient) { final assetReader = ProxyServerAssetReader( options.daemonPort, root: options.target, ); + // Read the entrypoint metadata generated by build_web_compilers if + // available. This avoids having to hard code 'main.dart' and allows us + // to initialize the frontend server if the build is fully cached. + String? canonicalUri; + if (options.configuration.webHotReload && + !options.configuration.release) { + final entrypointJsonStr = await assetReader.dartSourceContents( + '.web.entrypoint.json', + ); + if (entrypointJsonStr != null) { + try { + final json = jsonDecode(entrypointJsonStr) as Map; + canonicalUri = json['canonicalUri'] as String?; + } catch (e) { + _logger.warning('Failed to parse .web.entrypoint.json', e); + } + } + } + // TODO(https://github.com/flutter/devtools/issues/5350): Figure out how // to determine the build settings from the build. // Can we save build metadata in build_web_compilers and and read it in // the load strategy? final buildSettings = BuildSettings( appEntrypoint: Uri.parse( - '$multiRootScheme:///${options.target}/main.dart', + canonicalUri ?? '$multiRootScheme:///${options.target}/main.dart', ), canaryFeatures: options.configuration.canaryFeatures, isFlutterApp: false, experiments: options.configuration.experiments, + useDebuggerModuleNames: false, ); final LoadStrategy loadStrategy; - if (options.configuration.webHotReload) { - final frontendServerFileSystem = LocalFileSystem(); - final packageUriMapper = await PackageUriMapper.create( + if (options.configuration.webHotReload && + !options.configuration.release) { + final frontendServerFileSystem = const LocalFileSystem(); + final packageUriMapper = await BuildRunnerPathResolver.create( frontendServerFileSystem, findPackageConfigUri()!, useDebuggerModuleNames: false, ); - loadStrategy = FrontendServerDdcLibraryBundleStrategyProvider( + loadStrategy = FrontendServerBuildDaemonStrategyProvider( options.configuration.reload, assetReader, packageUriMapper, @@ -249,23 +277,21 @@ class WebDevServer { reloadedSourcesUri: Uri.parse('$basePath/$reloadedSourcesFileName'), injectScriptLoad: false, ).strategy; + } else if (options.configuration.moduleFormat == 'ddc') { + loadStrategy = BuildRunnerDdcLibraryBundleStrategyProvider( + options.configuration.reload, + assetReader, + buildSettings, + packageConfigPath: findPackageConfigFilePath(), + reloadedSourcesUri: Uri.parse('$basePath/$reloadedSourcesFileName'), + ).strategy; } else { - if (options.configuration.moduleFormat == 'ddc') { - loadStrategy = BuildRunnerDdcLibraryBundleStrategyProvider( - options.configuration.reload, - assetReader, - buildSettings, - packageConfigPath: findPackageConfigFilePath(), - reloadedSourcesUri: Uri.parse('$basePath/$reloadedSourcesFileName'), - ).strategy; - } else { - loadStrategy = BuildRunnerRequireStrategyProvider( - options.configuration.reload, - assetReader, - buildSettings, - packageConfigPath: findPackageConfigFilePath(), - ).strategy; - } + loadStrategy = BuildRunnerRequireStrategyProvider( + options.configuration.reload, + assetReader, + buildSettings, + packageConfigPath: findPackageConfigFilePath(), + ).strategy; } // Check that we're running from a compiled binary (like webdev.exe) and not @@ -290,7 +316,53 @@ class WebDevServer { } if (options.configuration.enableExpressionEvaluation) { - if (isAotMode && !useAotDdc) { + if (options.configuration.webHotReload && + !options.configuration.release) { + // Use the daemon expression compiler when web hot reload is enabled + // to reuse the Frontend Server's in-memory state. + var cachedFesPort = await _readFesPort(); + // Pre-initialize the Frontend Server if a port and canonical URI + // are found. Required for cached builds. + if (cachedFesPort != null && canonicalUri != null) { + _logger.info( + 'Early initializing Frontend Server with $canonicalUri', + ); + try { + final responseStr = await _sendFesRawRequest(cachedFesPort, { + 'instruction': 'COMPILE', + 'entrypoint': canonicalUri, + }); + _logger.fine('Early initialization response: $responseStr'); + } catch (e) { + _logger.warning('Failed to early initialize FES', e); + } + } + + ddcService = DaemonExpressionCompiler((request) async { + if (cachedFesPort != null) { + try { + return await _sendRequestToFes(cachedFesPort!, request); + } catch (e) { + _logger.warning( + 'Failed to connect to FES at $cachedFesPort, re-reading config file', + e, + ); + cachedFesPort = null; + } + } + + final port = await _readFesPort(); + if (port != null) { + cachedFesPort = port; + return await _sendRequestToFes(port, request); + } + return { + 'result': + 'InternalError: Failed to connect to Frontend Server worker.', + 'isError': true, + }; + }); + } else if (isAotMode && !useAotDdc) { _logger.warning( 'Expression evaluation will be disabled in AOT mode because ' 'dartdevc_aot.dart.snapshot was not found in the SDK.', @@ -445,3 +517,72 @@ String ddcUriToLibraryId(Uri uri) { ); return '$prefix.dart'; } + +Future _readFesPort() async { + try { + final file = File(p.join('.dart_tool', 'build', 'fes_manager_config')); + if (await file.exists()) { + final content = await file.readAsString(); + final json = jsonDecode(content) as Map; + return json['port'] as int?; + } + } catch (e) { + _logger.warning('Failed to read FES config', e); + } + return null; +} + +Future _sendFesRawRequest( + int port, + Map request, +) async { + final socket = await Socket.connect(InternetAddress.loopbackIPv4, port); + try { + socket.writeln(jsonEncode(request)); + await socket.flush(); + final responseStr = await socket + .cast>() + .transform(utf8.decoder) + .transform(const LineSplitter()) + .first; + return jsonDecode(responseStr); + } finally { + await socket.close(); + } +} + +Future> _sendRequestToFes( + int port, + Map request, +) async { + final compileResult = await _sendFesRawRequest(port, request); + if (compileResult is! Map) { + return { + 'result': + 'Unexpected response format from FES. ' + 'Expected a Map but got: $compileResult', + 'isError': true, + }; + } + + final error = compileResult['error'] as String?; + final errorCount = compileResult['errorCount'] as int?; + final errorMessage = compileResult['errorMessage'] as String?; + if (error != null || (errorCount != null && errorCount > 0)) { + return { + 'result': 'FES error: ${error ?? errorMessage ?? 'Unknown error'}', + 'isError': true, + }; + } + + final expressionData = compileResult['expressionData'] as String?; + if (expressionData == null) { + return { + 'result': 'Missing expressionData in FES compile result', + 'isError': true, + }; + } + + final decodedResult = utf8.decode(base64.decode(expressionData)); + return {'result': decodedResult, 'isError': false}; +} diff --git a/webdev/lib/src/util.dart b/webdev/lib/src/util.dart index 47033d5469..e241fbe69a 100644 --- a/webdev/lib/src/util.dart +++ b/webdev/lib/src/util.dart @@ -30,12 +30,12 @@ void serveHttpRequests( final String _sdkDir = (() { // The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is // "/path/to/sdk". - final String dartExecutable = Platform.isWindows + final dartExecutable = Platform.isWindows // Use 'where.exe' to support powershell as well ? (Process.runSync('where.exe', ['dart.exe']).stdout as String) .split(RegExp('(\r\n|\r|\n)')) .first - : Process.runSync('which', ['dart']).stdout; + : (Process.runSync('which', ['dart']).stdout as String).trim(); final aboveExecutable = p.dirname(p.dirname(dartExecutable)); assert(FileSystemEntity.isFileSync(p.join(aboveExecutable, 'version'))); return aboveExecutable; diff --git a/webdev/lib/src/version.dart b/webdev/lib/src/version.dart index 78c01d1a64..2384f91ac4 100644 --- a/webdev/lib/src/version.dart +++ b/webdev/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '4.1.0-wip'; +const packageVersion = '4.1.0'; diff --git a/webdev/pubspec.yaml b/webdev/pubspec.yaml index 390aca6293..140d635205 100644 --- a/webdev/pubspec.yaml +++ b/webdev/pubspec.yaml @@ -1,6 +1,6 @@ name: webdev # Every time this changes you need to run `dart run build_runner build`. -version: 4.1.0-wip +version: 4.1.0 # We should not depend on a dev SDK before publishing. # publish_to: none description: >- diff --git a/webdev/test/daemon/app_domain_common.dart b/webdev/test/daemon/app_domain_common.dart index a6009ad55c..92b38a3bde 100644 --- a/webdev/test/daemon/app_domain_common.dart +++ b/webdev/test/daemon/app_domain_common.dart @@ -114,14 +114,21 @@ void appDomainTests({required TestRunner testRunner}) { '[{"method":"app.restart","id":0,' '"params" : { "appId" : "$appId", "fullRestart" : false}}]'; webdev.stdin.add(utf8.encode('$extensionCall\n')); - await expectLater( - webdev.stdout, - emitsThrough( - startsWith( - '[{"id":0,"result":{"code":1,"message":"hot reload not yet supported', - ), - ), - ); + + var success = false; + while (await webdev.stdout.hasNext) { + final line = await webdev.stdout.next; + if (line.startsWith('[{"id":0,')) { + final unwrapped = line.substring(1, line.length - 1); + final response = json.decode(unwrapped) as Map; + final result = response['result'] as Map; + expect(result['code'], equals(0)); + expect(result['message'], equals('Hot reload successful')); + success = true; + break; + } + } + expect(success, isTrue); await exitWebdev(webdev); }, timeout: const Timeout(Duration(minutes: 2))); diff --git a/webdev/test/daemon/app_domain_ddc_library_bundle_test.dart b/webdev/test/daemon/app_domain_ddc_library_bundle_test.dart index 1f6fa8b3e7..ab58ec869b 100644 --- a/webdev/test/daemon/app_domain_ddc_library_bundle_test.dart +++ b/webdev/test/daemon/app_domain_ddc_library_bundle_test.dart @@ -12,11 +12,23 @@ import '../test_utils.dart'; import 'app_domain_common.dart'; void main() { - appDomainTests( - testRunner: TestRunner( - canaryFeatures: true, - webHotReload: false, - ddcModuleFormat: ModuleFormat.ddc, - ), - ); + group('Build Daemon', () { + appDomainTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: false, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); + + group('Build Daemon and Frontend Server', () { + appDomainTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: true, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); } diff --git a/webdev/test/daemon/daemon_domain_ddc_library_bundle_test.dart b/webdev/test/daemon/daemon_domain_ddc_library_bundle_test.dart index 30c04fb792..cad0ebcab1 100644 --- a/webdev/test/daemon/daemon_domain_ddc_library_bundle_test.dart +++ b/webdev/test/daemon/daemon_domain_ddc_library_bundle_test.dart @@ -12,11 +12,23 @@ import '../test_utils.dart'; import 'daemon_domain_common.dart'; void main() { - daemonDomainTests( - testRunner: TestRunner( - canaryFeatures: true, - webHotReload: false, - ddcModuleFormat: ModuleFormat.ddc, - ), - ); + group('Build Daemon', () { + daemonDomainTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: false, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); + + group('Build Daemon and Frontend Server', () { + daemonDomainTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: true, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); } diff --git a/webdev/test/daemon/launch_app_common.dart b/webdev/test/daemon/launch_app_common.dart index d0319a9546..43b68556b0 100644 --- a/webdev/test/daemon/launch_app_common.dart +++ b/webdev/test/daemon/launch_app_common.dart @@ -5,6 +5,8 @@ @Timeout(Duration(minutes: 2)) library; +import 'dart:io'; +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import '../test_utils.dart'; @@ -16,6 +18,11 @@ void launchAppTests({required TestRunner testRunner}) { setUpAll(() async { await testRunner.setUpAll(); exampleDirectory = await testRunner.prepareWorkspace(); + // Delete 'main.dart' to ensure only one entrypoint exists. + final mainDart = File(p.join(exampleDirectory, 'web', 'main.dart')); + if (mainDart.existsSync()) { + mainDart.deleteSync(); + } }); tearDownAll(testRunner.tearDownAll); diff --git a/webdev/test/daemon/launch_app_ddc_library_bundle_test.dart b/webdev/test/daemon/launch_app_ddc_library_bundle_test.dart index d9746fe977..baac246471 100644 --- a/webdev/test/daemon/launch_app_ddc_library_bundle_test.dart +++ b/webdev/test/daemon/launch_app_ddc_library_bundle_test.dart @@ -12,11 +12,23 @@ import '../test_utils.dart'; import 'launch_app_common.dart'; void main() { - launchAppTests( - testRunner: TestRunner( - canaryFeatures: true, - webHotReload: false, - ddcModuleFormat: ModuleFormat.ddc, - ), - ); + group('Build Daemon', () { + launchAppTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: false, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); + + group('Build Daemon and Frontend Server', () { + launchAppTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: true, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); } diff --git a/webdev/test/e2e_common.dart b/webdev/test/e2e_common.dart index 9080a1ae13..b7c2205a53 100644 --- a/webdev/test/e2e_common.dart +++ b/webdev/test/e2e_common.dart @@ -14,13 +14,11 @@ import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; -import 'package:test_process/test_process.dart'; import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service_io.dart'; import 'package:webdev/src/logging.dart'; import 'package:webdev/src/pubspec.dart'; import 'package:webdev/src/serve/utils.dart'; -import 'package:webdev/src/util.dart'; import 'package:yaml/yaml.dart'; import 'daemon/utils.dart'; @@ -44,23 +42,20 @@ void e2eTests({required TestRunner testRunner}) { setUpAll(() async { configureLogWriter(debug); await testRunner.setUpAll(); - exampleDirectory = p.absolute( - p.join(p.current, '..', 'dwds_test_common', 'fixtures', '_webdev_smoke'), - ); - - final process = await TestProcess.start( - dartPath, - ['pub', 'upgrade'], - workingDirectory: exampleDirectory, - environment: getPubEnvironment(), - ); - - await process.shouldExit(0); - - await d - .file('.dart_tool/package_config.json', isNotEmpty) - .validate(exampleDirectory); - await d.file('pubspec.lock', isNotEmpty).validate(exampleDirectory); + exampleDirectory = await testRunner.prepareWorkspace(); + // Delete files other than main.dart and index.html to ensure a single + // entrypoint exists. + final webDir = Directory(p.join(exampleDirectory, 'web')); + if (await webDir.exists()) { + await for (final entity in webDir.list()) { + if (entity is File) { + final name = p.basename(entity.path); + if (name != 'main.dart' && name != 'index.html') { + await entity.delete(); + } + } + } + } }); tearDownAll(testRunner.tearDownAll); @@ -217,10 +212,10 @@ void e2eTests({required TestRunner testRunner}) { final hostUrl = 'http://localhost:$openPort'; - // Wait for the initial build to finish. + // Wait for the server to be ready for connections. await expectLater( process.stdout, - emitsThrough(contains('Built with build_runner')), + emitsThrough(contains('Serving `web` on')), ); final client = HttpClient(); diff --git a/webdev/test/e2e_ddc_library_bundle_test.dart b/webdev/test/e2e_ddc_library_bundle_test.dart index 6a685854c2..ab0788892b 100644 --- a/webdev/test/e2e_ddc_library_bundle_test.dart +++ b/webdev/test/e2e_ddc_library_bundle_test.dart @@ -12,11 +12,23 @@ import 'e2e_common.dart'; import 'test_utils.dart'; void main() { - e2eTests( - testRunner: TestRunner( - canaryFeatures: true, - webHotReload: false, - ddcModuleFormat: ModuleFormat.ddc, - ), - ); + group('Build Daemon', () { + e2eTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: false, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); + + group('Build Daemon and Frontend Server', () { + e2eTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: true, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); } diff --git a/webdev/test/integration_common.dart b/webdev/test/integration_common.dart index 629a2d1fc1..c04c930f7e 100644 --- a/webdev/test/integration_common.dart +++ b/webdev/test/integration_common.dart @@ -296,7 +296,7 @@ dependencies: } const _supportedBuildRunnerVersion = '2.4.0'; -const _supportedWebCompilersVersion = '4.4.12'; +const _supportedWebCompilersVersion = '4.8.9'; const _supportedBuildDaemonVersion = '4.0.0'; String _pubspecYaml = ''' diff --git a/webdev/test/integration_ddc_library_bundle_test.dart b/webdev/test/integration_ddc_library_bundle_test.dart index 43870d02b4..e35e5ba8a2 100644 --- a/webdev/test/integration_ddc_library_bundle_test.dart +++ b/webdev/test/integration_ddc_library_bundle_test.dart @@ -12,11 +12,23 @@ import 'integration_common.dart'; import 'test_utils.dart'; void main() { - integrationTests( - testRunner: TestRunner( - canaryFeatures: true, - webHotReload: false, - ddcModuleFormat: ModuleFormat.ddc, - ), - ); + group('Build Daemon', () { + integrationTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: false, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); + + group('Build Daemon and Frontend Server', () { + integrationTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: true, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); } diff --git a/webdev/test/tls_common.dart b/webdev/test/tls_common.dart index 6984ae6507..939d6d3f6b 100644 --- a/webdev/test/tls_common.dart +++ b/webdev/test/tls_common.dart @@ -9,7 +9,6 @@ import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; -import 'package:test_process/test_process.dart'; import 'package:webdev/src/logging.dart'; import 'package:webdev/src/serve/utils.dart'; @@ -26,24 +25,15 @@ void tlsTests({required TestRunner testRunner}) { setUpAll(() async { configureLogWriter(debug); await testRunner.setUpAll(); - exampleDirectory = p.absolute( - p.join( - p.current, - '..', - 'dwds_test_common', - 'fixtures', - '_webdev_smoke', - ), - ); + exampleDirectory = await testRunner.prepareWorkspace(); - final process = await TestProcess.start( - 'dart', - ['pub', 'upgrade'], - workingDirectory: exampleDirectory, - environment: getPubEnvironment(), + // Delete 'scopes_main.dart' to ensure only one entrypoint exists. + final scopesMainDart = File( + p.join(exampleDirectory, 'web', 'scopes_main.dart'), ); - - await process.shouldExit(0); + if (scopesMainDart.existsSync()) { + scopesMainDart.deleteSync(); + } await d .file('.dart_tool/package_config.json', isNotEmpty) @@ -65,9 +55,10 @@ void tlsTests({required TestRunner testRunner}) { args, workingDirectory: exampleDirectory, ); + // Wait for the server to be ready for connections. await expectLater( process.stdout, - emitsThrough(contains('Built with build_runner')), + emitsThrough(contains('Serving `web` on')), ); final client = HttpClient() @@ -101,9 +92,10 @@ void tlsTests({required TestRunner testRunner}) { args, workingDirectory: exampleDirectory, ); + // Wait for the server to be ready for connections. await expectLater( process.stdout, - emitsThrough(contains('Built with build_runner')), + emitsThrough(contains('Serving `web` on')), ); final interfaces = await NetworkInterface.list( diff --git a/webdev/test/tls_ddc_library_bundle_test.dart b/webdev/test/tls_ddc_library_bundle_test.dart index 7c83b42c25..a0dc36d591 100644 --- a/webdev/test/tls_ddc_library_bundle_test.dart +++ b/webdev/test/tls_ddc_library_bundle_test.dart @@ -12,11 +12,23 @@ import 'test_utils.dart'; import 'tls_common.dart'; void main() { - tlsTests( - testRunner: TestRunner( - canaryFeatures: true, - webHotReload: false, - ddcModuleFormat: ModuleFormat.ddc, - ), - ); + group('Build Daemon', () { + tlsTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: false, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); + + group('Build Daemon and Frontend Server', () { + tlsTests( + testRunner: TestRunner( + canaryFeatures: true, + webHotReload: true, + ddcModuleFormat: ModuleFormat.ddc, + ), + ); + }); }