| // Copyright (c) 2020, 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 'dart:io'; |
| import 'dart:typed_data'; |
| |
| import 'package:dwds/src/debugging/dart_runtime_debugger.dart'; |
| import 'package:dwds/src/debugging/metadata/provider.dart'; |
| import 'package:dwds/src/readers/asset_reader.dart'; |
| import 'package:dwds/src/services/expression_compiler.dart'; |
| import 'package:dwds/src/utilities/dart_uri.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:shelf/shelf.dart'; |
| |
| abstract class LoadStrategy { |
| final AssetReader _assetReader; |
| final String? _packageConfigPath; |
| final _providers = <String, MetadataProvider>{}; |
| |
| LoadStrategy(this._assetReader, {String? packageConfigPath}) |
| : _packageConfigPath = packageConfigPath ?? _findPackageConfigFilePath(); |
| |
| /// The ID for this strategy. |
| /// |
| /// This ID is passed to the injected client so that it can react accordingly. |
| String get id; |
| |
| /// Module format for JS generated by the compiler. |
| /// |
| /// Module format is passed to the expression compilation worker during |
| /// expression evaluation. |
| String get moduleFormat; |
| |
| /// Module containing code for loading libraries. |
| /// |
| /// Used for preventing stepping into the library loading code. |
| String get loadLibrariesModule; |
| |
| /// Returns a snippet of JS code that can be used to load a JS module. |
| /// |
| /// The snippet should be a reference to a function that takes a single |
| /// argument which is the module name to load. |
| String get loadModuleSnippet; |
| |
| /// Provides a runtime debugger for the Dart runtime. |
| DartRuntimeDebugger get dartRuntimeDebugger; |
| |
| /// The relative root path for library paths. The current directory will be |
| /// used if this is not overridden. |
| String? get libraryRoot => null; |
| |
| /// The reload configuration for this strategy, e.g. liveReload. |
| ReloadConfiguration get reloadConfiguration; |
| |
| /// App build settings, such as entry point, build flags, app kind etc. |
| BuildSettings get buildSettings; |
| |
| /// A handler for strategy specific requests. |
| /// |
| /// Used as a part of the injected_handler middleware. |
| Handler get handler; |
| |
| /// Returns a loader to read the content of the package configuration. |
| /// |
| /// The package configuration URIs will be resolved relative to |
| /// [packageConfigPath], but the loader can read the config from a different |
| /// location. |
| /// |
| /// If null, the default loader will read from [packageConfigPath]. |
| Future<Uint8List?> Function(Uri uri)? get packageConfigLoader => null; |
| |
| /// The absolute path to the app's package configuration. |
| String get packageConfigPath { |
| return _packageConfigPath ?? _defaultPackageConfigPath; |
| } |
| |
| /// The default package config path if none is provided. |
| String get _defaultPackageConfigPath => |
| p.join(DartUri.currentDirectory, '.dart_tool', 'package_config.json'); |
| |
| /// Returns the absolute file path of the `package_config.json` file in the `.dart_tool` |
| /// directory, searching recursively from the current directory hierarchy. |
| static String? _findPackageConfigFilePath() { |
| var candidateDir = Directory(DartUri.currentDirectory).absolute; |
| |
| while (true) { |
| final candidatePackageConfigFile = File( |
| p.join(candidateDir.path, '.dart_tool', 'package_config.json'), |
| ); |
| |
| if (candidatePackageConfigFile.existsSync()) { |
| return candidatePackageConfigFile.path; |
| } |
| |
| final parentDir = candidateDir.parent; |
| if (parentDir.path == candidateDir.path) { |
| // We've reached the root directory |
| return null; |
| } |
| |
| candidateDir = parentDir; |
| } |
| } |
| |
| /// Returns the bootstrap required for this [LoadStrategy]. |
| /// |
| /// The bootstrap is appended to the end of the entry point module. |
| Future<String> bootstrapFor(String entrypoint); |
| |
| /// JS code snippet for loading the injected client script. |
| String loadClientSnippet(String clientScript); |
| |
| /// Returns the module for the corresponding server path. |
| /// |
| /// For example: |
| /// |
| /// /packages/path/path.ddc.js -> packages/path/path |
| /// |
| Future<String?> moduleForServerPath(String entrypoint, String serverPath); |
| |
| /// Returns the server path for the provided module. |
| /// |
| /// For example: |
| /// |
| /// web/main -> main.ddc.js |
| /// |
| Future<String?> serverPathForModule(String entrypoint, String module); |
| |
| /// Returns the source map path for the provided module. |
| /// |
| /// For example: |
| /// |
| /// web/main -> main.ddc.js.map |
| /// |
| Future<String?> sourceMapPathForModule(String entrypoint, String module); |
| |
| /// Returns a map from module id to module info for the provided entrypoint. |
| /// |
| /// For example: |
| /// |
| /// web/main -> {main.ddc.full.dill, main.ddc.dill} |
| /// |
| Future<Map<String, ModuleInfo>> moduleInfoForEntrypoint(String entrypoint); |
| |
| /// Returns the server path for the app uri. |
| /// |
| /// For example: |
| /// |
| /// org-dartlang-app://web/main.dart -> main.dart |
| /// |
| /// Will return `null` if the provided uri is not |
| /// an app URI. |
| String? serverPathForAppUri(String appUri); |
| |
| /// Returns the relative path in google3, determined by the [absolutePath]. |
| /// |
| /// Returns `null` if not a google3 app. |
| String? g3RelativePath(String absolutePath); |
| |
| /// Returns the [MetadataProvider] for the application located at the provided |
| /// [entrypoint]. |
| MetadataProvider metadataProviderFor(String entrypoint) { |
| if (_providers.containsKey(entrypoint)) { |
| return _providers[entrypoint]!; |
| } else { |
| throw StateError('No metadata provider for $entrypoint'); |
| } |
| } |
| |
| /// Creates and returns a [MetadataProvider] with the given [entrypoint] and |
| /// [reader]. |
| MetadataProvider createProvider(String entrypoint, AssetReader reader) => |
| MetadataProvider(entrypoint, reader); |
| |
| /// Initializes a [MetadataProvider] for the application located at the |
| /// provided [entrypoint]. |
| Future<void> trackEntrypoint(String entrypoint) { |
| final metadataProvider = createProvider(entrypoint, _assetReader); |
| _providers[metadataProvider.entrypoint] = metadataProvider; |
| // Returns a Future so that the asynchronous g3-implementation can override |
| // this method: |
| return Future.value(); |
| } |
| |
| Future<ModifiedModuleReport> reinitializeProviderAfterHotReload( |
| String entrypoint, |
| Map<String, List> reloadedModulesToLibraries, |
| ) { |
| final provider = _providers[entrypoint]!; |
| return provider.reinitializeAfterHotReload(reloadedModulesToLibraries); |
| } |
| } |
| |
| enum ReloadConfiguration { none, hotReload, hotRestart, liveReload } |
| |
| /// App build settings. |
| /// |
| /// We use load strategy to determine the build settings for the app. |
| /// Note that some load strategies need to read those arguments from |
| /// the build metadata as they are not always available until the app |
| /// is built and loaded. |
| class BuildSettings { |
| final Uri? appEntrypoint; |
| final bool canaryFeatures; |
| final bool isFlutterApp; |
| final List<String> experiments; |
| |
| const BuildSettings({ |
| this.appEntrypoint, |
| this.canaryFeatures = false, |
| this.isFlutterApp = true, |
| this.experiments = const <String>[], |
| }); |
| } |