Skip to content

feat: remove empty needless js output for normal css module#20162

Merged
alexander-akait merged 6 commits intowebpack:mainfrom
JSerFeng:feat/remove-empty-css-js-output
Feb 26, 2026
Merged

feat: remove empty needless js output for normal css module#20162
alexander-akait merged 6 commits intowebpack:mainfrom
JSerFeng:feat/remove-empty-css-js-output

Conversation

@JSerFeng
Copy link
Contributor

Summary

See https://github.com/orgs/webpack/discussions/20161

What kind of change does this PR introduce?

Did you add tests for your changes?

Does this PR introduce a breaking change?

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

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 25, 2025

Merging this PR will degrade performance by 35.22%

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 142 untouched benchmarks

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

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "many-chunks-esm", scenario '{"name":"mode-production","mode":"production"}' 8.8 MB 7.3 MB +20.63%
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 401.6 KB 620 KB -35.22%

Comparing JSerFeng:feat/remove-empty-css-js-output (1ed3fa0) with main (c323b39)

Open in CodSpeed

{
name: PLUGIN_NAME,
stage: 100
},
Copy link
Member

Choose a reason for hiding this comment

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

Why we need to change stage here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The 100 is just a draft here, main reason is to ensure this plugin run after JavascriptPlugin

We register dependencyTemplate, we don't want it to be override by others. compilation.dependencyTemplates.set. Can we do that in other hook maybe ?

Copy link
Member

Choose a reason for hiding this comment

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

hm, need to think about it, lets' look at tests firstly, I will think about it

Copy link
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.

I am fine with both solution to be honestly, just maybe ProxyHarmonyTemplate rename to ProxyHarmonyImportSideEffectDependency to be more clear and move it to HarmonyImportSideEffectDependency file

@JSerFeng
Copy link
Contributor Author

just maybe ProxyHarmonyTemplate rename to ProxyHarmonyImportSideEffectDependency

Done

and move it to HarmonyImportSideEffectDependency file

I think ProxyHarmonyImportSideEffectDependencyTemplate should be still placed in CssModulesPlugin, as it's only a proxy template for CssModulesPlugin

I'm checking the tests and see if HMR has issues

@alexander-akait
Copy link
Member

@JSerFeng Feel free to ping me if you will need a help

@JSerFeng JSerFeng force-pushed the feat/remove-empty-css-js-output branch 4 times, most recently from 8510363 to a05afc4 Compare November 27, 2025 10:18
compilation.dependencyTemplates.set(
HarmonyImportSideEffectDependency,
new ProxyHarmonyImportSideEffectDependencyTemplate()
);
Copy link
Member

Choose a reason for hiding this comment

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

There is another question, what about require or other things, I think we should try to solve it for any cases, not just for side effect imports...

Copy link
Contributor Author

@JSerFeng JSerFeng Nov 27, 2025

Choose a reason for hiding this comment

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

Currently I think it's the most safe way, I choose EsmSideEffectDependency because it just means this connection is for side effect, and we can ensure the js output of CssModule has no side effect(when disabled hmr). so this connection can be safely removed.

But others like require, user can write like this

const o = require('./foo.css')

call(o)

There is only one connection, and it contains CjsRequireDependency. CjsRequireDependency is not like EsmSideEffectDependency, it can have side effect and likely to consume the result. We cannot safely remove this connection.

Copy link
Member

Choose a reason for hiding this comment

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

What about just require('./foo.css')? Technically we can remove empty JS modules here too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but we can't do the same for require using the solution of this PR

SideEffectDependency means connection and executing side effect, we can know css has no side effect so we can remove SideEffectDependency.

If exports is used, there is SideEffectDependency and SpecifierDependency, so we can know it's exports are used

If exports is not used, there is only SideEffectDependency, so we can know it's exports are not used

But RequireDependency means connection and has access the exports, we can't know if the importer uses exports or not. Whether exports are used or not can not be told just by dependencies.

image image

You can see there is no difference.

Copy link
Contributor Author

@JSerFeng JSerFeng Nov 27, 2025

Choose a reason for hiding this comment

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

How about reading exportsType info ? Seems a way, I'll check if that's doable without affecting performance.

Copy link
Member

Choose a reason for hiding this comment

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

@JSerFeng I am fine with only for import, just think how we can solve it more common way (discussion, no more)

How about reading exportsType info ? Seems a way, I'll check if that's doable without affecting performance.

Feel free to experiments 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried, but unfortunately we can't tell if exports are used or not from exports info.

require('style.css')

FlagDependencyUsagePlugin doesn't analyze above syntax, even with __esModule.

I originally want to try removing js when CssModule has exportType === 'link' && !buildInfo.isCssModule, but that would break users who use if (require('./style.css') === undefined) {})

Copy link
Member

Choose a reason for hiding this comment

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

@JSerFeng PR looks good, let's resolve it, I still think we can move logic to HarmonyImportSideEffectDependency.apply and avoid stage here, so we will have the only one HarmonyImportSideEffectDependencyTemplate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, done.

I was thinking that HarmonyImportSideEffectDependency should only render when ref module's sourceType is JavaScript. But it seems other sourceType need it too, like ConsumeShared or Remote or Runtime.

So now don't render if only all sourceType is Css.

@JSerFeng JSerFeng force-pushed the feat/remove-empty-css-js-output branch 2 times, most recently from 889a09a to 958ec76 Compare November 28, 2025 06:13
@JSerFeng JSerFeng marked this pull request as ready for review November 28, 2025 06:15
@changeset-bot
Copy link

changeset-bot bot commented Nov 29, 2025

🦋 Changeset detected

Latest commit: 1ed3fa0

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

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

@JSerFeng JSerFeng force-pushed the feat/remove-empty-css-js-output branch 5 times, most recently from 21e8c42 to b4a3363 Compare November 29, 2025 14:21
// when no hmr required, css module js output contains no sideEffects at all
// js sideeffect connection doesn't require js type output
if (
module.hot !== true &&
Copy link
Member

@xiaoxiaojx xiaoxiaojx Jan 25, 2026

Choose a reason for hiding this comment

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

It seems we can remove the module.hot !== true line.
Since we no longer generate JS code for import "./index.css", it doesn’t exist in the output __webpack_modules__, Its behavior is closer to being self-accepted. Then will skip generating the side-effect hmrCode for it.

@alexander-akait
Copy link
Member

@JSerFeng And let's rebase, we made more optimizations, but this will be useful too, thanks

@JSerFeng JSerFeng force-pushed the feat/remove-empty-css-js-output branch 5 times, most recently from a16a587 to a52f09d Compare February 3, 2026 07:56
"B-2Index": "0: ./B-2.js",
BIndex: "0: ./B.js",
mainIndex: "0: ./main.js",
sharedIndex: "1: css ./m.css"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

don't know why there is no ./n.css in previous version, seems it should be here

Copy link
Member

Choose a reason for hiding this comment

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

No issues with the test case changes here, ./n.css got concatenated earlier.

data[`${name}Index`] = text;
}
expect(data).toEqual({
dynamicIndex: "0: css ./a.css",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

same above

@alexander-akait
Copy link
Member

@JSerFeng Oh, sorry, forgot about this PR, can you rebase and we will merge, thank you

@JSerFeng JSerFeng force-pushed the feat/remove-empty-css-js-output branch from a52f09d to 1ed3fa0 Compare February 26, 2026 07:32
@alexander-akait alexander-akait merged commit e43fcd0 into webpack:main Feb 26, 2026
54 of 55 checks passed
@github-actions
Copy link
Contributor

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

Install it locally:

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

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.

3 participants