Skip to content

fix: support CSS text/css-style-sheet exportType module concatenation#20851

Merged
alexander-akait merged 5 commits into
mainfrom
fix/css-text-css-style-sheet-concatenation
May 4, 2026
Merged

fix: support CSS text/css-style-sheet exportType module concatenation#20851
alexander-akait merged 5 commits into
mainfrom
fix/css-text-css-style-sheet-concatenation

Conversation

@xiaoxiaojx
Copy link
Copy Markdown
Member

Summary
Rename export-type-text-concatenation to export-type-concatenation and add test cases for all CSS exportTypes: text, css-style-sheet, style, and link (CSS modules). Each type verifies its specific output format (string, CSSStyleSheet, <style> injection, class name export) while all modules are concatenated into a single ConcatenatedModule.

What kind of change does this PR introduce?
fix

Did you add tests for your changes?
Yes

Does this PR introduce a breaking change?
No

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

Use of AI
Partial

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 20, 2026

🦋 Changeset detected

Latest commit: 4369850

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

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

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

Install it locally:

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Codecov Report

❌ Patch coverage is 98.59155% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.35%. Comparing base (25f937c) to head (4369850).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
lib/Dependency.js 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #20851      +/-   ##
==========================================
+ Coverage   91.32%   91.35%   +0.03%     
==========================================
  Files         562      562              
  Lines       55661    55736      +75     
  Branches    14719    14764      +45     
==========================================
+ Hits        50831    50920      +89     
+ Misses       4830     4816      -14     
Flag Coverage Δ
integration 90.31% <98.59%> (+0.03%) ⬆️
test262 45.94% <32.39%> (-0.01%) ⬇️
unit 36.14% <16.90%> (-0.02%) ⬇️

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.

Comment thread lib/css/CssGenerator.js Outdated
const moduleId = generateContext.chunkGraph.getModuleId(module);
const moduleId =
generateContext.chunkGraph.getModuleId(module) ||
module.identifier();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe always use module.identifier()? Sounds like it will work without any problems, also we need to check do we need escape them to avoid weird characters in JS code

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.

the identifier is already passed through JSON.stringify(), so special characters in the path are properly escaped in the JS output.

/**
* @returns {boolean} true
*/
get supportsConcatenation() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this improvement, no more hard-coded code, but let's make this as a Function, I am afraid in future when we will concat non JS modules with JS modules we can have more complex cases (where we need to pass module graph or maybe more)

Copy link
Copy Markdown
Member

@alexander-akait alexander-akait left a comment

Choose a reason for hiding this comment

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

Looks very good, let's make some improvements above, thanks

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 20, 2026

Merging this PR will degrade performance by 49.52%

⚡ 6 improved benchmarks
❌ 1 regressed benchmark
✅ 137 untouched benchmarks

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

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "devtool-eval", scenario '{"name":"mode-development","mode":"development"}' 829.5 KB 599 KB +38.5%
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development","mode":"development"}' 4,717.3 KB 671.6 KB ×7
Memory benchmark "side-effects-reexport", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,375.1 KB 848.5 KB +62.06%
Memory benchmark "concatenate-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 183.4 KB 363.3 KB -49.52%
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 367.9 KB 154.3 KB ×2.4
Memory benchmark "three-long", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 1,147.5 KB 855.3 KB +34.15%
Memory benchmark "json-modules", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 292.5 KB 220.4 KB +32.75%

Comparing fix/css-text-css-style-sheet-concatenation (4369850) with main (cad2b54)

Open in CodSpeed

@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch from 1d9cb7b to d30532a Compare April 21, 2026 06:39
@xiaoxiaojx xiaoxiaojx marked this pull request as draft April 21, 2026 12:24
Copy link
Copy Markdown
Member

@alexander-akait alexander-akait left a comment

Choose a reason for hiding this comment

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

Looks good, the only thing I am afraid supportsConcatenation is a new method, so if someone use custom Dependency implement (or using old version of webpack, where we don't have) we will fail here, make sense to add typeof check with TODO about removing this check in webpack@6 and we can merge

@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch from d30532a to 2400723 Compare April 23, 2026 15:56
Comment thread lib/optimize/ConcatenatedModule.js Outdated
if (concatId !== null) {
chunkGraph.setModuleId(this.rootModule, concatId);
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am afraid it will be a breaking change and potential module overlapping, we need to think how to fix it without it

@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch from 2400723 to 0247fa5 Compare April 25, 2026 07:00
Comment thread test/hotCases/css/export-type-concatenation/index.js Fixed
@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch 2 times, most recently from 21fcc44 to 60c4ca3 Compare April 25, 2026 09:16
@xiaoxiaojx xiaoxiaojx marked this pull request as ready for review April 25, 2026 17:11
@xiaoxiaojx
Copy link
Copy Markdown
Member Author

Sorry for the delay — @alexander-akait, thanks for the great review and suggestions.

@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch from a5831dd to a5720d6 Compare April 28, 2026 04:32
@xiaoxiaojx xiaoxiaojx marked this pull request as draft May 2, 2026 12:04
@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch from 0872686 to f1aee50 Compare May 2, 2026 13:36
@xiaoxiaojx xiaoxiaojx marked this pull request as ready for review May 2, 2026 13:56
@xiaoxiaojx xiaoxiaojx force-pushed the fix/css-text-css-style-sheet-concatenation branch from f1aee50 to 7ab61de Compare May 2, 2026 15:36
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

Types Coverage

Coverage after merging fix/css-text-css-style-sheet-concatenation into main will be
98.92%
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.20%100%100%98.20%379, 425
   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%1297–1299, 1307, 270, 273, 278, 282, 468
   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 3374804 into main May 4, 2026
61 checks passed
@alexander-akait alexander-akait deleted the fix/css-text-css-style-sheet-concatenation branch May 4, 2026 16:40
alexander-akait pushed a commit to webpack/webpack.js.org that referenced this pull request May 18, 2026
…107) (#8244)

Webpack 5.107 brings two additions to the experiments.css feature set:

- Scope hoisting (module concatenation) now applies to CSS Modules with
  exportType "text", "css-style-sheet", "style", or "link", reducing
  runtime overhead in CSS-heavy bundles when
  optimization.concatenateModules is enabled.
- @value identifiers can be used as the path argument to @import and
  inside url() references, so shared paths/assets are defined once and
  reused. Both quoted and bare forms are accepted and resolved through
  webpack's normal asset pipeline.

Adds two bullets to the existing experimental features list in
experiments.css.

Refs:
- webpack/webpack#20851 (scope hoisting)
- webpack/webpack#20925 (@value in URLs)
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