diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index 2e5036eee31..6dee7b7af60 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -401,6 +401,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C logger.printTrace('Connecting to existing DTD instance at: $existingDtdUri...'); await _dtdService.connect(dtdWsUri: existingDtdUri); } + _previewCodeGenerator.populateDtdConnectionInfo(_dtdService.dtdUri!); } Future runPreviewEnvironment({required FlutterProject widgetPreviewScaffoldProject}) async { @@ -463,12 +464,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C BuildMode.debug, null, treeShakeIcons: false, - // Provide the DTD connection information directly to the preview scaffold. - // This could, in theory, be provided via a follow up call to a service extension - // registered by the preview scaffold, but there's some uncertainty around how service - // extensions will work with Flutter web embedded in VSCode without a Chrome debugger - // connection. - dartDefines: ['$kWidgetPreviewDtdUriEnvVar=${_dtdService.dtdUri}'], packageConfigPath: widgetPreviewScaffoldProject.packageConfig.path, packageConfig: PackageConfig.parseBytes( widgetPreviewScaffoldProject.packageConfig.readAsBytesSync(), diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart index 104306a7744..84b2b1d6c30 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart @@ -52,6 +52,35 @@ class PreviewCodeGenerator { static String getGeneratedPreviewFilePath(FileSystem fs) => fs.path.join('lib', 'src', 'generated_preview.dart'); + static String getGeneratedDtdConnectionInfoFilePath(FileSystem fs) => + fs.path.join('lib', 'src', 'dtd', 'dtd_connection_info.dart'); + + void populateDtdConnectionInfo(Uri dtdUri) { + final emitter = cb.DartEmitter.scoped(useNullSafetySyntax: true); + final lib = cb.Library( + (cb.LibraryBuilder b) => b + ..ignoreForFile.add('implementation_imports') + ..body.addAll([ + cb.Field((b) { + b + ..name = 'kWidgetPreviewDtdUri' + ..modifier = cb.FieldModifier.constant + ..type = cb.refer('String') + ..assignment = cb.literalString(dtdUri.toString()).code; + }), + ]), + ); + final File generatedDtdConnectionInfoFile = fs.file( + widgetPreviewScaffoldProject.directory.uri.resolve(getGeneratedDtdConnectionInfoFilePath(fs)), + ); + generatedDtdConnectionInfoFile.writeAsStringSync( + // Format the generated file for readability, particularly during feature development. + // Note: we don't really care _how_ this is formatted, just that it's formatted, so we don't + // specify a language version. + DartFormatter(languageVersion: Version.none).format(lib.accept(emitter).toString()), + ); + } + // TODO(bkonyi): update generated example now that we're computing constants /// Generates code used by the widget preview scaffold based on the preview instances listed in /// [previews]. diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index 78aad99152e..c929526623a 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -361,6 +361,7 @@ "templates/widget_preview_scaffold/lib/src/controls.dart.tmpl", "templates/widget_preview_scaffold/lib/src/split.dart.tmpl", "templates/widget_preview_scaffold/lib/src/dtd/utils.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl", "templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl", "templates/widget_preview_scaffold/lib/src/dtd/editor_service.dart.tmpl", "templates/widget_preview_scaffold/lib/src/generated_preview.dart.tmpl", diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl new file mode 100644 index 00000000000..092d71d51a4 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const String kWidgetPreviewDtdUri = ''; diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl index 9a7799313ec..82d5a824dee 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl @@ -6,14 +6,12 @@ import 'dart:async'; import 'package:dtd/dtd.dart'; import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart'; import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; import 'package:widget_preview_scaffold/src/dtd/utils.dart'; /// Provides services, streams, and RPC invocations to interact with Flutter developer tooling. class WidgetPreviewScaffoldDtdServices with DtdEditorService { - /// Environment variable for the DTD URI. - static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI'; - // WARNING: Keep these constants and services in sync with those defined in the widget preview // scaffold's dtd_services.dart. // @@ -38,9 +36,7 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { /// If the connection is successful, the Widget Preview Scaffold will register services and /// subscribe to various streams to interact directly with other tooling (e.g., IDEs). Future connect({Uri? dtdUri}) async { - final Uri dtdWsUri = - dtdUri ?? - Uri.parse(const String.fromEnvironment(kWidgetPreviewDtdUriEnvVar)); + final Uri dtdWsUri = dtdUri ?? Uri.parse(kWidgetPreviewDtdUri); dtd = await DartToolingDaemon.connect(dtdWsUri); unawaited( dtd.postEvent( diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart index 3242940023c..c379fe3f147 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart @@ -245,7 +245,12 @@ void main() { onChangeDetected: (_) {}, onPubspecChangeDetected: (String path) {}, ); - codeGenerator = PreviewCodeGenerator(widgetPreviewScaffoldProject: project, fs: fs); + codeGenerator = PreviewCodeGenerator( + widgetPreviewScaffoldProject: FlutterProject.fromDirectoryTest( + project.widgetPreviewScaffold, + ), + fs: fs, + ); final pub = Pub.test( fileSystem: fs, logger: logger, @@ -267,15 +272,15 @@ void main() { 'correctly generates ${PreviewCodeGenerator.getGeneratedPreviewFilePath(fs)}', () async { // Check that the generated preview file doesn't exist yet. - final File generatedPreviewFile = project.directory.childFile( + final File generatedPreviewFile = project.widgetPreviewScaffold.childFile( PreviewCodeGenerator.getGeneratedPreviewFilePath(fs), ); expect(generatedPreviewFile, isNot(exists)); + generatedPreviewFile.createSync(recursive: true); final PreviewDependencyGraph details = await previewDetector.initialize(); // Populate the generated preview file. codeGenerator.populatePreviewsInGeneratedPreviewScaffold(details); - expect(generatedPreviewFile, exists); const expectedGeneratedPreviewFileContents = ''' // ignore_for_file: implementation_imports @@ -385,7 +390,6 @@ List<_i1.WidgetPreview> previews() => [ codeGenerator.populatePreviewsInGeneratedPreviewScaffold( const {}, ); - expect(generatedPreviewFile, exists); // The generated file should only contain: // - An import of the widget preview library @@ -401,5 +405,29 @@ List<_i1.WidgetPreview> previews() => []; expect(generatedPreviewFile.readAsStringSync(), emptyGeneratedPreviewFileContents); }, ); + + testUsingContext( + 'correctly generates ${PreviewCodeGenerator.getGeneratedDtdConnectionInfoFilePath(fs)}', + () async { + // Check that the generated preview file doesn't exist yet. + final File generatedDtdConnectionInfoFile = project.widgetPreviewScaffold.childFile( + PreviewCodeGenerator.getGeneratedDtdConnectionInfoFilePath(fs), + ); + expect(generatedDtdConnectionInfoFile, isNot(exists)); + generatedDtdConnectionInfoFile.createSync(recursive: true); + + // Populate the DTD connection info. + final Uri dtdUri = Uri.parse('ws://localhost:1234'); + codeGenerator.populateDtdConnectionInfo(dtdUri); + + final expectedDtdConnectionInfo = + ''' +// ignore_for_file: implementation_imports + +const String kWidgetPreviewDtdUri = '$dtdUri'; +'''; + expect(generatedDtdConnectionInfoFile.readAsStringSync(), expectedDtdConnectionInfo); + }, + ); }); } diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart new file mode 100644 index 00000000000..092d71d51a4 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const String kWidgetPreviewDtdUri = ''; diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart index 9a7799313ec..82d5a824dee 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart @@ -6,14 +6,12 @@ import 'dart:async'; import 'package:dtd/dtd.dart'; import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart'; import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; import 'package:widget_preview_scaffold/src/dtd/utils.dart'; /// Provides services, streams, and RPC invocations to interact with Flutter developer tooling. class WidgetPreviewScaffoldDtdServices with DtdEditorService { - /// Environment variable for the DTD URI. - static const String kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI'; - // WARNING: Keep these constants and services in sync with those defined in the widget preview // scaffold's dtd_services.dart. // @@ -38,9 +36,7 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { /// If the connection is successful, the Widget Preview Scaffold will register services and /// subscribe to various streams to interact directly with other tooling (e.g., IDEs). Future connect({Uri? dtdUri}) async { - final Uri dtdWsUri = - dtdUri ?? - Uri.parse(const String.fromEnvironment(kWidgetPreviewDtdUriEnvVar)); + final Uri dtdWsUri = dtdUri ?? Uri.parse(kWidgetPreviewDtdUri); dtd = await DartToolingDaemon.connect(dtdWsUri); unawaited( dtd.postEvent(