diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md index f83b12416..3baa50d78 100644 --- a/build_runner/CHANGELOG.md +++ b/build_runner/CHANGELOG.md @@ -16,9 +16,10 @@ - Upgrade to `frontend_service_client` version 3. -## 2.3.0 - +## 2.3.3-dev - Add `-d` as a shorthand for `--delete-conflicting-outputs`. +- Added a flag to redirect all not found to a specific + asset: ` not-found-defaults-to` ## 2.2.1 diff --git a/build_runner/lib/src/build_script_generate/bootstrap.dart b/build_runner/lib/src/build_script_generate/bootstrap.dart index 5f4783178..78928dc1b 100644 --- a/build_runner/lib/src/build_script_generate/bootstrap.dart +++ b/build_runner/lib/src/build_script_generate/bootstrap.dart @@ -158,6 +158,7 @@ Future _createKernelIfNeeded(Logger logger) async { scriptKernelCachedLocation, 'lib/_internal/vm_platform_strong.dill', printIncrementalDependencies: false, + enabledExperiments: ['records', 'patterns', 'inline-class'] ); var hadOutput = false; diff --git a/build_runner/lib/src/daemon/asset_server.dart b/build_runner/lib/src/daemon/asset_server.dart index 99f946c30..9bd710c80 100644 --- a/build_runner/lib/src/daemon/asset_server.dart +++ b/build_runner/lib/src/daemon/asset_server.dart @@ -34,7 +34,7 @@ class AssetServer { var cascade = Cascade().add((_) async { await builder.building; return Response.notFound(''); - }).add(AssetHandler(builder.reader, rootPackage).handle); + }).add(AssetHandler(builder.reader, rootPackage, null).handle); var pipeline = Pipeline(); if (options.logRequests) { diff --git a/build_runner/lib/src/entrypoint/options.dart b/build_runner/lib/src/entrypoint/options.dart index f78a844cc..7d88ed3b8 100644 --- a/build_runner/lib/src/entrypoint/options.dart +++ b/build_runner/lib/src/entrypoint/options.dart @@ -26,6 +26,7 @@ const liveReloadOption = 'live-reload'; const logPerformanceOption = 'log-performance'; const logRequestsOption = 'log-requests'; const lowResourcesModeOption = 'low-resources-mode'; +const notFoundDefaultsToOption = 'not-found-defaults-to'; const outputOption = 'output'; const releaseOption = 'release'; const trackPerformanceOption = 'track-performance'; @@ -276,41 +277,45 @@ class ServeOptions extends WatchOptions { final bool logRequests; final List serveTargets; - ServeOptions._({ - required this.hostName, - required this.buildUpdates, - required this.logRequests, - required this.serveTargets, - required Set buildFilters, - required bool deleteFilesByDefault, - required bool enableLowResourcesMode, - required String? configKey, - required Set buildDirs, - required bool outputSymlinksOnly, - required bool trackPerformance, - required bool skipBuildScriptCheck, - required bool verbose, - required Map> builderConfigOverrides, - required bool isReleaseBuild, - required String? logPerformanceDir, - required bool usePollingWatcher, - required List enableExperiments, - }) : super._( - buildFilters: buildFilters, - deleteFilesByDefault: deleteFilesByDefault, - enableLowResourcesMode: enableLowResourcesMode, - configKey: configKey, - buildDirs: buildDirs, - outputSymlinksOnly: outputSymlinksOnly, - trackPerformance: trackPerformance, - skipBuildScriptCheck: skipBuildScriptCheck, - verbose: verbose, - builderConfigOverrides: builderConfigOverrides, - isReleaseBuild: isReleaseBuild, - logPerformanceDir: logPerformanceDir, - usePollingWatcher: usePollingWatcher, - enableExperiments: enableExperiments, - ); + // An [AssetId] path to redirect when the file is not found by + // the web server + final String? notFoundDefaultsTo; + + ServeOptions._( + {required this.hostName, + required this.buildUpdates, + required this.logRequests, + required this.serveTargets, + required Set buildFilters, + required bool deleteFilesByDefault, + required bool enableLowResourcesMode, + required String? configKey, + required Set buildDirs, + required bool outputSymlinksOnly, + required bool trackPerformance, + required bool skipBuildScriptCheck, + required bool verbose, + required Map> builderConfigOverrides, + required bool isReleaseBuild, + required String? logPerformanceDir, + required bool usePollingWatcher, + required List enableExperiments, + required this.notFoundDefaultsTo}) + : super._( + buildFilters: buildFilters, + deleteFilesByDefault: deleteFilesByDefault, + enableLowResourcesMode: enableLowResourcesMode, + configKey: configKey, + buildDirs: buildDirs, + outputSymlinksOnly: outputSymlinksOnly, + trackPerformance: trackPerformance, + skipBuildScriptCheck: skipBuildScriptCheck, + verbose: verbose, + builderConfigOverrides: builderConfigOverrides, + isReleaseBuild: isReleaseBuild, + logPerformanceDir: logPerformanceDir, + usePollingWatcher: usePollingWatcher, + enableExperiments: enableExperiments); factory ServeOptions.fromParsedArgs(ArgResults argResults, Iterable positionalArgs, String rootPackage, Command command) { @@ -381,6 +386,7 @@ class ServeOptions extends WatchOptions { logPerformanceDir: argResults[logPerformanceOption] as String?, usePollingWatcher: argResults[usePollingWatcherOption] as bool, enableExperiments: argResults[enableExperimentOption] as List, + notFoundDefaultsTo: argResults[notFoundDefaultsToOption] as String?, ); } } diff --git a/build_runner/lib/src/entrypoint/serve.dart b/build_runner/lib/src/entrypoint/serve.dart index cfcc2d1d9..b401f4c65 100644 --- a/build_runner/lib/src/entrypoint/serve.dart +++ b/build_runner/lib/src/entrypoint/serve.dart @@ -31,7 +31,9 @@ class ServeCommand extends WatchCommand { ..addFlag(liveReloadOption, defaultsTo: false, negatable: false, - help: 'Enables automatic page reloading on rebuilds. '); + help: 'Enables automatic page reloading on rebuilds. ') + ..addOption(notFoundDefaultsToOption, + help: 'Redirect not found to a specific asset.'); } @override @@ -98,6 +100,7 @@ class ServeCommand extends WatchCommand { logPerformanceDir: options.logPerformanceDir, directoryWatcherFactory: options.directoryWatcherFactory, buildFilters: options.buildFilters, + notFoundDefaultsTo: options.notFoundDefaultsTo ); servers.forEach((target, server) { diff --git a/build_runner/lib/src/generate/build.dart b/build_runner/lib/src/generate/build.dart index 93391c439..abe13400b 100644 --- a/build_runner/lib/src/generate/build.dart +++ b/build_runner/lib/src/generate/build.dart @@ -165,7 +165,8 @@ Future watch(List builders, bool? isReleaseBuild, Map>? builderConfigOverrides, String? logPerformanceDir, - Set? buildFilters}) => + Set? buildFilters, + String? notFoundDefaultsTo}) => watch_impl.watch( builders, assumeTty: assumeTty, @@ -190,4 +191,5 @@ Future watch(List builders, isReleaseBuild: isReleaseBuild, logPerformanceDir: logPerformanceDir, buildFilters: buildFilters, + notFoundDefaultsTo: notFoundDefaultsTo ); diff --git a/build_runner/lib/src/generate/watch_impl.dart b/build_runner/lib/src/generate/watch_impl.dart index a85d307e2..59b40d617 100644 --- a/build_runner/lib/src/generate/watch_impl.dart +++ b/build_runner/lib/src/generate/watch_impl.dart @@ -55,6 +55,7 @@ Future watch( bool? isReleaseBuild, String? logPerformanceDir, Set? buildFilters, + String? notFoundDefaultsTo }) async { builderConfigOverrides ??= const {}; buildDirs ??= {}; @@ -105,7 +106,8 @@ Future watch( .any((target) => target.outputLocation?.path.isNotEmpty ?? false), buildDirs, buildFilters, - isReleaseMode: isReleaseBuild ?? false); + isReleaseMode: isReleaseBuild ?? false, + notFoundDefaultsTo: notFoundDefaultsTo); unawaited(watch.buildResults.drain().then((_) async { await terminator.cancel(); @@ -134,7 +136,8 @@ WatchImpl _runWatch( bool willCreateOutputDirs, Set buildDirs, Set buildFilters, - {bool isReleaseMode = false}) => + {bool isReleaseMode = false, + String? notFoundDefaultsTo}) => WatchImpl( options, environment, @@ -146,7 +149,8 @@ WatchImpl _runWatch( willCreateOutputDirs, buildDirs, buildFilters, - isReleaseMode: isReleaseMode); + isReleaseMode: isReleaseMode, + notFoundDefaultsTo: notFoundDefaultsTo); class WatchImpl implements BuildState { BuildImpl? _build; @@ -172,6 +176,9 @@ class WatchImpl implements BuildState { /// The [PackageGraph] for the current program. final PackageGraph packageGraph; + // An asset path to redirect when the file is not found by the web server + final String? notFoundDefaultsTo; + /// The directories to build upon file changes and where to output them. final Set _buildDirs; @@ -200,7 +207,7 @@ class WatchImpl implements BuildState { this._willCreateOutputDirs, this._buildDirs, this._buildFilters, - {bool isReleaseMode = false}) + {bool isReleaseMode = false, this.notFoundDefaultsTo}) : _debounceDelay = options.debounceDelay, packageGraph = options.packageGraph { buildResults = _run( diff --git a/build_runner/lib/src/server/server.dart b/build_runner/lib/src/server/server.dart index d2d28e8c8..7c24dd09c 100644 --- a/build_runner/lib/src/server/server.dart +++ b/build_runner/lib/src/server/server.dart @@ -47,8 +47,16 @@ ServeHandler createServeHandler(WatchImpl watch) { var rootPackage = watch.packageGraph.root.name; var assetGraphHanderCompleter = Completer(); var assetHandlerCompleter = Completer(); + watch.reader.then((reader) async { - assetHandlerCompleter.complete(AssetHandler(reader, rootPackage)); + final notFoundDefaultsToPath = watch.notFoundDefaultsTo; + final notFoundDefaultsTo = + notFoundDefaultsToPath != null && notFoundDefaultsToPath.isNotEmpty + ? AssetId(rootPackage, notFoundDefaultsToPath) + : null; + + assetHandlerCompleter + .complete(AssetHandler(reader, rootPackage, notFoundDefaultsTo)); assetGraphHanderCompleter .complete(AssetGraphHandler(reader, rootPackage, watch.assetGraph!)); }).catchError((_) {}); // These errors are separately handled. @@ -300,10 +308,11 @@ window.\$dartLoader.forceLoadModule('packages/build_runner/src/server/build_upda class AssetHandler { final FinalizedReader _reader; final String _rootPackage; + final AssetId? notFoundDefaultsTo; final _typeResolver = MimeTypeResolver(); - AssetHandler(this._reader, this._rootPackage); + AssetHandler(this._reader, this._rootPackage, this.notFoundDefaultsTo); Future handle(shelf.Request request, {String rootDir = ''}) => (request.url.path.endsWith('/') || request.url.path.isEmpty) @@ -332,6 +341,18 @@ class AssetHandler { return shelf.Response.notFound( await _findDirectoryList(assetId)); } + + final notFoundDefaultsTo = this.notFoundDefaultsTo; + + if (notFoundDefaultsTo != null) { + final pathSegments = assetId.pathSegments; + + if (!pathSegments.contains('package') && + !pathSegments.contains('lib')) { + return _handle(request, notFoundDefaultsTo); + } + } + return shelf.Response.notFound('Not Found'); default: return shelf.Response.notFound('Not Found'); diff --git a/build_runner/pubspec.yaml b/build_runner/pubspec.yaml index 036bd2a39..7d34efe0a 100644 --- a/build_runner/pubspec.yaml +++ b/build_runner/pubspec.yaml @@ -4,7 +4,7 @@ description: A build system for Dart code generation and modular compilation. repository: https://github.com/dart-lang/build/tree/master/build_runner environment: - sdk: ">=3.0.0-134.0.dev <4.0.0" + sdk: ">=3.0.0-263.0.dev <4.0.0" platforms: linux: diff --git a/build_runner/test/server/serve_handler_test.dart b/build_runner/test/server/serve_handler_test.dart index 9a7b8dbbe..a49d39875 100644 --- a/build_runner/test/server/serve_handler_test.dart +++ b/build_runner/test/server/serve_handler_test.dart @@ -26,6 +26,7 @@ import 'package:web_socket_channel/web_socket_channel.dart'; void main() { late ServeHandler serveHandler; + late ServeHandler serveSpaHandler; late InMemoryRunnerAssetReader reader; late MockWatchImpl watchImpl; late AssetGraph assetGraph; @@ -46,6 +47,9 @@ void main() { packageGraph, assetGraph); serveHandler = createServeHandler(watchImpl); + + watchImpl.notFoundDefaultsTo = 'web/index.html'; + serveSpaHandler = createServeHandler(watchImpl); watchImpl .addFutureResult(Future.value(BuildResult(BuildStatus.success, []))); }); @@ -66,6 +70,13 @@ void main() { expect(await response.readAsString(), 'content'); }); + test('retrieve default asset when not found', () async { + _addSource('a|web/index.html', 'content'); + var response = await serveSpaHandler.handlerFor('web')( + Request('GET', Uri.parse('http://server.com/foo.html'))); + expect(await response.readAsString(), 'content'); + }); + test('caching with etags works', () async { _addSource('a|web/index.html', 'content'); var handler = serveHandler.handlerFor('web'); @@ -450,6 +461,9 @@ class MockWatchImpl implements WatchImpl { Future? _currentBuild; + @override + String? notFoundDefaultsTo; + @override Future? get currentBuild => _currentBuild; @override @@ -457,7 +471,7 @@ class MockWatchImpl implements WatchImpl { throw UnsupportedError('unsupported!'); final _futureBuildResultsController = StreamController>(); - final _buildResultsController = StreamController(); + final _buildResultsController = StreamController.broadcast(); @override Stream get buildResults => _buildResultsController.stream; @@ -475,7 +489,8 @@ class MockWatchImpl implements WatchImpl { _futureBuildResultsController.add(result); } - MockWatchImpl(this.reader, this.packageGraph, this.assetGraph) { + MockWatchImpl(this.reader, this.packageGraph, this.assetGraph, + {this.notFoundDefaultsTo}) { var firstBuild = Completer(); _currentBuild = firstBuild.future; _futureBuildResultsController.stream.listen((futureBuildResult) {