Skip to content
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

Add config key invalidation #9597

Merged
merged 15 commits into from
Mar 26, 2024
Merged

Add config key invalidation #9597

merged 15 commits into from
Mar 26, 2024

Conversation

mattcompiles
Copy link
Contributor

@mattcompiles mattcompiles commented Mar 25, 2024

↪️ Pull Request

Currently, when using config stored inside of package.json keys (utilised by the default JS transformer, bundler and JS packager) you end up invalidation your results when anything in the root package.json changes. This causes massive over invalidation, especially in large monorepos that store a huge list of deps and unrelated config in the root.

This PR attempts to address this by adding a new type of node to the RequestGraph called a ConfigKeyNode. This node acts similar to the existing FileNode, however it also stores the package key and a content hash of it's value. When a plugin uses the config.loadConfigFrom API and it resolves to something using the packageKey option this new type of invalidation will be used. When the config request executes, it will calculate a content hash for the package value and store it in the request graph. Then when a file change is detected for the relevant package.json, the file will be read and the content hash validated against the stored one before causing any invalidation to occur.

Things to note:

  • This feature is currently behind a feature flag so we can test it at scale before defaulting the behaviour
  • This does not affect the invalidate on file create systems, they should continue to work as is
  • The content hash is build cached, so we should only calculate them once per key, per build
  • The JS transformer and JS packager have been updated to take advantage of the new API

✔️ PR Todo

  • Added/updated unit tests for this change
  • Included links to related issues/PRs

Copy link
Contributor

@marcins marcins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of minor things, but generally looks good!

@@ -20,6 +20,10 @@ type ConfigOpts = {|
env?: Environment,
result?: ConfigResult,
invalidateOnFileChange?: Set<ProjectPath>,
invalidateOnPackageKeyChange?: Array<{|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to make the invalidation part less “package key” specific and more generic “config key”? I’m thinking for plugin authors it could apply to other config files as well? The current implementation of pacakgeKey in getConfigFrom assumes pacakge.json, but the invalidation at least at the moment supports generic JSON, and could support other formats.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Updated the naming to be config key, rather than package key 👍

let packageKeyNodes = this.packageKeyNodes.get(_filePath);
if (packageKeyNodes && (type === 'delete' || type === 'update')) {
for (let nodeId of packageKeyNodes) {
let isInvalid = type === 'delete';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/nit this indentation is off - should prettier be run against this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it? Prettier is passing for me and I have it setup. Not sure what you're referring to?

Copy link
Contributor

@marcins marcins Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, the let isInvalid is showing the same indentation as the for .. { above, I'd expect it to be indented another level. Anyway, not going to argue with Prettier!

EDIT: huh, indentation is fine when I view the whole file.. must just be something about how GH renders the diff.

fromProjectPath(options.projectRoot, filePath),
);

if (conf == null || conf.config[packageKey] == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The packageKey being removed could be a valid scenario couldn't it? Like if I remove custom config for a transformer that I no longer need because the defaults are file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yeah you're right, good catch.

@@ -3,4 +3,5 @@
export type FeatureFlags = {|
// This feature flag mostly exists to test the feature flag system, and doesn't have any build/runtime effect
+exampleFeature: boolean,
+packageKeyInvalidation: boolean,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I had a "description" for feature flags as part of a richer FF object export, but decided it was unnecessary - however, it would be good to document what the feature flag is for in a comment here at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

assert.equal(b.changedAssets.size, 0);
});

it('should invalidate when package.json config keys change', async function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to comment earlier, should invalidate when config key is removed as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test

source: await options.inputFS.readFile(pkg.filePath, 'utf8'),
filePath: pkg.filePath,
data: conf?.contents,
source: await options.inputFS.readFile(conf.filePath, 'utf8'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we talked about this during pairing, and it's definitely different for the transformer that does this, but this will only read the package.json here once when the packager is initialised, not per package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once per build per worker I believe. However we can definitely improve this overreading in a separate PR.

@mattcompiles mattcompiles changed the title Add package key invalidation Add config key invalidation Mar 25, 2024
@mattcompiles mattcompiles merged commit 84fc9a9 into v2 Mar 26, 2024
16 of 17 checks passed
@mischnic mischnic deleted the package-key-invalidation branch April 4, 2024 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants