Skip to content

fix: bound the internal url and Range header caches - #2405

Merged
alexander-akait merged 3 commits into
mainfrom
fix/bound-memoize-caches
Sep 3, 2026
Merged

fix: bound the internal url and Range header caches#2405
alexander-akait merged 3 commits into
mainfrom
fix/bound-memoize-caches

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

memorize() backs two module-level caches that were never evicted: memoizedParse, keyed by request url, and parseRangeHeaders, keyed by size plus the Range header. Their keys come from request data — and in the second case from a header the client chooses — so the key space is not bounded by anything the server controls. Being module-level, they also outlived the middleware instance.

Measured against a fixture project, before and after:

before after
50,000 distinct urls 9.24 MB retained (9.18 MB of it surviving instance.close()) 0.37 MB
20,000 distinct Range headers 4.70 MB retained bounded — the remainder is fixed http overhead and does not grow with N (1.61 MB at 10k, 1.62 MB at 20k, noise at 40k)

The cache now keeps a fixed number of entries and drops the least recently used one, re-inserting on a hit so map insertion order tracks recency.

It also drops a spread that copied nothing: { ...urlObject } on a URL yields an empty object, because a URL's properties are prototype accessors. The value was therefore { pathname } while being annotated @type {URL} — reading .search off it would have type-checked and been undefined at runtime. Only the pathname was ever used, so it is returned on its own.

What kind of change does this PR introduce?

fix

Did you add tests for your changes?

Yes — test/utils/memorize.test.js covers the hit path, the callback, that the cache stops growing, and that the evicted key is the least recently used one.

Does this PR introduce a breaking change?

No. memorize() is internal, and the cache limit is an option with a default.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

n/a

Use of AI

AI was used. Claude Code found the caches while reviewing for memory issues, measured the retention before and after with --expose-gc, wrote the fix and its tests, and drafted this description. Every number above comes from running those measurements.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KUpsHWHZG2FxHzJxRUvVv3


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Prevented URL and HTTP range caches from growing indefinitely.
    • Cached data is now released when the development middleware is closed.
    • Added least-recently-used eviction to maintain bounded caches.
  • Performance

    • Improved repeated URL and file-path processing through more efficient pathname caching.
    • Added safeguards for predictable memory usage during extended development sessions.
  • Documentation

    • Documented configurable cache size limits and validation for invalid values.

Both memoized caches are module-level and were never evicted, so a
long-running server kept every key it had ever seen. Their keys come from
request data -- a url, and the Range header, which the client chooses --
so the key space is not bounded by anything the server controls, and
being module-level they outlived the middleware instance.

Measured before and after, with a fixture project:

  50,000 distinct urls    9.24 MB retained -> 0.37 MB
                          (9.18 MB of it survived instance.close())
  20,000 Range headers    4.70 MB retained -> bounded; the remainder is
                          fixed http overhead and does not grow with N

memorize() now keeps a fixed number of entries and drops the least
recently used one, re-inserting on a hit so map insertion order tracks
recency.

Also drops a spread that copied nothing: `{ ...urlObject }` on a URL
yields an empty object, since a URL's properties are prototype accessors,
so the value was `{ pathname }` while being annotated `@type {URL}`.
Reading `.search` off it would have type-checked and been undefined at
runtime. Only the pathname was ever used, so it is returned on its own.
@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01914f3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack-dev-middleware Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 4545b826-9e71-434d-8418-fda9f0b328cd

📥 Commits

Reviewing files that changed from the base of the PR and between 83c2369 and 01914f3.

📒 Files selected for processing (3)
  • src/utils.js
  • test/utils/memorize.test.js
  • types/utils.d.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/utils/memorize.test.js
  • src/utils.js
  • types/utils.d.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


Walkthrough

The PR adds a default cache limit of 1000 entries to memorize. Cache hits refresh recency, and least-recently-used entries are evicted before insertion. Types and tests cover the new option, validation, and eviction behavior. Middleware now memoizes decoded pathname strings for filename and public-path handling. A patch changeset documents bounded URL and Range header caches.

Merge Risk: ⚪ Minimal · up to 01914

This change bounds memoization caches with LRU eviction and updates pathname handling. No current merge-blocking risk is identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: bounding the internal URL and Range header caches.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bound-memoize-caches

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 9905928d-fbc4-481f-bdec-10f9026e068b

📥 Commits

Reviewing files that changed from the base of the PR and between 1abbf08 and dd1f8a5.

📒 Files selected for processing (4)
  • .changeset/bound-memoize-caches.md
  • src/middleware.js
  • src/utils.js
  • test/utils/memorize.test.js

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread src/utils.js
Comment thread src/utils.js Outdated
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.04%. Comparing base (1abbf08) to head (01914f3).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2405      +/-   ##
==========================================
+ Coverage   96.98%   97.04%   +0.06%     
==========================================
  Files          12       12              
  Lines        1656     1660       +4     
==========================================
+ Hits         1606     1611       +5     
+ Misses         50       49       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@alexander-akait
alexander-akait merged commit 5374bc4 into main Sep 3, 2026
22 checks passed
@alexander-akait
alexander-akait deleted the fix/bound-memoize-caches branch September 3, 2026 08:35
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