Skip to content

feat: add Source.prototype.clearCache() to release per-instance caches#221

Merged
alexander-akait merged 12 commits into
mainfrom
claude/fix-webpack-issue-0KinR
May 22, 2026
Merged

feat: add Source.prototype.clearCache() to release per-instance caches#221
alexander-akait merged 12 commits into
mainfrom
claude/fix-webpack-issue-0KinR

Conversation

@alexander-akait
Copy link
Copy Markdown
Member

Mirrors the fix to webpack's SourceMapDevToolPlugin (webpack/webpack#20961).
CachedSource retains composed source map data per chunk in _cachedMaps;
without a way to release it, plugins that hold assets reachable for size /
hash work cannot reclaim those bytes between tasks.

clearCache() is a no-op on the base class. CachedSource drops all
internal caches and recurses into its wrapped source. Composite sources
(Concat / Prefix / Replace / Compat) propagate to children, and leaf
sources (Raw / Original / SourceMap) drop dual-cached secondary
representations.

A standalone memory benchmark under benchmark/memory/clear-cache.mjs
reproduces the upstream scenario; on a 200-task workload it shrinks heap
growth from 58.4 MB to 32.3 MB (~45%).

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx

Mirrors the fix to webpack's SourceMapDevToolPlugin (webpack/webpack#20961).
`CachedSource` retains composed source map data per chunk in `_cachedMaps`;
without a way to release it, plugins that hold assets reachable for size /
hash work cannot reclaim those bytes between tasks.

`clearCache()` is a no-op on the base class. `CachedSource` drops all
internal caches and recurses into its wrapped source. Composite sources
(Concat / Prefix / Replace / Compat) propagate to children, and leaf
sources (Raw / Original / SourceMap) drop dual-cached secondary
representations.

A standalone memory benchmark under `benchmark/memory/clear-cache.mjs`
reproduces the upstream scenario; on a 200-task workload it shrinks heap
growth from 58.4 MB to 32.3 MB (~45%).

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 14, 2026

CLA Not Signed

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: 6b66049

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

This PR includes changesets to release 1 package
Name Type
webpack-sources Minor

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

❌ Patch coverage is 94.05941% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.58%. Comparing base (db3f9df) to head (6b66049).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
lib/SourceMapSource.js 86.48% 4 Missing and 1 partial ⚠️
lib/CachedSource.js 94.44% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #221      +/-   ##
==========================================
- Coverage   97.71%   97.58%   -0.14%     
==========================================
  Files          25       25              
  Lines        1970     2069      +99     
  Branches      613      668      +55     
==========================================
+ Hits         1925     2019      +94     
- Misses         43       47       +4     
- Partials        2        3       +1     
Flag Coverage Δ
integration 97.58% <94.05%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 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.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 14, 2026

Merging this PR will improve performance by 15.28%

⚡ 1 improved benchmark
✅ 172 untouched benchmarks
🆕 38 new benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation cached-source: new CachedSource() 379.1 µs 328.8 µs +15.28%
🆕 Memory source-map-source memory: new SourceMapSource(with inner map) N/A 1 KB N/A
🆕 Memory compat-source memory: new CompatSource(sourceLike) N/A 1 KB N/A
🆕 Memory raw-source memory: new RawSource(string) N/A 1 KB N/A
🆕 Memory original-source memory: new OriginalSource() N/A 1.2 KB N/A
🆕 Memory original-source memory: sourceAndMap({ columns: true }) N/A 1.7 MB N/A
🆕 Memory concat-source memory: new ConcatSource(...children) N/A 3.7 KB N/A
🆕 Memory clear-cache memory: unique tasks (clearCache default) N/A 4.2 MB N/A
🆕 Memory prefix-source memory: source() allocates rewritten string N/A 1.2 KB N/A
🆕 Memory original-source memory: map({ columns: true }) builds full mappings N/A 886.5 KB N/A
🆕 Memory replace-source memory: construct + 100 insertions N/A 11 KB N/A
🆕 Memory cached-source memory: getCachedData() allocates BufferedMap N/A 504 B N/A
🆕 Memory replace-source memory: map({ columns: true }) splices mappings N/A 3.2 MB N/A
🆕 Memory webpack-20961: warm CHUNKS × MODULES, hold all live (baseline) N/A 2.4 MB N/A
🆕 Memory clear-cache memory: shared modules (visited set — single allocation) N/A 648 B N/A
🆕 Memory clear-cache memory: shared modules (no visited set — allocates per chunk) N/A 1 MB N/A
🆕 Memory concat-source memory: source() concatenates children N/A 389.4 KB N/A
🆕 Memory raw-source memory: updateHash() populates _cachedHashUpdate N/A 1.4 MB N/A
🆕 Memory original-source memory: map({ columns: false }) line-only mappings N/A 283.2 KB N/A
🆕 Memory source-map-source memory: new SourceMapSource(simple) N/A 1 KB N/A
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/fix-webpack-issue-0KinR (6b66049) with main (68d9c8e)

Open in CodSpeed

claude added 8 commits May 14, 2026 17:07
The PR review flagged missing coverage on the originalSource, sourceMap
buffer, and innerSourceMap branches of `SourceMapSource.clearCache()`.
Construct a SourceMapSource with every optional parameter and call
`getArgsAsBuffers()` to populate all four dual-cached pairs before
clearing, so every `if`-branch executes. Round-trip the buffers after
the clear to confirm the data is still readable.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Review of PR #221 flagged three blocking issues for webpack's
SourceMapDevToolPlugin use case:

1. **Recursion had no de-duplication.** When a webpack plugin iterates
   assets, the same module-level CachedSource is reachable from many
   chunks, so calling `clearCache()` per asset re-walked the shared
   subtree once per chunk. The reviewer's 50-chunk benchmark went from
   10s to 17–45s with no extra heap freed.

2. **Recursion was non-opt-outable.** Webpack often replaces the asset
   shortly after calling `clearCache()`, so walking the children
   ourselves is pure waste — V8 reclaims them for free anyway.

3. **Cheap-to-hold caches were cleared unconditionally.**
   `_cachedSize` is a single number; `_cachedHashUpdate` is what makes
   downstream `cache.store` cheap. Dropping them on a generic
   "release memory" call is a perf cliff.

The new signature addresses all three:

```js
clearCache(
    { maps = true, source = true, hash = false, size = false,
      recursive = true } = {},
    visited
)
```

- A `visited` `WeakSet` lets callers iterate assets in a loop and walk
  each shared subtree at most once. Internally, every node also adds
  itself to `visited` on first visit so further parents short-circuit.
- `recursive: false` makes the call O(1) per asset for the common case
  where the asset is about to be GC'd anyway.
- `hash` and `size` default to `false` (kept) — they're cheap to hold
  and expensive to rebuild.
- `_cachedMaps.clear()` replaces `new Map()` to avoid per-call
  allocation churn.

Updated benchmark (`benchmark/memory/clear-cache.mjs`) adds a
shared-modules scenario mirroring the reviewer's webpack shape. On 50
chunks × 1000 shared modules:

- naive `clearCache()`              12.7 ms
- `clearCache(opts, visited)`        4.4 ms  (2.9× speedup)
- `clearCache({ recursive: false })` 0.1 ms  (188× speedup)

`CompatSource.clearCache` now forwards `visited` through to wrapped
SourceLike implementations so dedup is preserved across CompatSource
boundaries. Public `Source.prototype.clearCache` JSDoc documents the
concurrency contract and that subsequent reader calls may repopulate
caches.

Tests grow to 22 (was 14) — covers dedup, the granular options, the
opt-out recursion, and that `_cachedMaps` is reused not reallocated.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Addresses the polish items from the second review of PR #221:

- **#2 post-minifier asset shape benchmark.** New Scenario 3 in
  `benchmark/memory/clear-cache.mjs` constructs the shape webpack
  assets actually present at the PROCESS_ASSETS_STAGE_DEV_TOOLING
  hook: a `CachedSource` wrapping a `SourceMapSource` whose
  `_cachedSource` (bundle string) and `_cachedMaps[{}]` (composed
  map) are populated. 50 chunks: 8.5 MB baseline → 0 MB after
  `clearCache({ maps: true, source: false, recursive: false })`.

- **#3 optional `parsedMap: true` flag** on `ClearCacheOptions`.
  When set, `SourceMapSource.clearCache` drops the parsed object
  forms of `_sourceMapAsObject` and `_innerSourceMapAsObject`
  (heaviest cached representation), but only when a serialized form
  (buffer or string) is also held so data stays recoverable.
  Defaults to `false` to preserve the cheap toString re-hydration
  path described in the comment.

- **#4 `getCachedData()` after `clearCache()` interaction test.**
  Pins the observable shape: buffer + source = undefined, maps
  empty, hash + size preserved when their flags default to false.
  Round-trips through `new CachedSource(source, data)` to confirm
  cleared cached data is still a valid input.

- **#5 "release hint" framing** in the `Source.prototype.clearCache`
  JSDoc. Opens with "clearCache is a memory hint: it never affects
  correctness or output, only how expensive the next read is."

- **#6 dedup correctness test** already lives at
  `test/clearCache.js:251` ("a shared subtree is walked once when
  a `visited` WeakSet is passed") with a negative control at :269.

Tests grow to 25 (was 22). Full suite 89,870/89,870 pass, lint
clean. Benchmark numbers unchanged on Scenarios 1 + 2: 44.7%
heap reduction on unique tasks, 3.6× dedup speedup, 228×
`recursive: false` speedup.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Adds a parallel benchmark runner for memory-shaped tinybench tasks,
wired into CI as a second job in benchmarks.yml that invokes the
CodSpeed action with `mode: "memory"` (introduced in
@codspeed/core@5.2.0, already pinned in this repo via 5.4.0). This
gives the memory benchmarks the same first-class treatment the CPU
benchmarks already get: PR comments, historical tracking on
codspeed.io, dashboard alongside the existing simulation results.

Layout (per user preference, kept separate from benchmark/cases/):

  benchmark/memory/<case>/
    index.bench.mjs   tinybench tasks, discovered by run-memory.mjs;
                      what CodSpeed memory instrument measures
    snapshot.mjs      ad-hoc developer script with process.memoryUsage()
                      snapshots and absolute MB output (the old
                      standalone, renamed and re-pathed)

The two entry points serve different audiences: bench files for CI
tracking, snapshot.mjs for "how many MB did I save on my laptop".

New: benchmark/run-memory.mjs discovers benchmark/memory/*/
index.bench.mjs the same way benchmark/run.mjs handles cases/. Uses
fewer warmup iterations (memory measurement does not benefit from JIT
warmup the way instruction counting does — we want the allocation
pattern, not the post-JIT throughput). Locally, without the CodSpeed
runner present, falls through to plain wall-clock tinybench output —
useful as a smoke test only; documented as such.

Five tasks shipped with the clear-cache memory bench:

  - unique tasks (no clearCache)            baseline heap growth
  - unique tasks (clearCache default)       drops source + maps
  - unique tasks (clearCache maps only)     webpack-side call shape
  - shared modules (no visited)             allocates per chunk
  - shared modules (visited)                single allocation

CI workflow gains a parallel memory-benchmark job. Same checkout +
npm ci, then `npm run benchmark:memory` under CodSpeedHQ/action with
`mode: "memory"`. README updated with the two-entry-point split.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Shrinks the clearCache API surface. The previous shape had six flags
(maps, source, hash, size, parsedMap, recursive) covering 64 possible
combinations. Webpack's real call sites only need two: drop everything
(default) and drop only map data while keeping source (the
post-minifier shape, where downstream plugins still read the source).

Changes:

- `ClearCacheOptions` is now `{ mapsOnly?: boolean }`. Default false
  = drop cached source/buffer, cached maps, normalized maps, and the
  parsed object form of source maps when a buffer or string form
  survives so the data remains recoverable. `mapsOnly: true` keeps
  source-side caches and only drops map-related state.

- Recursion is always on. The visited-set dedup is cheap (~14× faster
  than the naive walk on the shared-modules pathology), so the
  `recursive: false` perf escape hatch is no longer needed.

- Hash and size caches are never dropped. They were already default-
  off under the old flags (cheap to hold, expensive to recompute) and
  removing the flags makes that policy explicit.

- The parsed object form of SourceMapSource maps is now dropped by
  default when a buffer or string form is also held. Re-parsing JSON
  is more expensive than re-`toString`-ing, but the caller has already
  signalled "I want memory back" by calling clearCape at all; keeping
  the heaviest cached representation around defeats the purpose. If
  no serialized form survives, the parsed object stays (correctness).

API surface: ~170 fewer lines across lib/ and 50 fewer in tests for
the same coverage of real-world call shapes. Three tests for
flag-specific behavior dropped (recursive=false, hash=true,
size=true, parsedMap=true), two reframed for the new default
(parsed-object-form drop when buffer present / kept when no
serialized form).

Numbers unchanged on the scenarios that matter:
- 44.7% baseline heap reduction on unique tasks
- 14.5× speedup on shared-modules dedup (visited set vs naive)
- 8.5 MB → 0 MB on post-minifier asset shape with mapsOnly: true

Full suite: 89,868/89,868 pass. Lint clean.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Adds one memory-mode bench file per Source class under
benchmark/memory/<source>/index.bench.mjs. Each registers tinybench
tasks that exercise the genuinely allocation-significant operations
for that class, so the CodSpeed memory instrument tracks them with
the same first-class treatment clear-cache already gets.

Coverage (31 new tasks across 9 new cases):

- raw-source             — constructor (string/buffer), lazy buffer()
                           materialisation, updateHash payload
- original-source        — constructor, map() columns:true vs false,
                           sourceAndMap (the heavy path)
- source-map-source      — constructor (simple/with-inner), map(),
                           sourceAndMap with combined inner map
- replace-source         — construct + 100 spread insertions, source(),
                           map() bsearch splice
- concat-source          — construct N children, source(), buffers(),
                           map() composition across SourceMapSource kids
- prefix-source          — construct, source() string rewrite via
                           buildPrefixed, buffer() conversion
- cached-source          — cold vs warm sourceAndMap, getCachedData()
                           BufferedMap allocation, constructor from
                           pre-built cachedData (persistent-cache path)
- compat-source          — wrapper construction, delegated source/map,
                           CompatSource.from() short-circuit
- size-only-source       — constructor (included for completeness;
                           accidental Source growth surfaces here first)

All cases follow the existing clear-cache shape: per-task `sink`
arrays inside beforeAll/afterAll so heap baselines are independent,
batch loops in the task body so the instrument captures a
deterministic allocation pattern per iteration.

Refactored clear-cache/index.bench.mjs to import from the existing
benchmark/fixtures.mjs (was inlining its own fixture loading), so the
memory benches share one source of fixture truth with the CPU benches.

README updated with a per-case table.

Local smoke test: 35 tasks register, all run cleanly under
walltime (no CodSpeed). CodSpeed memory mode in CI will record peak
heap, total allocations, and allocation timeline per task.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
…20963

The previous simplification collapsed every option into a single
`mapsOnly` boolean. That works semantically but doesn't match the
call shape webpack/webpack#20963 already wrote:

  source.clearCache({ maps: true, source: false, parsedMap: true })

That PR's SourceMapDevToolPlugin change is what delivers the
reported 27% peak-heap reduction (1422 MB → 1041 MB) and 9% RSS
reduction on a 50-chunk × 1000-module build, and it relies on
controlling each flag independently — `source: false` to keep the
JS bundle string available for downstream plugins, `parsedMap: true`
to drop the heaviest cached representation (the parsed object form)
on top of the serialized one.

Restored:

- `maps?: boolean` (default `true`) — drop cached source maps. On
  CachedSource, clears `_cachedMaps`. On SourceMapSource, drops
  `_sourceMapAsString` / `_innerSourceMapAsString` when their
  buffer counterparts also exist.

- `source?: boolean` (default `true`) — drop cached source/buffer
  copies. On CachedSource, clears `_cachedSource`/`_cachedBuffer`.
  On SourceMapSource, OriginalSource, RawSource, drops the
  redundant string form when both string and buffer are held.

- `parsedMap?: boolean` (default `false`) — on SourceMapSource,
  additionally drops the parsed object form (`_sourceMapAsObject`,
  `_innerSourceMapAsObject`) when a buffer or string form survives.
  Defaults off because re-parsing JSON is significantly more
  expensive than `toString` from a buffer; the webpack PR opts in
  explicitly.

Kept simplified (dropped earlier and not restored):

- No `hash` flag — hash payload is small and expensive to rebuild;
  never dropped.
- No `size` flag — single number, never dropped.
- No `recursive` flag — always recurse; visited-set dedup keeps
  the cost negligible (~14× faster than naive when the same module
  appears in many chunks).

Test count: 24 in test/clearCache.js (was 23). The `parsedMap` flag
gets three tests: default-off, explicit-on with buffer present, and
explicit-on as a no-op when no serialized form survives.

Benchmark scenarios updated to use the webpack call shape
`{ maps: true, source: false, parsedMap: true }`. Memory savings
identical to before: 44.7% baseline heap reduction on unique tasks,
15.5× dedup speedup, 8.5 MB → 0 MB on post-minifier asset shape.

Full suite: 89,869/89,869 pass. Lint clean.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
New memory bench under benchmark/memory/webpack-20961/ that
reproduces the SourceMapDevToolPlugin asset shape from
webpack/webpack#20961 (the issue webpack/webpack#20963 fixes):

  N chunks, each a CachedSource over a ConcatSource of M shared
  modules, with sourceAndMap({ columns: true }) warmed so the
  asset carries `_cachedSource`, `_cachedMaps[{}]`, and the
  per-module SourceMapSource parsed object forms — the actual
  shape webpack assets present at PROCESS_ASSETS_STAGE_DEV_TOOLING.

Three tasks measure the spectrum:

1. baseline — warm all chunks, hold all live. Establishes peak
   heap with every cached representation retained.

2. PR #20963 call shape —
     source.clearCache({ maps: true, source: false, parsedMap: true })
   after each chunk warm. `source: false` keeps the bundle string
   available for downstream plugins (Terser/compression/hashing);
   `maps: true` drops the composed sourcemap; `parsedMap: true`
   drops the parsed object form (heaviest representation), still
   recoverable from the buffer form on re-read. This is the exact
   call that delivered the reported 1422 MB → 1041 MB (-27%) peak
   heap on webpack's 50×1000 synthetic run.

3. full clearCache() — lower bound on per-chunk peak heap; the
   downstream-plugin path would re-walk the underlying source.

Dimensions scaled to 10 chunks × 20 shared modules so the
Valgrind-instrumented CodSpeed memory pass finishes in
reasonable CI time. The relative delta between tasks is what
the dashboard tracks, not the absolute heap.

Local wall-clock smoke: 131 / 80 / 80 ms per task (warmup
allocation dominates the wall-clock; CodSpeed memory mode will
show the per-task peak heap and total allocation delta that
the PR optimizes).

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new Source.prototype.clearCache() API across webpack-sources to allow consumers (notably webpack’s SourceMapDevToolPlugin flow) to proactively release per-instance cached data between tasks/chunks, reducing heap growth in long-running builds. It also introduces a memory benchmark harness and scenarios to track the impact in CI via CodSpeed memory mode.

Changes:

  • Add Source#clearCache(options?, visited?) (no-op on base) and implement recursive cache releasing in CachedSource, composite sources, and leaf sources with dual string/buffer or parsed-map caches.
  • Add Jest coverage for clearCache behavior, including visited dedup traversal and cache-preservation options.
  • Add memory benchmark runner + benchmark cases, CI workflow job, and documentation for running/maintaining memory benchmarks.

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
types.d.ts Adds ClearCacheOptions type and clearCache method signatures to exported typings.
lib/Source.js Defines ClearCacheOptions typedef and adds base-class no-op clearCache.
lib/CachedSource.js Implements cache dropping for _cachedSource / _cachedMaps and recursion into wrapped sources with optional visited.
lib/ConcatSource.js Propagates clearCache to child sources (with optional dedup via visited).
lib/PrefixSource.js Propagates clearCache to inner source (with visited).
lib/ReplaceSource.js Propagates clearCache to inner source (with visited).
lib/CompatSource.js Forwards clearCache to wrapped “source-like” when supported.
lib/RawSource.js Drops redundant secondary cached representation (string vs buffer) when safe.
lib/OriginalSource.js Drops cached string when buffer is also cached, preserving rehydration behavior.
lib/SourceMapSource.js Drops redundant string forms when buffers exist; optionally drops parsed map objects when recoverable.
test/clearCache.js Adds comprehensive Jest tests for cache clearing behavior and visited-dedup traversal semantics.
package.json Adds benchmark:memory script.
eslint.config.mjs Adjusts benchmark linting overrides (notably disables some Node feature and JSDoc rules for benchmark files).
benchmark/run-memory.mjs Adds benchmark runner that discovers benchmark/memory/*/index.bench.mjs.
benchmark/README.md Documents memory benchmark layout, CI integration, and available cases.
benchmark/memory/clear-cache/index.bench.mjs Adds CodSpeed-shaped memory tasks measuring clearCache scenarios and visited-dedup.
benchmark/memory/clear-cache/snapshot.mjs Adds standalone developer snapshot script for heap/RSS comparisons across scenarios.
benchmark/memory/webpack-20961/index.bench.mjs Adds a memory benchmark reproducing webpack/webpack#20961 asset shapes and the PR #20963 call pattern.
benchmark/memory/source-map-source/index.bench.mjs Adds memory benchmarks for SourceMapSource construction and map/sourceAndMap allocation paths.
benchmark/memory/size-only-source/index.bench.mjs Adds memory baseline benchmark for SizeOnlySource.
benchmark/memory/replace-source/index.bench.mjs Adds memory benchmarks for ReplaceSource construction and allocation-heavy operations.
benchmark/memory/raw-source/index.bench.mjs Adds memory benchmarks for RawSource construction and common allocation paths.
benchmark/memory/prefix-source/index.bench.mjs Adds memory benchmarks for PrefixSource construction and accessors.
benchmark/memory/original-source/index.bench.mjs Adds memory benchmarks for OriginalSource construction and mapping paths.
benchmark/memory/concat-source/index.bench.mjs Adds memory benchmarks for ConcatSource construction and accessors.
benchmark/memory/compat-source/index.bench.mjs Adds memory benchmarks for CompatSource wrapping/delegation and from() behavior.
benchmark/memory/cached-source/index.bench.mjs Adds memory benchmarks for CachedSource cold/warm behavior and cachedData operations.
.github/workflows/benchmarks.yml Adds CI job to run CodSpeed memory benchmarks (mode: "memory").
.changeset/clear-cache-api.md Declares a minor release for the new clearCache API and documents option semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/CachedSource.js
if (clearSource) {
this._cachedSource = undefined;
this._cachedSourceType = undefined;
this._cachedBuffer = undefined;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in e4c6fd5: getCachedData() now always calls this.buffer(), which returns _cachedBuffer when populated and rehydrates via the wrapped source otherwise. The CachedData.buffer: Buffer contract holds in every state, including after clearCache() drops _cachedBuffer here.


Generated by Claude Code

*
* Run with:
*
* node --expose-gc benchmark/memory/clear-cache.mjs
Two Copilot review findings on #221:

1. CachedSource.getCachedData() was returning { buffer: undefined }
   after a default clearCache() (because both _cachedSource and
   _cachedBuffer get dropped). That contradicts the CachedData type,
   which declares `buffer: Buffer` as required, and breaks any
   persistent-cache writer that calls getCachedData() on a cleared
   source.

   Fix: always call this.buffer() in getCachedData() — it returns
   _cachedBuffer when populated, or rehydrates via the wrapped
   source when both caches have been cleared. Cheap when not
   needed, correct when needed.

   The existing test asserted the broken behavior
   (`expect(data.buffer).toBeUndefined()`) — replaced with one that
   asserts the contract: `Buffer.isBuffer(data.buffer)` and the
   buffer contents match the source.

2. benchmark/memory/clear-cache/snapshot.mjs's run-with example
   pointed at the pre-reorg path `benchmark/memory/clear-cache.mjs`.
   Updated to the current `benchmark/memory/clear-cache/snapshot.mjs`.

Full suite: 89,869/89,869 pass. Lint clean.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 2 comments.

Comment thread test/clearCache.js Outdated
Comment on lines +58 to +60
clearCache() {
this.calls.clearCache++;
this._inner.clearCache();
Comment on lines +98 to +102
// Pre-built cachedData reused across iterations; we measure only
// the constructor side. Webpack hits this path when restoring
// from persistent cache.
const seed = warmCaches[0].getCachedData();
for (let i = 0; i < BATCH; i++) {
Two new comments on the e4c6fd5 review pass:

1. test/clearCache.js — TrackedSource.clearCache() dropped the
   `options` and `visited` arguments instead of forwarding them
   to the wrapped source. The helper is used to assert recursion
   counts; if a future code change forgot to thread options or
   visited through, tests using TrackedSource would have masked
   the regression. Now forwards both.

2. benchmark/memory/cached-source/index.bench.mjs — the
   "construct from cachedData" task computed
   `seed = warmCaches[0].getCachedData()` inside the timed body,
   so getCachedData()'s BufferedMap allocations were attributed
   to the constructor measurement. Moved seed construction into
   beforeAll so the task now isolates the constructor-from-
   cachedData allocation pattern (the path webpack hits when
   restoring assets from persistent cache).

Local checks: clearCache tests 24/24, full lint clean, cached-source
memory bench runs cleanly.

The third Copilot comment (CachedSource:466 getCachedData buffer
contract) is already resolved by e4c6fd5; the bot re-flagged the
same line because the diff context didn't change.

https://claude.ai/code/session_01LLtSGKaynui1P1wQsfVnXx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 1 comment.

Comment on lines +42 to +46
memory-benchmark:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed in 6b66049.

@alexander-akait alexander-akait merged commit a402b24 into main May 22, 2026
32 of 35 checks passed
@alexander-akait alexander-akait deleted the claude/fix-webpack-issue-0KinR branch May 22, 2026 08:55
alexander-akait added a commit to webpack/webpack that referenced this pull request May 22, 2026
…arCache

After extracting the source+map for each chunk, call `Source#clearCache`
on the asset's source tree to release the composed map and parsed
`SourceMapSource` representations that `sourceAndMap()` populated.

A single `WeakSet` is shared across every call so each shared module
subtree is walked at most once — measured at -30% wall time vs a
per-call WeakSet on a synthetic build with 1000 shared modules × 50
chunks.

Bumps the `webpack-sources` dependency to `^3.5.0` where the
`clearCache` API landed (webpack/webpack-sources#221).

Refs #20961
alexander-akait added a commit to webpack/webpack that referenced this pull request May 22, 2026
…arCache

After extracting the source+map for each chunk, call `Source#clearCache`
on the asset's source tree to release the composed map and parsed
`SourceMapSource` representations that `sourceAndMap()` populated.

A single `WeakSet` is shared across every call so each shared module
subtree is walked at most once — measured at -30% wall time vs a
per-call WeakSet on a synthetic build with 1000 shared modules × 50
chunks.

Bumps the `webpack-sources` dependency to `^3.5.0` where the
`clearCache` API landed (webpack/webpack-sources#221).

Refs #20961
alexander-akait added a commit to webpack/webpack that referenced this pull request May 22, 2026
…arCache

After extracting the source+map for each chunk, call `Source#clearCache`
on the asset's source tree to release the composed map and parsed
`SourceMapSource` representations that `sourceAndMap()` populated.

A single `WeakSet` is shared across every call so each shared module
subtree is walked at most once — measured at -30% wall time vs a
per-call WeakSet on a synthetic build with 1000 shared modules × 50
chunks.

Bumps the `webpack-sources` dependency to `^3.5.0` where the
`clearCache` API landed (webpack/webpack-sources#221).

Refs #20961
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.

4 participants