Skip to content

Commit 32bca25

Browse files
authored
Adding incremental Frontend Server-compatible DDC builders + supporting infrastructure (#4240)
Glues FES into build_runner, which is our first step towards hot reload + build_runner. The high level workflow is: 1) A persistently running frontend server is initialized once when a build starts. 2) build_runner requests JS files based on locally modified/generated dart files (as usual). Builders that collect meta-information about DDC modules also - as a side effect - record the main app entrypoint and any locally modified files. 3) When a JS file is requested, the frontend server receives recompile requests via a proxy queue (to maintain communication order with the frontend server). 4) The frontend server processes compilation requests and serves compiled JS files back to `build_runner` (hot-reload ready). Major changes: * Adds a `DdcFrontendServerBuilder` to our set of DDC builders (enabled via the `web-hot-reload` config). This builder keeps a `PersistentFrontendServer` instance alive across rebuilds. Compile/recompile requests are queued via a `FrontendServerProxyDriver` resource. * Uses `scratch_space` to record both 1) the main app entrypoint and 2) updated local files from the `entrypoint_marker` builder and the `module_builder` builder respectively. These are side effects that break certain stateful 'guarantees' of standard build_runner execution. The `entrypoint_marker` builder runs before any of the downstream DDC builders and finds the web entrypoint, as Frontend Server must receive the same entrypoint on every compilation request. * Requires that strongly connected components in both the frontend server and `build_runner` be disabled. Test changes: * Extends `build_test` to permit incremental builds. This involves passing the asset graph + asset reader/writer across build results and only performing cleanup operations after a series of rebuilds. * `build_test` doesn't support `runs_before` and other ordering rules in `build.yaml`, so the above changes allows a kind of imperative ordering, which is important for testing `entrypoint_marker`. Minor changes: * Added a flag to disable strongly connected components in build_web_compilers (implemented using raw ddc meta-modules over clean ddc meta-modules + enforcing fine module aggregation). * Added disposal logic to `scratch_space` so that rebuilds only retain modified files. * Updated `scratch_space` `package_config.json` specs (`packageUri` and `rootUri`). The previous values didn't seem to make sense to me, but I'm also not familiar with how that's standardized in `scratch_space`. * Added `file` and `uuid` deps to `build_modules`. * Moved around some helper functions. * Ported some naming functions from the DDC runtime.
1 parent 6988984 commit 32bca25

37 files changed

+2271
-220
lines changed

build_modules/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
## 5.1.0
2+
3+
- Add drivers and state resources required for DDC + Frontend Server compilation.
4+
- Add an option to disable strongly connected components for determining module boundaries.
5+
16
## 5.0.18
27

3-
- Remove unused dev depencency: `build_runner_core`.
8+
- Remove unused dev dependency: `build_runner_core`.
49
- Allow Dart SDK 3.10.x and 3.11 prerelease.
510

611
## 5.0.17

build_modules/lib/build_modules.dart

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
export 'src/ddc_names.dart';
56
export 'src/errors.dart' show MissingModulesException, UnsupportedModules;
6-
export 'src/kernel_builder.dart'
7-
show KernelBuilder, multiRootScheme, reportUnusedKernelInputs;
7+
export 'src/frontend_server_resources.dart'
8+
show frontendServerState, frontendServerStateResource;
9+
export 'src/kernel_builder.dart' show KernelBuilder, reportUnusedKernelInputs;
810
export 'src/meta_module_builder.dart'
911
show MetaModuleBuilder, metaModuleExtension;
1012
export 'src/meta_module_clean_builder.dart'
1113
show MetaModuleCleanBuilder, metaModuleCleanExtension;
1214
export 'src/module_builder.dart' show ModuleBuilder, moduleExtension;
15+
export 'src/module_library.dart' show ModuleLibrary;
1316
export 'src/module_library_builder.dart'
1417
show ModuleLibraryBuilder, moduleLibraryExtension;
1518
export 'src/modules.dart';
1619
export 'src/platform.dart' show DartPlatform;
1720
export 'src/scratch_space.dart' show scratchSpace, scratchSpaceResource;
18-
export 'src/workers.dart' show dartdevkDriverResource, maxWorkersPerTask;
21+
export 'src/workers.dart'
22+
show
23+
dartdevkDriverResource,
24+
frontendServerProxyDriverResource,
25+
maxWorkersPerTask,
26+
persistentFrontendServerResource;

build_modules/lib/src/common.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:io';
6+
57
import 'package:build/build.dart';
8+
import 'package:path/path.dart' as p;
69
import 'package:scratch_space/scratch_space.dart';
710

11+
const multiRootScheme = 'org-dartlang-app';
12+
const webHotReloadOption = 'web-hot-reload';
13+
final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
14+
final packagesFilePath = p.join('.dart_tool', 'package_config.json');
15+
816
final defaultAnalysisOptionsId = AssetId(
917
'build_modules',
1018
'lib/src/analysis_options.default.yaml',
@@ -16,6 +24,12 @@ String defaultAnalysisOptionsArg(ScratchSpace scratchSpace) =>
1624
enum ModuleStrategy { fine, coarse }
1725

1826
ModuleStrategy moduleStrategy(BuilderOptions options) {
27+
// DDC's Library Bundle module system only supports fine modules since it must
28+
// align with the Frontend Server's library management scheme.
29+
final usesWebHotReload = options.config[webHotReloadOption] as bool? ?? false;
30+
if (usesWebHotReload) {
31+
return ModuleStrategy.fine;
32+
}
1933
final config = options.config['strategy'] as String? ?? 'coarse';
2034
switch (config) {
2135
case 'coarse':

build_web_compilers/lib/src/ddc_names.dart renamed to build_modules/lib/src/ddc_names.dart

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:path/path.dart' as p;
66

7+
/// Logic in this file must be synchronized with their namesakes in DDC at:
8+
/// pkg/dev_compiler/lib/src/compiler/js_names.dart
9+
710
/// Transforms a path to a valid JS identifier.
811
///
9-
/// This logic must be synchronized with [pathToJSIdentifier] in DDC at:
10-
/// pkg/dev_compiler/lib/src/compiler/module_builder.dart
11-
///
1212
/// For backwards compatibility, if this pattern is changed,
1313
/// dev_compiler_bootstrap.dart must be updated to accept both old and new
1414
/// patterns.
@@ -35,12 +35,11 @@ String toJSIdentifier(String name) {
3535
for (var i = 0; i < name.length; i++) {
3636
final ch = name[i];
3737
final needsEscape = ch == r'$' || _invalidCharInIdentifier.hasMatch(ch);
38-
if (needsEscape && buffer == null) {
39-
buffer = StringBuffer(name.substring(0, i));
40-
}
41-
if (buffer != null) {
42-
buffer.write(needsEscape ? '\$${ch.codeUnits.join("")}' : ch);
38+
if (needsEscape) {
39+
buffer ??= StringBuffer(name.substring(0, i));
4340
}
41+
42+
buffer?.write(needsEscape ? '\$${ch.codeUnits.join("")}' : ch);
4443
}
4544

4645
final result = buffer != null ? '$buffer' : name;
@@ -56,7 +55,11 @@ String toJSIdentifier(String name) {
5655
/// Also handles invalid variable names in strict mode, like "arguments".
5756
bool invalidVariableName(String keyword, {bool strictMode = true}) {
5857
switch (keyword) {
59-
// http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words
58+
// https://262.ecma-international.org/6.0/#sec-reserved-words
59+
case 'true':
60+
case 'false':
61+
case 'null':
62+
// https://262.ecma-international.org/6.0/#sec-keywords
6063
case 'await':
6164
case 'break':
6265
case 'case':
@@ -79,7 +82,6 @@ bool invalidVariableName(String keyword, {bool strictMode = true}) {
7982
case 'import':
8083
case 'in':
8184
case 'instanceof':
82-
case 'let':
8385
case 'new':
8486
case 'return':
8587
case 'super':
@@ -99,6 +101,7 @@ bool invalidVariableName(String keyword, {bool strictMode = true}) {
99101
// http://www.ecma-international.org/ecma-262/6.0/#sec-identifiers-static-semantics-early-errors
100102
case 'implements':
101103
case 'interface':
104+
case 'let':
102105
case 'package':
103106
case 'private':
104107
case 'protected':

0 commit comments

Comments
 (0)