Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: do not hash localName if the localName is inlined #1410

Merged

Conversation

subzey
Copy link
Contributor

@subzey subzey commented Jan 26, 2022

This PR contains a:

  • bugfix
  • new feature
  • code refactor
  • test update
  • typo fix
  • metadata update

Motivation / Use-Case

Proposal: The localName should not affect the hash if it is present in the generated id inline. This way the generated identifiers are compressed better with both GZIP and Brotli.


Currently the hash is computed from the file relative path and the local identifier name. It's good when the [local] is not preset in the template localIdentName:

✔️ [hash], before the change
.header from one.css becomes .ABCDEF
.button from one.css becomes .GHIJKL
.button from two.css becomes .MNOPQR

But it is redundant if the [local] is present:

😔 [local]--[hash], before the change
.header from one.css becomes .header--ABCDEF
.button from one.css becomes .button--GHIJKL
.button from two.css becomes .button--MNOPQR

The .button from one.css and .button from two.css still have to have different hashes.

But the .header from one.css and .button from the same one.css can never collide, they're from the same module! Omitting localName from the hash we get:

✔️ [local]--[hash], after the change
.header from one.css becomes .header--UVWXYZ
.button from one.css becomes .button--UVWXYZ
.button from two.css becomes .button--FGHIJK

Now all identifiers from the same module has the same hash. (If and only if the localName would be substituted into the resulting generated identifier!) And there are less unique random string runs in the output, making the result more GZIP friendly.

I've used test/__snapshots__/modules-option.test.js.snap for my "gzippability" estimations. This file "before" and "after" has the same "raw" byte length. But the "after" is almost 8% smaller after being GZIPped

We use the similar technique on tradingview.com for a year or so -- using custom getLocalIdent() in the webpack settings. But now it's time to reveal one of our optimization secrets 😀

Breaking Changes

None

Additional Info

⚠️ The generated identifiers will change compared to the previous versions - only if the [local] is used in the template. -- Postponed to the next major release.

@codecov
Copy link

codecov bot commented Jan 26, 2022

Codecov Report

Merging #1410 (381d470) into master (3240394) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1410   +/-   ##
=======================================
  Coverage   96.80%   96.81%           
=======================================
  Files          12       12           
  Lines        1127     1130    +3     
  Branches      408      411    +3     
=======================================
+ Hits         1091     1094    +3     
  Misses         27       27           
  Partials        9        9           
Impacted Files Coverage Δ
src/utils.js 94.72% <100.00%> (+0.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3240394...381d470. Read the comment docs.

@alexander-akait
Copy link
Member

alexander-akait commented Jan 26, 2022

Let's move this under option to avoid breaking changes, we can set it by default in the next major release

@subzey
Copy link
Contributor Author

subzey commented Jan 26, 2022

Sounds reasonable! How should I better do and name it?

Adding a new setting localNamesInHash: false | true | "auto" with the default true. And then in the next major release the default becomes "auto".

Or should it be something more generic like hashStrategy: "include-local-name" | "omit-local-name" | "auto"?

UPD:

I'm going with the options.modules.localIdentHashSource: "use-local-name" | "omit-local-name" | "auto". Is it okay?

@alexander-akait
Copy link
Member

I think options.modules.hashStrategy sounds good

Adds a new setting .modules.hashStrategy, default is backward-compat
___CSS_LOADER_EXPORT___.push([module.id, \\".iHMJbI42 {\\\\n background: red;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n background: blue;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n background: red;\\\\n}\\\\n\\\\n#iHMJbI42 {\\\\n background: green;\\\\n}\\\\n\\\\n.iHMJbI42 .iHMJbI42 {\\\\n color: green;\\\\n}\\\\n\\\\n#iHMJbI42 .iHMJbI42 {\\\\n color: blue;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n color: red;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n margin-left: auto !important;\\\\n margin-right: auto !important;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n margin-left: auto !important;\\\\n margin-right: auto !important;\\\\n}\\\\n\\\\n/* matches elements with class=\\\\\\":\`(\\\\\\" */\\\\n.iHMJbI42 {\\\\n color: aqua;\\\\n}\\\\n\\\\n/* matches elements with class=\\\\\\"1a2b3c\\\\\\" */\\\\n.iHMJbI42 {\\\\n color: aliceblue;\\\\n}\\\\n\\\\n/* matches the element with id=\\\\\\"#fake-id\\\\\\" */\\\\n#iHMJbI42 {\\\\n color: antiquewhite;\\\\n}\\\\n\\\\n/* matches the element with id=\\\\\\"-a-b-c-\\\\\\" */\\\\n#iHMJbI42 {\\\\n color: azure;\\\\n}\\\\n\\\\n/* matches the element with id=\\\\\\"©\\\\\\" */\\\\n#iHMJbI42 {\\\\n color: black;\\\\n}\\\\n\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n.iHMJbI42 { background: lime; }\\\\n\\\\n.iHMJbI42 {\\\\n background: hotpink;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n background: hotpink;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n background: hotpink;\\\\n}\\\\n\\\\n.iHMJbI42 {\\\\n background: hotpink;\\\\n}\\\\n\\", \\"\\"]);
// Exports
___CSS_LOADER_EXPORT___.locals = {
\\"123\\": \\"iHMJbI42\\",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

On a second thought, it's hard to imagine a use-case when 'omit-local-name' could be used:

  • If [local] is used, it works just like 'auto'.
  • If [local] is not there, it screws everything up.

Should we keep it for completeness and probably #1181 like features?

Or is it a bad idea too keep senseless options and it should better be removed?

Copy link
Member

Choose a reason for hiding this comment

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

Let's avoid senseless options, we can add them late, if we will need

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, in 976ff89

Remove senseless .modules.hashStrategy = "omit-local-name"
@alexander-akait
Copy link
Member

@subzey Looks good, let's regenerate lock file due security problem in CI 👍

@subzey
Copy link
Contributor Author

subzey commented Jan 28, 2022

EPERM on Windows? I hope we could just restart the test and have it fixed

@alexander-akait
Copy link
Member

I fixed the problem

README.md Outdated
Should local name be used when computing the hash.

- `'use-local-name'` Each identifier in a module gets its own hash digest.
- `'auto'` Identifiers from the same module shares the same hash digest if it's possible. Use this value to optimize the output for better GZIP or Brotli compression.
Copy link
Member

Choose a reason for hiding this comment

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

After review, I think 'auto' is not good name, 'auto' sounds like it should be default value, yes, maybe this should be the default (as I written above), but let's found better name, I think we can do it more understandable, I suggest: resource-path and resource-path-and-local-name, the developer can immediately understand what is used from the name, instead of reading the documentation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a chance a developer would set a resource-path, but the hashing would work as resource-path-and-local-name because of the automagic circuit breaker. That may lead to confusion, especially when copypasting the config partially into StackOverflow or GH issues.

Maybe minimal-subset or infer-from-template or something like that? Something that implies the automatic nature of the setting

I'm not a big fan of bikeshedding and just will use your suggestion if we don't come up with a decision after a short while

Copy link
Member

Choose a reason for hiding this comment

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

Let's use minimal-subset 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed the option enum values in 381d470

@alexander-akait alexander-akait merged commit ca4abce into webpack-contrib:master Feb 2, 2022
@alexander-akait
Copy link
Member

Thanks

@subzey subzey deleted the feat/local-names-in-hashes branch February 8, 2022 13:06
@dovidweisz
Copy link
Contributor

dovidweisz commented Mar 15, 2022

This is really nice.

In what release is this (or will it be) available?

Upgrading css-loader to version 6.7.1 worked for me :)

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.

None yet

3 participants