Skip to content

perf: use webpack-sources 3.4 buffers() to avoid concat on hashing#20897

Merged
alexander-akait merged 3 commits into
mainfrom
claude/update-webpack-sources-memory-UrwI7
Apr 30, 2026
Merged

perf: use webpack-sources 3.4 buffers() to avoid concat on hashing#20897
alexander-akait merged 3 commits into
mainfrom
claude/update-webpack-sources-memory-UrwI7

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Bumps webpack-sources to ^3.4.1 and feeds Source bytes into hashes via
the new Source.prototype.buffers() API. For ConcatSource/ReplaceSource
outputs this removes the intermediate Buffer.concat that source.buffer()
performs, eliminating a peak-memory spike equal to the source's total
size on each hashed asset:

  • lib/util/source.js: add updateHashFromSource(hash, source) helper
    that prefers buffers() and falls back for older sources.
  • lib/asset/AssetGenerator.js: use the helper in getFullContentHash.
  • lib/dependencies/CssIcssExportDependency.js: same for [contenthash]
    in CSS ident generation.
  • lib/optimize/RealContentHashPlugin.js: replace mapAndDeduplicateBuffers
    with mapAndDeduplicateSourceBuffers + bufferArraysEqual so dedup and
    hashing both work on chunk arrays without concatenation.

Quick measurement on a 64 MiB ConcatSource:
buffer() path: ~121 ms, +64 MiB peak external memory
buffers() path: ~64 ms, 0 MiB peak external memory

Copilot AI review requested due to automatic review settings April 30, 2026 15:13
@changeset-bot

changeset-bot Bot commented Apr 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1d511f3

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

This PR includes changesets to release 1 package
Name Type
webpack 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

@github-actions

github-actions Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (9e91057).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@9e91057
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@9e91057
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@9e91057

@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.03175% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.12%. Comparing base (bbcce7f) to head (1d511f3).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
lib/optimize/RealContentHashPlugin.js 41.50% 31 Missing ⚠️
lib/util/source.js 66.66% 2 Missing ⚠️
lib/dependencies/CssIcssExportDependency.js 50.00% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (46.03%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your changes status has failed because you have indirect coverage changes. Learn more about Unexpected Coverage Changes and reasons for indirect coverage changes.

Additional details and impacted files
@@             Coverage Diff             @@
##             main   #20897       +/-   ##
===========================================
- Coverage   91.29%   80.12%   -11.17%     
===========================================
  Files         562      525       -37     
  Lines       55615    53586     -2029     
  Branches    14705    14190      -515     
===========================================
- Hits        50772    42938     -7834     
- Misses       4843    10648     +5805     
Flag Coverage Δ
integration 77.29% <46.03%> (-12.96%) ⬇️
test262 45.98% <8.19%> (-0.04%) ⬇️
unit 36.15% <8.19%> (-0.03%) ⬇️

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 updates webpack’s hashing paths to take advantage of webpack-sources@^3.4.1’s new Source.prototype.buffers() API, aiming to reduce peak memory usage and improve performance when hashing large concatenated/modified sources.

Changes:

  • Bump webpack-sources from ^3.3.4 to ^3.4.1.
  • Add updateHashFromSource(hash, source) helper to hash Source content via buffers() when available (fallback to buffer()).
  • Rework RealContentHashPlugin dedup/hash input flow to operate on buffer chunk arrays without concatenating.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
yarn.lock Updates lockfile entry for webpack-sources@^3.4.1.
package.json Bumps webpack-sources dependency to ^3.4.1.
lib/util/source.js Introduces updateHashFromSource helper and exports it.
lib/asset/AssetGenerator.js Uses updateHashFromSource for full content hashing.
lib/dependencies/CssIcssExportDependency.js Uses updateHashFromSource for CSS [contenthash]-based ident hashing.
lib/optimize/RealContentHashPlugin.js Avoids forced concatenation by deduping/processing Source buffer chunks.
.changeset/webpack-sources-buffers.md Adds a changeset entry documenting the perf/memory improvement.

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

Comment thread lib/optimize/RealContentHashPlugin.js Outdated
Comment on lines 502 to 506
const assetsContent = [];
for (const chunks of uniqueChunkArrays) {
for (const c of chunks) assetsContent.push(c);
}
let newHash = hooks.updateHash.call(assetsContent, oldHash);

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

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

assetsContent passed into hooks.updateHash used to contain one Buffer per unique asset source (deduped). This change flattens chunk arrays returned by source.buffers() so a single asset may contribute multiple entries, which changes the observable hook contract (e.g. existing updateHash taps that rely on input.length === 1 for a single unique source will stop working). To keep backwards compatibility, consider preserving the per-source Buffer[] shape when calling the hook (or only using chunked hashing when there are no taps), or explicitly updating the hook API/typedef + in-repo usages/tests to handle chunked inputs as a concatenated byte stream.

Copilot uses AI. Check for mistakes.

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.

Good catch. Fixed in ec06ae0: the Buffer[] shape passed to hooks.updateHash is preserved (one entry per unique asset, exactly as before), but the array is only built when the hook is actually used (hooks.updateHash.isUsed()). When nobody is tapped — the common case — we feed chunks directly into the internal hash and skip the per-asset Buffer.concat entirely. Contracts seen by existing taps stay identical; the memory win is preserved on the default path.


Generated by Claude Code

@alexander-akait alexander-akait force-pushed the claude/update-webpack-sources-memory-UrwI7 branch from 3143927 to ec06ae0 Compare April 30, 2026 15:26
@codspeed-hq

codspeed-hq Bot commented Apr 30, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 37.95%

⚡ 13 improved benchmarks
❌ 3 regressed benchmarks
✅ 128 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "cache-filesystem", scenario '{"name":"mode-development","mode":"development"}' 46.1 MB 25.4 MB +81.64%
Memory benchmark "three-long", scenario '{"name":"mode-development","mode":"development"}' 20.5 MB 13.8 MB +48.23%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-production","mode":"production"}' 6 MB 8.1 MB -25.33%
Memory benchmark "many-modules-esm", scenario '{"name":"mode-production","mode":"production"}' 9.7 MB 7.6 MB +27.37%
Memory benchmark "devtool-eval-source-map", scenario '{"name":"mode-development","mode":"development"}' 1.1 MB 1.4 MB -22.56%
Memory benchmark "many-modules-esm", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,588.8 KB 168.3 KB ×9.4
Memory benchmark "devtool-eval", scenario '{"name":"mode-development","mode":"development"}' 1,144.5 KB 847.3 KB +35.08%
Memory benchmark "lodash", scenario '{"name":"mode-development","mode":"development"}' 4.4 MB 2.8 MB +56.84%
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development","mode":"development"}' 5.5 MB 4.1 MB +33.09%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 980.8 KB 458.1 KB ×2.1
Memory benchmark "json-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 457.1 KB 289.4 KB +57.95%
Memory benchmark "many-chunks-commonjs", scenario '{"name":"mode-development","mode":"development"}' 804.5 KB 1,296.4 KB -37.95%
Memory benchmark "asset-modules-resource", scenario '{"name":"mode-development","mode":"development"}' 2.3 MB 1.6 MB +48.25%
Memory benchmark "three-long", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,244.1 KB 948.3 KB +31.2%
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-production","mode":"production"}' 5.8 MB 4.4 MB +31.3%
Memory benchmark "asset-modules-inline", scenario '{"name":"mode-development","mode":"development"}' 2.9 MB 2.1 MB +36.59%

Comparing claude/update-webpack-sources-memory-UrwI7 (1d511f3) with main (bbcce7f)

Open in CodSpeed

alexander-akait and others added 3 commits April 30, 2026 18:41
Bumps webpack-sources to ^3.4.1 and feeds Source bytes into hashes via
the new Source.prototype.buffers() API. For ConcatSource/ReplaceSource
outputs this removes the intermediate Buffer.concat that source.buffer()
performs, eliminating a peak-memory spike equal to the source's total
size on each hashed asset:

- lib/util/source.js: add updateHashFromSource(hash, source) helper
  that prefers buffers() and falls back for older sources.
- lib/asset/AssetGenerator.js: use the helper in getFullContentHash.
- lib/dependencies/CssIcssExportDependency.js: same for [contenthash]
  in CSS ident generation.
- lib/optimize/RealContentHashPlugin.js: replace mapAndDeduplicateBuffers
  with mapAndDeduplicateSourceBuffers + bufferArraysEqual so dedup and
  hashing both work on chunk arrays without concatenation.

Quick measurement on a 64 MiB ConcatSource:
  buffer()  path: ~121 ms, +64 MiB peak external memory
  buffers() path:  ~64 ms,   0 MiB peak external memory
@alexander-akait alexander-akait force-pushed the claude/update-webpack-sources-memory-UrwI7 branch from ec06ae0 to 1d511f3 Compare April 30, 2026 15:48
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/update-webpack-sources-memory-UrwI7 into main will be
98.93%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/typescript
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%101
   CacheFacade.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%37
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js98.72%100%100%98.72%206, 226, 382
   CodeGenerationResults.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.55%100%100%98.55%1554, 1850, 1857, 1865, 1887, 2783, 3208, 3870, 3899, 3952–3953, 3957, 3962, 3978–3979, 3993–3994, 3999–4000, 4477, 4503, 493, 498, 5211, 5292, 5307, 5332–5333, 5335, 5659, 5664, 5670, 5673, 5685, 5687, 5691, 5707, 5722, 5754, 5808, 5832, 5946, 712–713
   Compiler.js99.55%100%100%99.55%1116–1117, 1125
   ConcatenationScope.js98.59%100%100%98.59%189
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js100%100%100%100%
   ContextModuleFactory.js97.75%100%100%97.75%258, 393, 418, 443, 447, 458
   ContextReplacementPlugin.js100%100%100%100%
   DefinePlugin.js98.92%100%100%98.92%158–159, 175, 194, 268
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.13%100%100%98.13%371, 404
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DotenvPlugin.js97.88%100%100%97.88%237, 378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.89%100%100%98.89%399–403, 542
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js99.49%100%100%99.49%182, 2213–2214, 2217, 2228, 2239, 2250, 278, 3596, 3611, 3635
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.74%100%100%98.74%399, 401, 405
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1304, 1309, 1370, 1384, 1446, 1455
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1004
   ModuleGraphConnection.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%645
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.78%100%100%97.78%1020, 1036, 1123, 1774, 1779–1789, 708, 711, 728, 745, 986
   NormalModuleFactory.js99.47%100%100%99.47%1067, 1376, 466, 478
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js98.75%100%100%98.75%446–447, 452, 454, 518
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js99.16%100%100%99.16%267–268, 610
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js98.86%100%100%98.86%134–135
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js100%100%100%100%
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%325
   cli.js98.71%100%100%98.71%117, 469, 501, 543, 813
   index.js100%100%100%100%
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.22%100%100%97.22%196, 218, 220
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModulesPlugin.js97.77%100%100%97.77%285, 309, 312, 364, 40
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%71, 83, 91
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%106, 113–114, 122, 89
   PackFileCacheStrategy.js96.40%100%100%96.40%1250, 1350, 1354, 1416, 628, 647, 657–659, 661, 677–678, 683, 686, 688, 693, 698, 722, 728, 762, 768, 774, 779, 790, 799, 804–805, 807, 824, 830–831, 833
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   browserslistTargetHandler.js100%100%100%100%
   defaults.js99.13%100%100%99.13%1291–1293, 1301, 270, 273, 278, 282, 467
   normalization.js99%100%100%99%191–192, 258, 273
   target.js100%100%100%100%
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.js100%100%100%100%
   ContainerEntryModuleFactory.js100%100%100%100%
   ContainerExposedDependency.js100%100%100%100%
   ContainerPlugin.js100%100%100%100%
   ContainerReferencePlugin.js100%100%100%100%
   FallbackDependency.js100%100%100%100%
   FallbackItemDependency.js100%100%100%100%
   FallbackModule.js100%100%100%100%
   FallbackModuleFactory.js100%100%100%100%
   HoistContainerReferencesPlugin.js100%100%100%100%
   ModuleFederationPlugin.js100%100%100%100%
   RemoteModule.js100%100%100%100%
   RemoteRuntimeModule.js100%100%100%100%
   

@alexander-akait alexander-akait merged commit 9e91057 into main Apr 30, 2026
58 of 61 checks passed
@alexander-akait alexander-akait deleted the claude/update-webpack-sources-memory-UrwI7 branch April 30, 2026 17:12
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.

2 participants