blob: e38cea08395f3d467d7bef7854251de5b65aeb28 [file] [log] [blame]
// 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>[],
});
}