-
Couldn't load subscription status.
- Fork 221
Adding incremental Frontend Server-compatible DDC builders + supporting infrastructure #4240
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
base: master
Are you sure you want to change the base?
Conversation
…r fine modules for hot reload
PR HealthChangelog Entry ✔️
Changes to files need to be accounted for in their respective changelogs. This check can be disabled by tagging the PR with |
|
Thanks! This will take me a little work to digest :) I guess I can get to it in 1-2 days. It definitely makes sense to expand support for testing. The details are hard :) ... in particular, I would like to avoid exposing any So I will see if I can come up with a slightly differently suggestion. Currently One possibility would be to go all the way to real builds, I recently added a new type of integration test that I'm pretty happy with
I'll give it some thought :) |
Can you say more about the affect this has on the structure of compilations? Are we doing more work by turning off SCCs? My assumption was that SCCs allowed us to limit the scope of an invalidation. |
|
@davidmorgan Thanks! It's a huge change, so please take your time. I wasn't able to configure it in a prettier way since I'm flying out for two weeks. I'm highly unopinionated wrt testing, but it was definitely a struggle to get @biggs0125 The SCCs here operate on a the import-graph level. DDC's old module system requires that cyclic dependencies be 'unified' into a single module. Both the Frontend Server and build_runner doing this non-deterministically might cause With SCCs enabled, Re: failures. I think some tests are failing due to API changes without pinning new versions? I'll add those (maybe in a separate PR) if the current approach looks sound. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This generally looks good, a few comments re: scratch_space.
Re: testing :)
You sort of don't need to pass assetGraph, it's serialized to the readerWriter and the next build will read it if it's there.
But, the next build will discard the asset graph if any builders changed, so you have to pass the exact same builders each time, which means you will get a full build the first time you call it anyway.
You commented with build.yaml ordering is not respected, and that's true. Adding workarounds for that is not too pretty, it's getting very close to a real build but now with quite a lot of configuration that doesn't exactly match a real build.
Would you be up for trying a different way of testing that is a real build?
I added tests recently that make it easy to set up some packages, run build_runner for real and check the output. Since you want persistent processes, watch mode would make sense, so something like:
| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
There is an example web build using the same test infra here
The test infra is currently internal to build_runner, mainly build_runner/test/common/build_runner_tester.dart, if it looks useful let's just hack around that for now and I'll work out how best to clean it up, I guess adding to build_test is one option.
Package publishing
Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation. |
|
@davidmorgan Wow, the tests in Thanks for the review - back from vacation! |
|
|
||
| /// Bundles information associated with a DDC library. | ||
| class LibraryInfo { | ||
| final String moduleName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the moduleName in the new module system?
| /// Logic in this file must be synchronized with their namesakes in DDC at: | ||
| /// pkg/dev_compiler/lib/src/compiler/js_names.dart | ||
| bool isSdkInternalRuntimeUri(Uri importUri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK to make private?
| return importUri.isScheme('dart') && importUri.path == '_runtime'; | ||
| } | ||
|
|
||
| String libraryUriToJsIdentifier(Uri importUri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the JsIdentifier used for? I see it being used later in this change for moduleName but I'm getting confused from there.
| if (compilerOutput == null) { | ||
| throw Exception('Frontend Server failed to recompile $entrypoint'); | ||
| } | ||
| if (compilerOutput.errorCount != 0 || compilerOutput.errorMessage != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the reload rejection errors flow here and do these errors get handled somewhere else? We want to make sure the user can recover from them, make some edits and reload again right?
| // BSD-style license that can be found in the LICENSE file. | ||
|
|
||
| // ignore_for_file: constant_identifier_names | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point we need to unify all of the implementations of this type of library right? In creating this, have you seen any common parts we can factor out and reuse?
| } | ||
|
|
||
| /// The module name according to ddc for [jsId] which represents the real js | ||
| /// module file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't quite make sense of what the module name according to DDC means. Is this the path to the Javascript file?
| return jsPath.substring(0, jsPath.length - jsModuleExtension.length); | ||
| } | ||
|
|
||
| String ddcLibraryId(AssetId jsId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this is the Dart library import uri? I'm nervous about recreating this from whatever the jsId is. Where does that come from?
|
It looks like you made some changes in response to my comments but I don't see any responses to the comments so I'm not sure if you're done--please let me know if/when I should take another look. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welcome back!
Looking good so far.
There are some CI failures, could you take a look at those please?
| ); | ||
|
|
||
| @override | ||
| void reset() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you don't need this any more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I think you already removed it, so I guess just close...)
| /// Deletes [id] from the [TestReaderWriter] in-memory filesystem. | ||
| void delete(AssetId id); | ||
|
|
||
| /// Resets state in this [TestReaderWriter] between rebuilds. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you don't need this any more?
| }); | ||
| }, timeout: defaultTimeout); | ||
|
|
||
| test('DDC compiled with the Frontend Server', () async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add a TODO for me?
// TODO(davidmorgan): the remaining tests are integration tests of
// the web compilers themselves, support testing like this outside the
// `build_runner` package.
| /// defaults to [BuildLog.failurePattern] so that `expect` will stop if the | ||
| /// process reports a build failure. | ||
| /// | ||
| /// if [expectFailure] is set, then both [pattern] and [failOn] must be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm multiple expectations is a bit awkward, I wonder if there's a nicer way.
Would it work to accumulate lines and return them, then the second expectation can be checked on the result?
Looking at how this method is used, I think maybe something like this:
- most callers are not using the result, so how about the base method is
Future<void> expect. - a few callers are using the single matching line, so how about
Future<String> expectAndGetLinefor that - and then I think you can do what you want with the full accumulated output until the match, how about
Future<List<String>> expectAndGetBlock
?
Whoops, looks like I had unpublished comments that you addressed by coincidence. Sorry about that! Now published. |
Glues FES into build_runner, which is our first step towards hot reload + build_runner.
The high level workflow is:
build_runner(hot-reload ready).Major changes:
DdcFrontendServerBuilderto our set of DDC builders (enabled via theweb-hot-reloadconfig). This builder keeps aPersistentFrontendServerinstance alive across rebuilds. Compile/recompile requests are queued via aFrontendServerProxyDriverresource.scratch_spaceto record both 1) the main app entrypoint and 2) updated local files from theentrypoint_markerbuilder and themodule_builderbuilder respectively. These are side effects that break certain stateful 'guarantees' of standard build_runner execution. Theentrypoint_markerbuilder 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.build_runnerbe disabled.Test changes:
build_testto 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_testdoesn't supportruns_beforeand other ordering rules inbuild.yaml, so the above changes allows a kind of imperative ordering, which is important for testingentrypoint_marker.Minor changes:
scratch_spaceso that rebuilds only retain modified files.scratch_spacepackage_config.jsonspecs (packageUriandrootUri). The previous values didn't seem to make sense to me, but I'm also not familiar with how that's standardized inscratch_space.fileanduuiddeps tobuild_modules.Currently doesn't support live-reloading (functionality appears to have been broken a while ago). This'll be added in an upcoming change and permit
webdev-like auto-hot-reload on save (on top of manual).Enable this by adding the following to a project's
build.yaml: