blob: 685f7b7466d81ca2138b078a7af218f39f0f2c66 [file] [log] [blame]
// Copyright 2020 The Dart Authors. 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:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:path/path.dart' as p;
import 'dartdevc_bootstrap_amd.dart';
import 'frontend_server_client.dart';
import 'shared.dart';
/// Wraps a [FrontendServerClient] with opinionated web specific functionality,
/// and provides some typical defaults.
///
/// Loads into memory the [CompileResult]s from each [compile] call, and
/// provides access to the up to date sources and source maps.
///
/// Also has the ability to create a bootstrap file for the current entrypoint.
class DartDevcFrontendServerClient implements FrontendServerClient {
final FrontendServerClient _frontendServerClient;
final _assets = <String, Uint8List>{};
final String _entrypoint;
/// The last compile, or `null` once it has been accepted or rejected.
CompileResult? _lastResult;
/// The bootstrap js contents, provided in [_assets] at
/// the path `${_entrypointModule}.js`.
///
/// This is `null` if the module format is not supported for bootstrapping.
final String? _bootstrapJs;
/// The generated main module js contents, provided in [_assets] at
/// the path `${_entrypointModule}.bootstrap.js`.
///
/// This is `null` if the module format is not supported for bootstrapping.
final String? _mainModuleJs;
DartDevcFrontendServerClient._(
this._frontendServerClient, this._entrypoint, String moduleFormat)
: _bootstrapJs = moduleFormat == 'amd'
? generateAmdBootstrapScript(
requireUrl: 'require.js',
mapperUrl: 'dart_stack_trace_mapper.js',
entrypoint: _entrypoint)
: null,
_mainModuleJs = moduleFormat == 'amd'
? generateAmdMainModule(entrypoint: _entrypoint)
: null {
_resetAssets();
}
static Future<DartDevcFrontendServerClient> start(
String entrypoint,
String outputDillPath, {
String dartdevcModuleFormat = 'amd',
bool debug = false,
bool enableHttpUris = false,
List<String> fileSystemRoots = const [], // For `fileSystemScheme` uris,
String fileSystemScheme =
'org-dartlang-root', // Custom scheme for virtual `fileSystemRoots`.
String? frontendServerPath, // Defaults to the snapshot in the sdk.
String packagesJson = '.dart_tool/package_config.json',
String? platformKernel, // Defaults to the dartdevc platform from the sdk.
String? sdkRoot, // Defaults to the current SDK root.
bool verbose = false,
bool printIncrementalDependencies = true,
}) async {
var feServer = await FrontendServerClient.start(
entrypoint,
outputDillPath,
platformKernel ?? _dartdevcPlatformKernel,
dartdevcModuleFormat: dartdevcModuleFormat,
debug: debug,
enableHttpUris: enableHttpUris,
fileSystemRoots: fileSystemRoots,
fileSystemScheme: fileSystemScheme,
frontendServerPath: frontendServerPath,
packagesJson: packagesJson,
sdkRoot: sdkRoot,
target: 'dartdevc',
verbose: verbose,
);
return DartDevcFrontendServerClient._(
feServer, Uri.parse(entrypoint).path, dartdevcModuleFormat);
}
/// Returns the current bytes for the asset at [path].
///
/// The [path] should be exactly as it appears in the
/// [CompileResult.jsManifestOutput] file.
///
/// **Note**: Assets are not updated until `accept` is called after a
/// successful compile. They are not updated if `reject` is called.
///
/// Returns `null` if no asset exists at [path].
///
/// In addition to any DDC compiled assets, this serves
Uint8List? assetBytes(String path) => _assets[path];
/// The contents of a JS file capable of bootstrapping the current app.
///
/// TODO: implement
String bootstrapJs() => throw UnimplementedError();
/// Updates [_assets] for [result].
void _updateAssets(CompileResult? result) {
if (result == null) {
return;
}
final manifest =
jsonDecode(File(result.jsManifestOutput).readAsStringSync())
as Map<String, dynamic>;
final sourceBytes = File(result.jsSourcesOutput).readAsBytesSync();
final sourceMapBytes = File(result.jsSourceMapsOutput).readAsBytesSync();
for (var entry in manifest.entries) {
var metadata = entry.value as Map<String, dynamic>;
var sourceOffsets = metadata['code'] as List;
_assets[entry.key] =
sourceBytes.sublist(sourceOffsets[0] as int, sourceOffsets[1] as int);
var sourceMapOffsets = metadata['sourcemap'] as List;
_assets['${entry.key}.map'] = sourceMapBytes.sublist(
sourceMapOffsets[0] as int, sourceMapOffsets[1] as int);
}
}
@override
Future<CompileResult?> compile([List<Uri>? invalidatedUris]) async {
return _lastResult = await _frontendServerClient.compile(invalidatedUris);
}
@override
Future<CompileResult> compileExpression({
required String expression,
required List<String> definitions,
required bool isStatic,
required String klass,
required String libraryUri,
required List<String> typeDefinitions,
}) =>
throw UnsupportedError(
'Use `compileExpressionToJs` for dartdevc based clients');
@override
Future<CompileResult> compileExpressionToJs({
required String expression,
required int column,
required Map<String, String> jsFrameValues,
required Map<String, String> jsModules,
required String libraryUri,
required int line,
required String moduleName,
}) =>
_frontendServerClient.compileExpressionToJs(
expression: expression,
column: column,
jsFrameValues: jsFrameValues,
jsModules: jsModules,
libraryUri: libraryUri,
line: line,
moduleName: moduleName);
@override
void accept() {
_frontendServerClient.accept();
_updateAssets(_lastResult);
_lastResult = null;
}
@override
Future<void> reject() async {
await _frontendServerClient.reject();
_lastResult = null;
}
@override
void reset() {
_frontendServerClient.reset();
_resetAssets();
}
@override
bool kill({ProcessSignal processSignal = ProcessSignal.sigterm}) =>
_frontendServerClient.kill();
@override
Future<int> shutdown() => _frontendServerClient.shutdown();
/// Clears any previously compiled assets and adds the bootstrap modules as
/// assets if available.
void _resetAssets() {
_assets.clear();
var bootstrapJs = _bootstrapJs;
if (bootstrapJs != null) {
_assets['$_entrypoint.js'] = Uint8List.fromList(utf8.encode(bootstrapJs));
}
var mainModuleJs = _mainModuleJs;
if (mainModuleJs != null) {
_assets['$_entrypoint.bootstrap.js'] =
Uint8List.fromList(utf8.encode(mainModuleJs));
}
}
}
final _dartdevcPlatformKernel =
p.toUri(p.join(sdkDir, 'lib', '_internal', 'ddc_sdk.dill')).toString();