33// BSD-style license that can be found in the LICENSE file.
44
55import 'dart:async' ;
6+ import 'dart:convert' ;
67import 'dart:io' ;
78
89import 'package:build_daemon/data/build_status.dart' as daemon;
910import 'package:dds/devtools_server.dart' ;
1011import 'package:dwds/data/build_result.dart' ;
1112import 'package:dwds/dwds.dart' ;
1213import 'package:dwds/sdk_configuration.dart' ;
14+ import 'package:file/local.dart' ;
1315import 'package:http/http.dart' as http;
1416import 'package:http/io_client.dart' ;
1517import 'package:http_multi_server/http_multi_server.dart' ;
@@ -21,10 +23,14 @@ import '../command/configuration.dart';
2123import '../util.dart' ;
2224import 'chrome.dart' ;
2325import 'handlers/favicon_handler.dart' ;
24- import 'utils.dart' show findPackageConfigFilePath;
26+ import 'utils.dart' show findPackageConfigFilePath, findPackageConfigUri ;
2527
2628Logger _logger = Logger ('WebDevServer' );
2729
30+ const reloadedSourcesFileName = 'reloaded_sources.json' ;
31+ const jsLibraryBundleExtension = '.ddc.js' ;
32+ const multiRootScheme = 'org-dartlang-app' ;
33+
2834class ServerOptions {
2935 final Configuration configuration;
3036 final int port;
@@ -80,6 +86,7 @@ class WebDevServer {
8086 ServerOptions options,
8187 Stream <daemon.BuildResults > buildResults,
8288 ) async {
89+ final basePath = 'http://localhost:${options .port }' ;
8390 var pipeline = const Pipeline ();
8491
8592 if (options.configuration.logRequests) {
@@ -88,8 +95,47 @@ class WebDevServer {
8895
8996 pipeline = pipeline.addMiddleware (interceptFavicon);
9097
98+ /// JSON-ifiable list of sources that were reloaded in this restart and
99+ /// follows the following format:
100+ ///
101+ /// `src` : A string that corresponds to the file path containing a DDC library
102+ /// bundle. To support embedded libraries, the path should include the
103+ /// `baseUri` of the web server.
104+ /// `module` : The name of the library bundle in `src` .
105+ /// `libraries` : An array of strings containing the libraries that were
106+ /// compiled in `src` .
107+ ///
108+ /// For example:
109+ /// ```json
110+ /// [
111+ /// {
112+ /// "src": "<baseUri>/<file_name>",
113+ /// "module": "<module_name>",
114+ /// "libraries": ["<lib1>", "<lib2>"],
115+ /// },
116+ /// ]
117+ /// ```
118+ ///
119+ /// The path of the output file should stay consistent across the lifetime of
120+ /// the app.
121+ final reloadedSources = < Map <String , dynamic >> [];
122+
91123 // Only provide relevant build results
92124 final filteredBuildResults = buildResults.asyncMap <BuildResult >((results) {
125+ if (options.configuration.canaryFeatures) {
126+ // Clear reloaded sources for the new build results.
127+ reloadedSources.clear ();
128+ results.changedAssets? .forEach ((uri) {
129+ if (uri.path.endsWith (jsLibraryBundleExtension)) {
130+ final reloadedSource = {
131+ 'src' : ddcUriToSourceUrl (basePath, options.target, uri),
132+ 'module' : ddcUriToLibraryId (uri),
133+ 'libraries' : [ddcUriToLibraryId (uri)],
134+ };
135+ reloadedSources.add (reloadedSource);
136+ }
137+ });
138+ }
93139 final result = results.results.firstWhere (
94140 (result) => result.target == options.target,
95141 );
@@ -132,20 +178,39 @@ class WebDevServer {
132178 // the load strategy?
133179 final buildSettings = BuildSettings (
134180 appEntrypoint: Uri .parse (
135- 'org-dartlang-app :///${options .target }/main.dart' ,
181+ '$ multiRootScheme :///${options .target }/main.dart' ,
136182 ),
137183 canaryFeatures: options.configuration.canaryFeatures,
138184 isFlutterApp: false ,
139185 experiments: options.configuration.experiments,
140186 );
141187
142- final loadStrategy = BuildRunnerRequireStrategyProvider (
143- assetHandler,
144- options.configuration.reload,
145- assetReader,
146- buildSettings,
147- packageConfigPath: findPackageConfigFilePath (),
148- ).strategy;
188+ final LoadStrategy loadStrategy;
189+ if (options.configuration.canaryFeatures) {
190+ final frontendServerFileSystem = LocalFileSystem ();
191+ final packageUriMapper = await PackageUriMapper .create (
192+ frontendServerFileSystem,
193+ findPackageConfigUri ()! ,
194+ useDebuggerModuleNames: false ,
195+ );
196+ loadStrategy = FrontendServerDdcLibraryBundleStrategyProvider (
197+ options.configuration.reload,
198+ assetReader,
199+ packageUriMapper,
200+ () async => {},
201+ buildSettings,
202+ packageConfigPath: findPackageConfigFilePath (),
203+ reloadedSourcesUri: Uri .parse ('$basePath /$reloadedSourcesFileName ' ),
204+ ).strategy;
205+ } else {
206+ loadStrategy = BuildRunnerRequireStrategyProvider (
207+ assetHandler,
208+ options.configuration.reload,
209+ assetReader,
210+ buildSettings,
211+ packageConfigPath: findPackageConfigFilePath (),
212+ ).strategy;
213+ }
149214
150215 if (options.configuration.enableExpressionEvaluation) {
151216 ddcService = ExpressionCompilerService (
@@ -161,8 +226,10 @@ class WebDevServer {
161226 final debugSettings = DebugSettings (
162227 enableDebugExtension: options.configuration.debugExtension,
163228 enableDebugging: options.configuration.debug,
229+ // ignore: deprecated_member_use
164230 spawnDds: ! options.configuration.disableDds,
165231 expressionCompiler: ddcService,
232+ // ignore: deprecated_member_use
166233 devToolsLauncher: shouldServeDevTools
167234 ? (String hostname) async {
168235 final server = await DevToolsServer ().serveDevTools (
@@ -191,6 +258,18 @@ class WebDevServer {
191258 );
192259 pipeline = pipeline.addMiddleware (dwds.middleware);
193260 cascade = cascade.add (dwds.handler);
261+ if (options.configuration.canaryFeatures) {
262+ // Add a handler to serve reloaded sources.
263+ cascade = cascade.add ((Request request) {
264+ if (request.url.path == reloadedSourcesFileName) {
265+ return Response .ok (
266+ jsonEncode (reloadedSources),
267+ headers: {'Content-Type' : 'application/json' },
268+ );
269+ }
270+ return Response .notFound ('' );
271+ });
272+ }
194273 cascade = cascade.add (assetHandler);
195274 } else {
196275 cascade = cascade.add (assetHandler);
@@ -233,3 +312,58 @@ class WebDevServer {
233312 );
234313 }
235314}
315+
316+ /// Transforms a package:build JS asset id [uri] into a source url compatible
317+ /// with DDC's bootstrapper.
318+ ///
319+ /// [basePath] is the path from which JS files as served up to but not
320+ /// including the path.
321+ /// [target] is the path whose files will be served from [basePath] .
322+ /// [uri] is the asset id's uri being transformed.
323+ ///
324+ /// Example:
325+ /// basePath: http://localhost:8080
326+ /// target: web
327+ ///
328+ /// uri: asset:some_package/web/main.ddc.js
329+ /// returns http://localhost:8080/main.ddc.js
330+ ///
331+ /// uri: package:some_package/src/sub_dir/file.ddc.js
332+ /// returns http://localhost:8080/some_package/src/sub_dir/file.ddc.js
333+ String ddcUriToSourceUrl (String basePath, String target, Uri uri) {
334+ String jsPath;
335+ if (uri.isScheme ('asset' )) {
336+ // This indicates that this asset is the 'main' web asset. We directly
337+ // serve all files under the package's [target] directory.
338+ var pathParts = uri.pathSegments.skip (1 );
339+ if (pathParts.first == target) {
340+ pathParts = pathParts.skip (1 );
341+ }
342+ jsPath = pathParts.join ('/' );
343+ } else if (uri.isScheme ('package' )) {
344+ jsPath = 'packages/${uri .path }' ;
345+ } else {
346+ jsPath = uri.path;
347+ }
348+ return '$basePath /$jsPath ' ;
349+ }
350+
351+ /// Transforms a package:build JS asset id [uri] into a library id compatible
352+ /// with DDC's bootstrapper.
353+ ///
354+ /// Example:
355+ /// uri: asset:some_package/web/main.ddc.js
356+ /// returns org-dartlang-app:///web/main.dart
357+ ///
358+ /// uri: package:some_package/src/sub_dir/file.ddc.js
359+ /// returns package:some_package/src/sub_dir/file.dart
360+ String ddcUriToLibraryId (Uri uri) {
361+ final jsPath = uri.isScheme ('package' )
362+ ? 'package:${uri .path }'
363+ : '$multiRootScheme :///${uri .path }' ;
364+ final prefix = jsPath.substring (
365+ 0 ,
366+ jsPath.length - jsLibraryBundleExtension.length,
367+ );
368+ return '$prefix .dart' ;
369+ }
0 commit comments