Repository navigation
fix(react-router): a lazy route follows a barrel that re-exports an import - #2558
Merged
Merged
Conversation
…mport
A lazily loaded folder whose index passes on a page it imports linked
nothing: DigiShop.Frontend's `src/layout/index.ts` does `import { UserLayout }
from './UserLayout'; export { UserLayout }`, and its routes load
`await import('@/layout')`. exportedComponent, the barrel walk behind lazy
routes, React.lazy values and lazy layouts (#2436, #2452, #2480), read the
export clause as naming a declaration in the barrel, which has none: the
name is an import binding. `import Login from './Login'; export default
Login` and `export { Page as Component }` of an imported Page failed the
same way, so all 27 lazy route refs in DigiShop's App.tsx stayed failed.
A binding the module exports but does not declare is now followed through
the module's import of it, a default import as `default` and a named one by
the name it imports, with each file read added to `reads` so lazyModules
still redraws the route. A namespace import or a package's binding links
nothing. `export default withAuth(Page)` hands over a call, whose first
identifier ownExport reads; that was harmless while only same-file
declarations counted, so a default is followed only when the statement is
the binding itself (exportsBinding), or the route would link the HOC.
#2451 (open) adds the same step to the general import walk; #2420 (open)
carried it in a lazyRouteComponent rewrite that exportedComponent superseded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 14, 2026
…2560) A lazy React Router route whose module is a barrel, and whose page sits in a folder below the barrel's, stayed unlinked after `codegraph sync` added the page or gave it its export, though a fresh index linked it. In DigiShop.Frontend, `@/pages` is `src/pages/index.ts`, which imports `./Home`, `src/pages/Home/index.tsx`, and exports it again. While the page is missing, the route's reference fails and waits under the key of the module it names, `module:pages` (#2422). The retry looks failed references up by the keys of the changed files, and a file in `pages/Home/` gives only `module:Home` and its own stem. The limit, which the `export *` barrels of #2436 share, was recorded with #2558. After the failed-ref retry, which makes the barrel's import edge into an added file, sync now walks up the import edges from each file it added, or gave a definition, to the barrels that pass it on: `export … from`, or an import exported again (`import { X } from './x'; export { X }`, `export default X`), as deep as the resolver chases a name. It re-opens, for the sweep, the failed references waiting under those barrels' keys. Only keys a failed reference waits under are looked up, in one DISTINCT over the failed rows parked under module keys, so a sync with none reads nothing, and a body-only edit seeds nothing. The ImportReader that the added-files reopen (#2512) uses is shared, and gains exportedBindings, behind a regex pre-check, since most importers export nothing. defaultExportBinding moves to alias-binding.ts for this walk and for react.ts's exportsBinding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A lazily loaded folder whose
index.tspasses on a page it imports linked nothing:exportedComponentinsrc/resolution/frameworks/react.tsis the barrel walk behind lazy routes,React.lazyvalues and lazily loaded layouts (#2436, #2452, #2480). It reads the module's own export statement, thenexport … fromandexport *. For an export clause it looked for a declaration of that name in the barrel, butUserLayoutthere is an import binding, not a declaration, so it found nothing. The same happened forimport Login from './Login'; export default Login, and forimport { Page } from './page'; export { Page as Component }. All 27 lazy route refs in DigiShop'ssrc/App.tsxstayed failed on main, and #2480's PR body listed this under "Not in this PR".Fix
When the export names a binding with no declaration in the module,
exportedComponentfollows the module's import of that binding to the module it comes from. A default import continues asdefault, a named one by the name it imports.readscollects each file passed through, solazyModules(#2452) still redraws the route when the page behind the import changes.import * as Tabs …; export { Tabs }) is no component, so it links nothing.import { Button } from '@mui/material'; export { Button }) doesn't resolve to a file, so it links nothing.export default withAuth(Page)hands over a call, not a binding.ownExportreads its first identifier (withAuth). That was harmless while only same-file declarations counted, but following imports would link the route to the imported HOC. So anexport default Xis followed only when the statement is the binding itself (exportsBinding), and the default stays unlinked, as on main.Verification
Regression test.
__tests__/react-router-barrel-imports.test.tshas 9 tests:export defaultof an imported binding,export { Page as Component }, and aReact.lazyvalue throughexport { HelpPage as default }.export default withAuth(AccountPage).On main, 7 of the 9 fail: every positive case and both sync cases. The 2 negative tests pass on main too, which is the point: they pin that the new step adds nothing there.
Checks.
tsc --noEmitis clean, and so is a strict type-check of the test file.export defaultbinding check fails thewithAuthcase, which then linkswithAuth@src/hoc/withAuth.ts.A/B on real repos (fresh
codegraph init, main5da4a41dvs this branch; onlyreact.jsdiffers between the builds)50 repos:
Results:
@/pagesand the 14 layout edges behind@/layout. Each lands on the declaration its barrel's import names; for exampleHomePageis insrc/pages/Home/index.tsx, whichsrc/pages/index.tsimports as{ HomePage } from './Home'. No edge was retargeted or lost.Sync. On DigiShop:
codegraph syncrun.src/layout/UserLayout.tsxsits in the folder the route imports (@/layout), whose key the route's failed reference waits under.src/pages/Home/index.tsxbehind@/pages), andmodule:pagesis not a key a file inpages/Home/gives.That is the limit #2436 recorded for
export *barrels; see "Not in this PR". A fresh index links those pages, and so does a later edit ofsrc/pages/index.ts.Not in this PR
Overlap
import-resolver.ts, throughclauseImportsandexportedOrForwarded, for plainimport { X } from './barrel'. This PR adds it to the lazy-route walk only, which fix(react): a JSX tag renders what the file imports under that name #2451 doesn't touch. The rule is the same: a clause name with no declaration is the module's import of it. Plain imports through such a barrel remain fix(react): a JSX tag renders what the file imports under that name #2451's to fix.lazyRouteComponentcarried an import-forwarding step forimport Login from './Login'; export default Login.exportedComponent(fix(react-router): a lazy route links the page a barrel module forwards #2436) superseded that walk, and this PR adds the step there, so fix(react): a component a file declares itself as a value is what its JSX renders #2420 can drop it when rebased.🤖 Generated with Claude Code