| // 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? librariesSpec, | |
| 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 { | |
| final feServer = await FrontendServerClient.start( | |
| entrypoint, | |
| outputDillPath, | |
| platformKernel ?? _dartdevcPlatformKernel, | |
| dartdevcModuleFormat: dartdevcModuleFormat, | |
| debug: debug, | |
| enableHttpUris: enableHttpUris, | |
| fileSystemRoots: fileSystemRoots, | |
| fileSystemScheme: fileSystemScheme, | |
| frontendServerPath: frontendServerPath, | |
| librariesSpec: librariesSpec, | |
| 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.dillOutput == 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 (final entry in manifest.entries) { | |
| final metadata = entry.value as Map<String, dynamic>; | |
| final sourceOffsets = metadata['code'] as List; | |
| _assets[entry.key] = sourceBytes.sublist( | |
| sourceOffsets[0] as int, | |
| sourceOffsets[1] as int, | |
| ); | |
| final 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(); | |
| if (_lastResult != null) _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(); | |
| final bootstrapJs = _bootstrapJs; | |
| if (bootstrapJs != null) { | |
| _assets['$_entrypoint.js'] = Uint8List.fromList(utf8.encode(bootstrapJs)); | |
| } | |
| final 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(); |