Skip to content

metro-file-map: Let plugins report what changed in a batch, on the change event - #1953

Draft
robhogan wants to merge 1 commit into
robhogan/observe-haste-namesfrom
robhogan/plugin-change-summaries
Draft

robhogan wants to merge 1 commit into
robhogan/observe-haste-namesfrom
robhogan/plugin-change-summaries

Conversation

@robhogan

@robhogan robhogan commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Context

A file map plugin often knows things about a batch of changes that nothing else can work out. For Haste, #1952 records the names a resolution looked up, and invalidating from those means knowing which names a change rebound. The ChangeEvent carries paths, and by the time a consumer sees it a removed file's name is already gone from the map, but HastePlugin.onChanged has it to hand:

onChanged(delta: ReadonlyFileSystemChanges<?string>): void {
// Process removals first so that moves aren't treated as duplicates.
for (const [canonicalPath, maybeHasteId] of delta.removedFiles) {
this.#onRemovedFile(canonicalPath, maybeHasteId);
}

This change

This lets a plugin's onChanged return a summary, published on ChangeEvent.pluginChanges by plugin name. The summary type defaults to void, so existing plugins are unaffected. HastePlugin reports changedNames.

Plugin names now have to be unique across all plugins, not just those with a worker. Persisted state was already keyed by name, so two sharing a name were already overwriting each other.

Not addressed here: on a cold start HastePlugin builds a summary nobody reads (transient, and nothing where Haste is inert), and an edit that changes a file's Haste name is neither applied to the map nor reported, which predates this.

Invalidating from the summary is the next PR.

Changelog: Internal

Test plan

New unit tests cover the watch and recrawl paths and duplicate plugin names.

@robhogan
robhogan added this pull request to stack #1947 September 18, 2026 21:05
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 18, 2026
@robhogan
robhogan force-pushed the robhogan/plugin-change-summaries branch from 128f7ac to 6a3fc0e Compare September 25, 2026 18:04
@robhogan
robhogan force-pushed the robhogan/plugin-change-summaries branch from 6a3fc0e to 5de1b40 Compare September 29, 2026 14:30
@robhogan
robhogan force-pushed the robhogan/plugin-change-summaries branch from 5de1b40 to aa75dc8 Compare September 29, 2026 14:36
@robhogan
robhogan removed this pull request from stack #1947 October 9, 2026 15:33
@robhogan
robhogan force-pushed the robhogan/plugin-change-summaries branch from aa75dc8 to 45d155a Compare October 9, 2026 15:34
@robhogan
robhogan added this pull request to stack #2045 October 9, 2026 15:34
…ange event

Stacked on #1952.

## Summary
A file map plugin often knows things about a batch of changes that nothing else can work out. The motivating case is Haste - #1952 records the Haste names a resolution looks up, and to invalidate from those a consumer needs to know which names were rebound by a change. It can't derive that from the `ChangeEvent`, which carries paths and modified times, and by the time it sees the event a removed file's name is already gone from the map. `HastePlugin` has it to hand - its `onChanged` is given each removed file's old name and each added file's new one.

This gives plugins a way to say so. `FileMapPlugin.onChanged` may now return a summary, and the file map publishes what each plugin returned as `ChangeEvent.pluginChanges`, by plugin name. A plugin that returns nothing is absent, so existing plugins are unaffected - the summary is a third type parameter on `FileMapPlugin` that defaults to `void`. `getPluginChanges(event, plugin)` reads an entry with the plugin's own summary type, so a consumer never writes the name or casts.

The summary describes exactly the batch in `changes`. Plugins are already updated synchronously, immediately before the emit, so there's no ordering for a consumer to get wrong - which is the problem with the alternative of asking the plugin afterwards. There are two emit sites: the batched watch path updates plugins inline, and a recrawl updates them inside `#applyFileDelta`, which now returns what they reported along with the changes. The initial build goes through `#applyFileDelta` too but emits nothing, so its summaries go unused.

`HastePlugin` is the first user. It reports `changedNames`, the names bound or unbound for any platform by the files added and removed - a bare name, matching what #1952 records.

Plugin names now have to be unique across all of a file map's plugins. That was only checked for plugins with a worker, but persisted plugin state is keyed by name for every plugin, so two that shared a name would already have overwritten each other's state.

Two things worth knowing:
 - On a cold start `onChanged` sees every file as added, so `HastePlugin` builds a set of every name for a summary nobody reads. That's nothing where Haste is inert, which is Metro's default, and transient otherwise. If it matters, the alternative is an optional method the file map calls only for a batch it's about to emit.
 - A modified file reaches plugins in `modifiedFiles` with its new data only, and `HastePlugin.onChanged` doesn't read that list. So an edit that changes a file's Haste name (a docblock, or a global package's `name`) is neither applied to the map nor reported here. That predates this, and can't happen with a path-based `hasteImpl`. `FileSystemChangeAggregator` keeps each file's pre-batch metadata, so giving plugins the old value is feasible, separately.

Nothing consumes this yet.

## Test plan
```
yarn jest packages/metro-file-map packages/metro-resolver packages/metro/src/node-haste packages/metro/src/DeltaBundler packages/metro/src/integration_tests packages/metro/src/__tests__ packages/metro/src/Server
yarn flow check
yarn typecheck-ts
yarn verify-api-snapshots
```
 - `HastePlugin-test.js`: `onChanged` reports the names bound and unbound, and nothing when no file with a name was added or removed.
 - `index-test.js`: in watch mode an added and a removed file put both names on the event, read back through `getPluginChanges`, and plugins with nothing to say are absent. A recrawl publishes what plugins reported too. Two plugins sharing a name throw, with or without a worker.

Changelog: Internal
@robhogan
robhogan force-pushed the robhogan/plugin-change-summaries branch from 45d155a to d42120c Compare October 9, 2026 15:42

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant