Skip to content

Add minimal bootstrapper for ddc_library_bundle format #2523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dwds/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Replace deprecated JS code `this.__proto__` with `Object.getPrototypeOf(this)`. - [#2500](https://github.com/dart-lang/webdev/pull/2500)
- Migrate injected client code to `package:web`. - [#2491](https://github.com/dart-lang/webdev/pull/2491)
- Deprecated MetadataProvider's, CompilerOptions', SdkConfiguration's & SdkLayout's soundNullSafety. - [#2427](https://github.com/dart-lang/webdev/issues/2427)
- Add load strategy and an unimplemented hot restart strategy for DDC library bundle format.

## 24.1.0

Expand Down
1,428 changes: 708 additions & 720 deletions dwds/lib/src/injected/client.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dwds/lib/src/loaders/ddc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ String removeJsExtension(String path) =>
String addJsExtension(String path) => '$path.js';

/// JavaScript snippet to determine the base URL of the current path.
const _baseUrlScript = '''
const baseUrlScript = '''
var baseUrl = (function () {
// Attempt to detect --precompiled mode for tests, and set the base url
// appropriately, otherwise set it to '/'.
Expand Down Expand Up @@ -179,7 +179,7 @@ class DdcStrategy extends LoadStrategy {
scripts.add(<String, String>{'src': '$path.js', 'id': name});
});
return '''
$_baseUrlScript
$baseUrlScript
var scripts = ${const JsonEncoder.withIndent(" ").convert(scripts)};
window.\$dartLoader.loadConfig.loadScriptFn = function(loader) {
loader.addScriptsToQueue(scripts, null);
Expand Down
193 changes: 193 additions & 0 deletions dwds/lib/src/loaders/ddc_library_bundle.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright (c) 2024, 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:convert';

import 'package:dwds/src/debugging/metadata/provider.dart';
import 'package:dwds/src/loaders/ddc.dart';
import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/readers/asset_reader.dart';
import 'package:dwds/src/services/expression_compiler.dart';
import 'package:shelf/shelf.dart';

// TODO(srujzs): This is mostly a copy of `DdcStrategy`. Some of the
// functionality in here may not make sense with the library bundle format yet.
class DdcLibraryBundleStrategy extends LoadStrategy {
@override
final ReloadConfiguration reloadConfiguration;

/// Returns a map of module name to corresponding server path (excluding .js)
/// for the provided Dart application entrypoint.
///
/// For example:
///
/// web/main -> main.ddc
/// packages/path/path -> packages/path/path.ddc
///
final Future<Map<String, String>> Function(MetadataProvider metadataProvider)
_moduleProvider;

/// Returns a map of module name to corresponding digest value.
///
/// For example:
///
/// web/main -> 8363b363f74b41cac955024ab8b94a3f
/// packages/path/path -> d348c2a4647e998011fe305f74f22961
///
final Future<Map<String, String>> Function(MetadataProvider metadataProvider)
// ignore: unused_field
_digestsProvider;

/// Returns the module for the corresponding server path.
///
/// For example:
///
/// /packages/path/path.ddc.js -> packages/path/path
///
final Future<String?> Function(
MetadataProvider metadataProvider,
String sourcePath,
) _moduleForServerPath;

/// Returns a map from module id to module info.
///
/// For example:
///
/// web/main -> {main.ddc.full.dill, main.ddc.dill}
///
final Future<Map<String, ModuleInfo>> Function(
MetadataProvider metadataProvider,
) _moduleInfoForProvider;

/// Returns the server path for the provided module.
///
/// For example:
///
/// web/main -> main.ddc.js
///
final Future<String?> Function(
MetadataProvider metadataProvider,
String module,
) _serverPathForModule;

/// Returns the source map path for the provided module.
///
/// For example:
///
/// web/main -> main.ddc.js.map
///
final Future<String?> Function(
MetadataProvider metadataProvider,
String module,
) _sourceMapPathForModule;

/// 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.
final String? Function(String appUri) _serverPathForAppUri;

/// Returns the relative path in google3, determined by the [absolutePath].
///
/// Returns `null` if not a google3 app.
final String? Function(String absolutePath) _g3RelativePath;

final BuildSettings _buildSettings;

DdcLibraryBundleStrategy(
this.reloadConfiguration,
this._moduleProvider,
this._digestsProvider,
this._moduleForServerPath,
this._serverPathForModule,
this._sourceMapPathForModule,
this._serverPathForAppUri,
this._moduleInfoForProvider,
AssetReader assetReader,
this._buildSettings,
this._g3RelativePath,
String? packageConfigPath,
) : super(assetReader, packageConfigPath: packageConfigPath);

@override
Handler get handler => (request) async {
// TODO(markzipan): Implement a hot restarter that uses digests for
// the DDC module system.
return Response.notFound(request.url.toString());
};

@override
String get id => 'ddc-library-bundle';

// DDC doesn't have a 'ddc-library-bundle' format flag. Instead, it treats the
// combination of the DDC module format and canary mode as the DDC library
// bundle format, so we just pass 'ddc' here.
@override
String get moduleFormat => 'ddc';

@override
String get loadLibrariesModule => 'ddc_module_loader.ddk.js';

// TODO(srujzs): Refactor code that uses this to avoid loading individual
// libraries, as that's no longer supported in the new module format.
@override
String get loadModuleSnippet =>
"function() { throw new Error('LoadStrategy.loadModuleSnippet is used. "
"This is currently unsupported in the DDC library bundle format.'); }";

@override
Future<String> bootstrapFor(String entrypoint) async =>
await _ddcLoaderSetup(entrypoint);

@override
String loadClientSnippet(String clientScript) =>
'window.\$dartLoader.forceLoadModule("$clientScript");\n';

Future<String> _ddcLoaderSetup(String entrypoint) async {
final metadataProvider = metadataProviderFor(entrypoint);
final modulePaths = await _moduleProvider(metadataProvider);
final scripts = <Map<String, String?>>[];
modulePaths.forEach((name, path) {
scripts.add(<String, String>{'src': '$path.js', 'id': name});
});
return '''
$baseUrlScript
var scripts = ${const JsonEncoder.withIndent(" ").convert(scripts)};
window.\$dartLoader.loadConfig.loadScriptFn = function(loader) {
loader.addScriptsToQueue(scripts, null);
loader.loadEnqueuedModules();
};
window.\$dartLoader.loader.nextAttempt();
''';
}

@override
Future<String?> moduleForServerPath(String entrypoint, String serverPath) =>
_moduleForServerPath(metadataProviderFor(entrypoint), serverPath);

@override
Future<Map<String, ModuleInfo>> moduleInfoForEntrypoint(String entrypoint) =>
_moduleInfoForProvider(metadataProviderFor(entrypoint));

@override
Future<String?> serverPathForModule(String entrypoint, String module) =>
_serverPathForModule(metadataProviderFor(entrypoint), module);

@override
Future<String?> sourceMapPathForModule(String entrypoint, String module) =>
_sourceMapPathForModule(metadataProviderFor(entrypoint), module);

@override
String? serverPathForAppUri(String appUri) => _serverPathForAppUri(appUri);

@override
BuildSettings get buildSettings => _buildSettings;

@override
String? g3RelativePath(String absolutePath) => _g3RelativePath(absolutePath);
}
33 changes: 33 additions & 0 deletions dwds/lib/src/loaders/frontend_server_strategy_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:dwds/src/debugging/metadata/provider.dart';
import 'package:dwds/src/loaders/ddc.dart';
import 'package:dwds/src/loaders/ddc_library_bundle.dart';
import 'package:dwds/src/loaders/require.dart';
import 'package:dwds/src/loaders/strategy.dart';
import 'package:dwds/src/readers/asset_reader.dart';
Expand Down Expand Up @@ -132,6 +133,38 @@ class FrontendServerDdcStrategyProvider
DdcStrategy get strategy => _ddcStrategy;
}

/// Provides a [DdcLibraryBundleStrategy] suitable for use with the Frontend
/// Server.
class FrontendServerDdcLibraryBundleStrategyProvider
extends FrontendServerStrategyProvider<DdcLibraryBundleStrategy> {
late final DdcLibraryBundleStrategy _libraryBundleStrategy =
DdcLibraryBundleStrategy(
_configuration,
_moduleProvider,
(_) => _digestsProvider(),
_moduleForServerPath,
_serverPathForModule,
_sourceMapPathForModule,
_serverPathForAppUri,
_moduleInfoForProvider,
_assetReader,
_buildSettings,
(String _) => null,
null,
);

FrontendServerDdcLibraryBundleStrategyProvider(
super._configuration,
super._assetReader,
super._packageUriMapper,
super._digestsProvider,
super._buildSettings,
);

@override
DdcLibraryBundleStrategy get strategy => _libraryBundleStrategy;
}

/// Provides a [RequireStrategy] suitable for use with Frontend Server.
class FrontendServerRequireStrategyProvider
extends FrontendServerStrategyProvider<RequireStrategy> {
Expand Down
2 changes: 2 additions & 0 deletions dwds/test/evaluate_common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void testAll({
enableExpressionEvaluation: true,
useDebuggerModuleNames: useDebuggerModuleNames,
verboseCompiler: debug,
canaryFeatures: provider.canaryFeatures,
),
);
});
Expand Down Expand Up @@ -832,6 +833,7 @@ void testAll({
moduleFormat: provider.ddcModuleFormat,
enableExpressionEvaluation: false,
verboseCompiler: debug,
canaryFeatures: provider.canaryFeatures,
),
);
});
Expand Down
15 changes: 9 additions & 6 deletions dwds/test/fixtures/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,13 @@ class TestContext {
buildSettings,
).strategy,
ModuleFormat.ddc => buildSettings.canaryFeatures
? throw Exception(
'''Unsupported DDC module format ${testSettings.moduleFormat.name}
with canaryFeatures set to ${buildSettings.canaryFeatures}.''',
)
? FrontendServerDdcLibraryBundleStrategyProvider(
testSettings.reloadConfiguration,
assetReader,
packageUriMapper,
() async => {},
buildSettings,
).strategy
: FrontendServerDdcStrategyProvider(
testSettings.reloadConfiguration,
assetReader,
Expand All @@ -370,8 +373,8 @@ class TestContext {

final debugPort = await findUnusedPort();
if (testSettings.launchChrome) {
// If the environment variable DWDS_DEBUG_CHROME is set to the string true
// then Chrome will be launched with a UI rather than headless.
// If the environment variable DWDS_DEBUG_CHROME is set to the string
// true then Chrome will be launched with a UI rather than headless.
// If the extension is enabled, then Chrome will be launched with a UI
// since headless Chrome does not support extensions.
final enableDebugExtension = debugSettings.enableDebugExtension;
Expand Down
16 changes: 7 additions & 9 deletions dwds/web/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import 'package:uuid/uuid.dart';
import 'package:web/helpers.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import 'reloader/ddc_library_bundle_restarter.dart';
import 'reloader/ddc_restarter.dart';
import 'reloader/manager.dart';
import 'reloader/require_restarter.dart';
import 'reloader/restarter.dart';
import 'run_main.dart';
import 'web_utils.dart';

Expand All @@ -47,14 +47,12 @@ Future<void>? main() {
? WebSocketClient(WebSocketChannel.connect(fixedUri))
: SseSocketClient(SseClient(fixedPath, debugKey: 'InjectedClient'));

Restarter restarter;
if (dartModuleStrategy == 'require-js') {
restarter = await RequireRestarter.create();
} else if (dartModuleStrategy == 'ddc' || dartModuleStrategy == 'legacy') {
restarter = DdcRestarter();
} else {
throw StateError('Unknown module strategy: $dartModuleStrategy');
}
final restarter = switch (dartModuleStrategy) {
'require-js' => await RequireRestarter.create(),
'ddc-library-bundle' => DdcLibraryBundleRestarter(),
'ddc' || 'legacy' => DdcRestarter(),
_ => throw StateError('Unknown module strategy: $dartModuleStrategy')
};

final manager = ReloadingManager(client, restarter);

Expand Down
16 changes: 16 additions & 0 deletions dwds/web/reloader/ddc_library_bundle_restarter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2024, 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:async';

import 'restarter.dart';

class DdcLibraryBundleRestarter implements Restarter {
@override
Future<bool> restart({String? runId, Future? readyToRunMain}) async {
throw UnimplementedError(
"Hot restart isn't supported for the DDC library bundle format yet.",
);
}
}
4 changes: 4 additions & 0 deletions frontend_server_common/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.3-wip

- Add bootstrapping code for DDC library bundle format.

## 0.2.2

- Start the frontend server from the AOT snapshot shipped in the Dart SDK.
Expand Down
Loading