fix(router): use instanceof Router instead of constructor.name for sub-router detection#354
Open
jcpsimmons wants to merge 1 commit into
Open
fix(router): use instanceof Router instead of constructor.name for sub-router detection#354jcpsimmons wants to merge 1 commit into
jcpsimmons wants to merge 1 commit into
Conversation
…b-router detection
The current check `candidate.constructor.name === 'Router'` is fragile to
bundler/minifier class renaming. Modern JS bundlers and minifiers commonly
rename class bodies during compilation, in which case `.name` no longer
returns "Router" and sub-router mounting silently fails (every
`app.use(pattern, router)` call becomes a no-op).
Concrete example: esbuild 0.25+ emits `class _Router {}` when bundling
this library as a transitive dependency, breaking all sub-router routing
in the output bundle. The author of this PR's company hit this in
production after an esbuild upgrade and had to enable `keepNames: true`
as an expensive workaround (it injects `__name()` wrappers around every
function and class in the bundle).
`instanceof Router` walks the prototype chain to test class identity, so
it works regardless of how the class is renamed in compiled output. It's
the same semantic check, just done in a way that survives compilation.
No API change, no behavior change for callers; only the internal detection
becomes more robust.
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
The sub-router detection in
Router.jsusescandidate.constructor.name === 'Router':This is fragile to bundler/minifier class renaming. Modern JS bundlers and minifiers commonly rename class bodies during compilation, in which case
.nameno longer returns"Router"and sub-router mounting silently fails: everyapp.use(pattern, router)call becomes a no-op and the routes registered on the sub-router return 404.Concrete failure case
esbuild 0.25+ emits
class _Router {}when bundling this library as a transitive dependency, which changes.nameto"_Router"and breaks the check. We hit this in production after an esbuild upgrade. The only viable workaround waskeepNames: true, which restores.namebut injects__name()wrappers around every function and class in the bundle. At our scale (thousands of classes in the bundle) this cost ~20% production CPU on the affected service.Fix
Swap the check for
candidate instanceof Router:instanceofwalks the prototype chain to test class identity, so it works regardless of how the class is renamed in compiled output. Same semantic check, just done in a way that survives compilation.Compatibility
Routerinstanceinstanceofis stricter and only matches actual HyperExpressRouterinstances, which is the intended semanticWhy this matters even if you have no plans to ship bundled
People bundle their HyperExpress apps. esbuild, webpack, Rollup, swc, etc. all touch class names during compilation in ways that can break
.name-based dispatch. Making the internal detection robust to that is a one-line change that prevents a hard-to-debug silent failure for downstream users.(I appreciate the project may be in maintenance mode — leaving this PR here so the fix is documented and available to anyone else who hits the same issue, even if it's not merged.)