diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 814cfe500..a98ce7a95 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -3,6 +3,7 @@ - Bump `build_web_compilers` to ^4.4.1. - Remove unused `clientFuture` arg from `DwdsVmClient` methods. - Fix pausing starting of `main` after the hot restart. +- Updating bootstrapper for DDC library bundler module format + Frontend Server. ## 26.2.2 diff --git a/dwds/lib/src/loaders/ddc_library_bundle.dart b/dwds/lib/src/loaders/ddc_library_bundle.dart index 58a0fc949..bfe74b88e 100644 --- a/dwds/lib/src/loaders/ddc_library_bundle.dart +++ b/dwds/lib/src/loaders/ddc_library_bundle.dart @@ -195,15 +195,19 @@ class DdcLibraryBundleStrategy extends LoadStrategy { modulePaths.forEach((name, path) { scripts.add({'src': '$path.js', 'id': name}); }); - return ''' -$baseUrlScript + // canary-mode uses the Frontend Server, which begins script loads via a + // separate pathway. + final scriptLoader = buildSettings.canaryFeatures + ? ''' var scripts = ${const JsonEncoder.withIndent(" ").convert(scripts)}; window.\$dartLoader.loadConfig.loadScriptFn = function(loader) { loader.addScriptsToQueue(scripts, null); loader.loadEnqueuedModules(); }; window.\$dartLoader.loader.nextAttempt(); -'''; +''' + : ''; + return '$baseUrlScript\n$scriptLoader'; } @override diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 52e8eef52..6f7aa961b 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.8.1-wip + +- Adding initial DDC library bundler support behind the `canary` flag. + ## 3.8.0-wip - Bump minimum SDK constraint to 3.10.0 diff --git a/webdev/lib/src/command/build_command.dart b/webdev/lib/src/command/build_command.dart index 7b68ffbf4..9a8a7c6f8 100644 --- a/webdev/lib/src/command/build_command.dart +++ b/webdev/lib/src/command/build_command.dart @@ -93,7 +93,8 @@ class BuildCommand extends Command { DefaultBuildTarget( (b) => b ..target = configuration.outputInput - ..outputLocation = outputLocation?.toBuilder(), + ..outputLocation = outputLocation?.toBuilder() + ..reportChangedAssets = true, ), ); client.startBuild(); diff --git a/webdev/lib/src/command/shared.dart b/webdev/lib/src/command/shared.dart index b6c9f9f4d..132009c7a 100644 --- a/webdev/lib/src/command/shared.dart +++ b/webdev/lib/src/command/shared.dart @@ -126,6 +126,24 @@ List buildRunnerArgs(Configuration configuration) { arguments.add('--enable-experiment=$experiment'); } + if (configuration.canaryFeatures) { + arguments + ..add('--define') + ..add('build_web_compilers|sdk_js=web-hot-reload=true'); + arguments + ..add('--define') + ..add('build_web_compilers|entrypoint=web-hot-reload=true'); + arguments + ..add('--define') + ..add('build_web_compilers|entrypoint_marker=web-hot-reload=true'); + arguments + ..add('--define') + ..add('build_web_compilers|ddc=web-hot-reload=true'); + arguments + ..add('--define') + ..add('build_web_compilers|ddc_modules=web-hot-reload=true'); + } + return arguments; } diff --git a/webdev/lib/src/serve/dev_workflow.dart b/webdev/lib/src/serve/dev_workflow.dart index e950620f3..e7212eb6c 100644 --- a/webdev/lib/src/serve/dev_workflow.dart +++ b/webdev/lib/src/serve/dev_workflow.dart @@ -144,7 +144,8 @@ void _registerBuildTargets( DefaultBuildTarget( (b) => b ..target = target - ..outputLocation = outputLocation?.toBuilder(), + ..outputLocation = outputLocation?.toBuilder() + ..reportChangedAssets = true, ), ); } @@ -161,7 +162,8 @@ void _registerBuildTargets( DefaultBuildTarget( (b) => b ..target = '' - ..outputLocation = outputLocation.toBuilder(), + ..outputLocation = outputLocation.toBuilder() + ..reportChangedAssets = true, ), ); } diff --git a/webdev/lib/src/serve/utils.dart b/webdev/lib/src/serve/utils.dart index e6e062a67..7bde46c8c 100644 --- a/webdev/lib/src/serve/utils.dart +++ b/webdev/lib/src/serve/utils.dart @@ -106,3 +106,27 @@ String? findPackageConfigFilePath() { candidateDir = parentDir; } } + +/// Returns the absolute file path of the `package_config.json` file in the `.dart_tool` +/// directory, searching recursively from the current directory hierarchy. +Uri? findPackageConfigUri() { + var candidateDir = Directory(p.current).absolute; + + while (true) { + final candidatePackageConfigFile = File( + p.join(candidateDir.path, '.dart_tool', 'package_config.json'), + ); + + if (candidatePackageConfigFile.existsSync()) { + return candidatePackageConfigFile.uri; + } + + final parentDir = candidateDir.parent; + if (parentDir.path == candidateDir.path) { + // We've reached the root directory + return null; + } + + candidateDir = parentDir; + } +} diff --git a/webdev/lib/src/serve/webdev_server.dart b/webdev/lib/src/serve/webdev_server.dart index b1604d96c..91cf9d595 100644 --- a/webdev/lib/src/serve/webdev_server.dart +++ b/webdev/lib/src/serve/webdev_server.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:build_daemon/data/build_status.dart' as daemon; @@ -10,6 +11,7 @@ import 'package:dds/devtools_server.dart'; import 'package:dwds/data/build_result.dart'; import 'package:dwds/dwds.dart'; import 'package:dwds/sdk_configuration.dart'; +import 'package:file/local.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; import 'package:http_multi_server/http_multi_server.dart'; @@ -21,10 +23,14 @@ import '../command/configuration.dart'; import '../util.dart'; import 'chrome.dart'; import 'handlers/favicon_handler.dart'; -import 'utils.dart' show findPackageConfigFilePath; +import 'utils.dart' show findPackageConfigFilePath, findPackageConfigUri; Logger _logger = Logger('WebDevServer'); +const reloadedSourcesFileName = 'reloaded_sources.json'; +const jsLibraryBundleExtension = '.ddc.js'; +const multiRootScheme = 'org-dartlang-app'; + class ServerOptions { final Configuration configuration; final int port; @@ -80,6 +86,7 @@ class WebDevServer { ServerOptions options, Stream buildResults, ) async { + final basePath = 'http://localhost:${options.port}'; var pipeline = const Pipeline(); if (options.configuration.logRequests) { @@ -88,8 +95,47 @@ class WebDevServer { pipeline = pipeline.addMiddleware(interceptFavicon); + /// JSON-ifiable list of sources that were reloaded in this restart and + /// follows the following format: + /// + /// `src`: A string that corresponds to the file path containing a DDC library + /// bundle. To support embedded libraries, the path should include the + /// `baseUri` of the web server. + /// `module`: The name of the library bundle in `src`. + /// `libraries`: An array of strings containing the libraries that were + /// compiled in `src`. + /// + /// For example: + /// ```json + /// [ + /// { + /// "src": "/", + /// "module": "", + /// "libraries": ["", ""], + /// }, + /// ] + /// ``` + /// + /// The path of the output file should stay consistent across the lifetime of + /// the app. + final reloadedSources = >[]; + // Only provide relevant build results final filteredBuildResults = buildResults.asyncMap((results) { + if (options.configuration.canaryFeatures) { + // Clear reloaded sources for the new build results. + reloadedSources.clear(); + results.changedAssets?.forEach((uri) { + if (uri.path.endsWith(jsLibraryBundleExtension)) { + final reloadedSource = { + 'src': ddcUriToSourceUrl(basePath, options.target, uri), + 'module': ddcUriToLibraryId(uri), + 'libraries': [ddcUriToLibraryId(uri)], + }; + reloadedSources.add(reloadedSource); + } + }); + } final result = results.results.firstWhere( (result) => result.target == options.target, ); @@ -132,20 +178,39 @@ class WebDevServer { // the load strategy? final buildSettings = BuildSettings( appEntrypoint: Uri.parse( - 'org-dartlang-app:///${options.target}/main.dart', + '$multiRootScheme:///${options.target}/main.dart', ), canaryFeatures: options.configuration.canaryFeatures, isFlutterApp: false, experiments: options.configuration.experiments, ); - final loadStrategy = BuildRunnerRequireStrategyProvider( - assetHandler, - options.configuration.reload, - assetReader, - buildSettings, - packageConfigPath: findPackageConfigFilePath(), - ).strategy; + final LoadStrategy loadStrategy; + if (options.configuration.canaryFeatures) { + final frontendServerFileSystem = LocalFileSystem(); + final packageUriMapper = await PackageUriMapper.create( + frontendServerFileSystem, + findPackageConfigUri()!, + useDebuggerModuleNames: false, + ); + loadStrategy = FrontendServerDdcLibraryBundleStrategyProvider( + options.configuration.reload, + assetReader, + packageUriMapper, + () async => {}, + buildSettings, + packageConfigPath: findPackageConfigFilePath(), + reloadedSourcesUri: Uri.parse('$basePath/$reloadedSourcesFileName'), + ).strategy; + } else { + loadStrategy = BuildRunnerRequireStrategyProvider( + assetHandler, + options.configuration.reload, + assetReader, + buildSettings, + packageConfigPath: findPackageConfigFilePath(), + ).strategy; + } if (options.configuration.enableExpressionEvaluation) { ddcService = ExpressionCompilerService( @@ -161,8 +226,10 @@ class WebDevServer { final debugSettings = DebugSettings( enableDebugExtension: options.configuration.debugExtension, enableDebugging: options.configuration.debug, + // ignore: deprecated_member_use spawnDds: !options.configuration.disableDds, expressionCompiler: ddcService, + // ignore: deprecated_member_use devToolsLauncher: shouldServeDevTools ? (String hostname) async { final server = await DevToolsServer().serveDevTools( @@ -191,6 +258,18 @@ class WebDevServer { ); pipeline = pipeline.addMiddleware(dwds.middleware); cascade = cascade.add(dwds.handler); + if (options.configuration.canaryFeatures) { + // Add a handler to serve reloaded sources. + cascade = cascade.add((Request request) { + if (request.url.path == reloadedSourcesFileName) { + return Response.ok( + jsonEncode(reloadedSources), + headers: {'Content-Type': 'application/json'}, + ); + } + return Response.notFound(''); + }); + } cascade = cascade.add(assetHandler); } else { cascade = cascade.add(assetHandler); @@ -233,3 +312,58 @@ class WebDevServer { ); } } + +/// Transforms a package:build JS asset id [uri] into a source url compatible +/// with DDC's bootstrapper. +/// +/// [basePath] is the path from which JS files as served up to but not +/// including the path. +/// [target] is the path whose files will be served from [basePath]. +/// [uri] is the asset id's uri being transformed. +/// +/// Example: +/// basePath: http://localhost:8080 +/// target: web +/// +/// uri: asset:some_package/web/main.ddc.js +/// returns http://localhost:8080/main.ddc.js +/// +/// uri: package:some_package/src/sub_dir/file.ddc.js +/// returns http://localhost:8080/some_package/src/sub_dir/file.ddc.js +String ddcUriToSourceUrl(String basePath, String target, Uri uri) { + String jsPath; + if (uri.isScheme('asset')) { + // This indicates that this asset is the 'main' web asset. We directly + // serve all files under the package's [target] directory. + var pathParts = uri.pathSegments.skip(1); + if (pathParts.first == target) { + pathParts = pathParts.skip(1); + } + jsPath = pathParts.join('/'); + } else if (uri.isScheme('package')) { + jsPath = 'packages/${uri.path}'; + } else { + jsPath = uri.path; + } + return '$basePath/$jsPath'; +} + +/// Transforms a package:build JS asset id [uri] into a library id compatible +/// with DDC's bootstrapper. +/// +/// Example: +/// uri: asset:some_package/web/main.ddc.js +/// returns org-dartlang-app:///web/main.dart +/// +/// uri: package:some_package/src/sub_dir/file.ddc.js +/// returns package:some_package/src/sub_dir/file.dart +String ddcUriToLibraryId(Uri uri) { + final jsPath = uri.isScheme('package') + ? 'package:${uri.path}' + : '$multiRootScheme:///${uri.path}'; + final prefix = jsPath.substring( + 0, + jsPath.length - jsLibraryBundleExtension.length, + ); + return '$prefix.dart'; +} diff --git a/webdev/lib/src/version.dart b/webdev/lib/src/version.dart index f0d3ee90c..c65a75c91 100644 --- a/webdev/lib/src/version.dart +++ b/webdev/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '3.8.0-wip'; +const packageVersion = '3.8.1-wip'; diff --git a/webdev/pubspec.yaml b/webdev/pubspec.yaml index 94e62e0dd..9f429dad0 100644 --- a/webdev/pubspec.yaml +++ b/webdev/pubspec.yaml @@ -1,6 +1,6 @@ name: webdev # Every time this changes you need to run `dart run build_runner build`. -version: 3.8.0-wip +version: 3.8.1-wip # We should not depend on a dev SDK before publishing. # publish_to: none description: >- @@ -19,7 +19,8 @@ dependencies: crypto: ^3.0.2 dds: ^4.1.0 # Pin DWDS to avoid dependency conflicts with vm_service: - dwds: 24.3.11 + dwds: ^25.0.0 + file: ^7.0.1 http: ^1.0.0 http_multi_server: ^3.2.0 io: ^1.0.3