Skip to content

perf(engine): look for an import's uses in its own file only - #193

Merged
Upd4ting merged 2 commits into
mainfrom
perf/import-references-in-file
Nov 10, 2026
Merged

Upd4ting merged 2 commits into
mainfrom
perf/import-references-in-file

Conversation

@Upd4ting

@Upd4ting Upd4ting commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Before and after a save rewrites a page, the builder asks which named imports the page file uses, so it can prune the ones the rewrite orphaned. It asked once per import with a project-wide find-all-references. That search reads every other file and builds display text for each definition, then keeps only the hits in the page file.

 isReferenced(sourceFile, specifier)              engine/writable.ts
-  specifier.getNameNode().findReferencesAsNodes()  every file, display parts built
+  languageService.getDocumentHighlights(file, binding, [file])  this file only
   keep a use outside the import declaration

The highlights are those of the local binding: the alias, else the name. That is what a use in the file refers to.

Evidence

Evidence level: benchmark (micro and engine operations).

referencedImportNames on the Board page (24 named imports), after an edit, 5 runs:

Pages in the app find-all-references document highlights
1 358 ms (303–958) 32 ms (23–47)
100 3175 ms (2450–5820) 40 ms (28–212)

Both give the same names, including an aliased import that is used (Get as Fetch) and one that is not (Post as Unused).

A save (SetPageBlocks), sides interleaved:

Workload main this PR
10 pages, 3 pairs 2584 ms (2549–2665) 1495 ms (1385–2100)
100 pages, 2 pairs 4529–6626 ms 2285–3843 ms
1 page, on top of the typecheck fix, 5 pairs 676 ms (509–758) 188 ms (167–209)

On main with one page the gain hides inside the whole-program typecheck, which then pays the program rebuild the search used to pay. The typecheck fix removes that.

Cumulative impact: the search runs twice per save, so the cost grows with the number of pages in the app. That is about 0.3 s per save at 1 page and seconds at 100 pages.

The engine specs (test:unit) pass: 523 passing.

Verification

  • pnpm run checks passes. Main fails sync:check on a stale compat row in packages/dms-builder/README.md (interface-dms-builder 0.4.0, published 0.4.1), so a separate commit syncs it. It is the same line build(deps): move to @antelopejs/tooling-configs 0.5.0 #190 carries, byte for byte, so whichever of the two lands second has an empty or trivial conflict there.
  • Verify harness on a stack of the three perf PRs: smoke, blocks (3), pages (3) and resources (4) journeys pass. Dashboard save, Save click to the server's answer, 15 saves per side: 2325 ms (2010–3792) on main, 630 ms (440–1102) with the three PRs.
  • formal-page-save: the same output on main and on this branch. The only finding is the recorded F2 (Moving a page with an unsaved draft lets the next Save overwrite an outside change without the stale banner #163), which none of these lines touch. The gate flags the covered code's emit as changed.

Merge Danger

Door: two-way.

Blast Radius: import pruning on save.

Two theoretical forms differ, and the reviews found neither in a page the builder writes. The first is an aliased import whose export does not resolve: it is now counted as used. The typecheck refuses that save either way. The second is the same export bound twice through different names: the unused one is now kept, as pruneUnusedImports intends.

@Upd4ting
Upd4ting merged commit da213a8 into main Nov 10, 2026
13 checks passed
@Upd4ting
Upd4ting deleted the perf/import-references-in-file branch November 10, 2026 16:24
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.

1 participant