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: Brotli compression support #432

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

dcsaszar
Copy link

@dcsaszar dcsaszar commented Apr 10, 2021

This is my go at #113

Instead of adding Brotli support, I added low-level options to replace Gzip support. The overall approach is inspired by me trying to avoid the hassle of conditional code based on the Node.js version, my own requirements (no need for high-level options), and #113 (comment):

I have the use-case that the pipeline analysis doesn't use gzip at all, it relies completely on brotli

Updated with high-level option compressionAlgorithm: gzip | brotli

Example usage:

new BundleAnalyzerPlugin({
  defaultSizes: "compressed",
  compressionAlgorithm: "brotli",
})

Regarding the requirements...

...stated in #113 (comment)

The only concern I have is the increasing of analyze time. Agree with @valscion here - it would be nice to see a proof-of-concept PR and comparison of analyze times with and without it.

For me, the POC was trying out https://www.npmjs.com/package/webpack-bundle-analyzer-brotli, which works great, but is outdated.
The new option is opt-in. Either gzip or Brotli. Users who want Brotli support will be willing to accept the longer wait.

It might be possible to add brotli support if it would be calculated only when some CLI or plugin option would be there. That way it would not increase analyze time for those who don't yet care about Brotli.

The user must specify the compression as an option. They know their environment (e.g. the Node.js version) and resources best.

The crux is that we don't want this to increase analyze time, and this feature would have to be opt-in. Currently it might require some architectural changes, though, so it isn't that easy to do.

I tried to keep the changes at a minimum.
In this PR I even left most of the internals (including a lot of gzip* naming) untouched. A cleanup of the internal naming could be done independently, but I'm not sure if this is required.

@dcsaszar dcsaszar force-pushed the dave-custom-compression-support-aka-brotli branch from e9d87bf to 32dc3e7 Compare April 10, 2021 19:49
@valscion
Copy link
Member

Thanks for taking a stab at this!

I'm checking out the PR description and the API changes now, and are not focusing on the internal code changes yet as I write this comment, so consider this comment a high-level review ☺️

I'm a bit concerned how this API separetes the compressedSize and compressedSizeLabel options from each other. I'd hope we could have an option like compressionType or compressionAlgorithm instead where one could choose gzip or brotli for now, and it would then automatically set the label.

I can see that we could then later, after this pull request, consider opening up the API more and allow some sort of a compressionAlgorithm: {function} API — and then we'd have to figure out how we want to handle the label in that case.

For now, to ease reviewing, I'd favor a less open API change where we could focus only on choosing between gzip and brotli. We could later discuss the "custom algorithm" option, if we want that.

This "brotli" or "gzip" choice could very well be implemented only at the CLI and Plugin options levels — the internal wiring could keep on using the compressedSize and compressedSizeLabel options you've implemented here. So my comment about only allowing the choice between gzip and brotli would affect only the code in CLI and Plugin options handling, and not the internal wiring.

What do you think? Am I making any sense? 😅


Oh, and as you can see, the tests have become unfortunately quite flaky indeed. I haven't had the time to look into why the tests are timing out or getting aborted. Any help in getting the GitHub actions to run properly would be very much appreciated and would also help reviewing this PR once we're happy with the way the feature has been designed 🙏

We haven't really done anything about the CI since #402 where the checks were running successfully. So something has likely changed since then, and it likely isn't anything we've written as there isn't quite much git history since removing Travis CI: https://github.com/webpack-contrib/webpack-bundle-analyzer/commits/master

@dcsaszar
Copy link
Author

compressionAlgorithm: 'gzip' | 'brotli' sounds good to me. I'll give it a go.

@deanshub deanshub mentioned this pull request Apr 13, 2021
@valscion
Copy link
Member

The CI is now hopefully fixed, thanks to #435. Feel free to merge or rebase newest changes in to this PR to hopefully get a green CI ☺️

To be in line with the new `compressedSize*` options.

Supports `gzip` as a synonym for backwards compatibility.
Internal naming is also still `gzip*`.
@dcsaszar dcsaszar force-pushed the dave-custom-compression-support-aka-brotli branch from 32dc3e7 to 85a6376 Compare April 14, 2021 23:19
@dcsaszar dcsaszar force-pushed the dave-custom-compression-support-aka-brotli branch from 85a6376 to 70d8cf1 Compare April 14, 2021 23:33
@dcsaszar
Copy link
Author

@valscion ready for the next review!

@dcsaszar dcsaszar changed the title feat: Custom compression support (a.k.a. Brotli) feat: Brotli compression support Apr 14, 2021
Copy link
Member

@valscion valscion left a comment

Choose a reason for hiding this comment

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

I like the direction of this PR 👍. It was a good idea to get rid of the gzip-size package and rely on the native zlib module also for the gzip case — it makes the code paths for brotli and gzip look a bit more similar, and all the previous usages of gzip-size were easy to find 👍

src/sizeUtils.js Show resolved Hide resolved
test/plugin.js Outdated
await webpackCompile(config, '4.44.2');
await expectValidReport({
parsedSize: 1311,
gzipSize: 302
Copy link
Member

Choose a reason for hiding this comment

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

I think that, even though it increases the size of this PR, it might be better to aim to use brotliSize in the data when Brotli is used instead of gzipSize. It would be quite confusing for direct users of the report JSON file (e.g. those who supply --analyzerMode json value to CLI) if the key would remain as gzipSize even though Brotli was selected.

What do you think? Does the confusion risk outweigh changing the code more?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just compressedSize?

Copy link
Author

@dcsaszar dcsaszar Apr 15, 2021

Choose a reason for hiding this comment

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

@th0r I thought about this too, as I'm not a friend of key explosion.

On the other hand I'm OK with gzipSize, brotliSize:

  • backwards compatible
  • unambiguous for (human/bot) readers of the raw data
  • theoretically allows for dual-compression stats

src/viewer.js Outdated
@@ -126,21 +136,23 @@ async function generateReport(bundleStats, opts) {
openBrowser = true,
reportFilename,
reportTitle,
compressionAlgorithm = 'gzip',
Copy link
Member

Choose a reason for hiding this comment

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

Oh yeah, I see you've found that we have multiple places where we're defining default values for options, and haven't really made up our mind where they'd belong 😅

How do you feel about moving all the compressionAlgorithm = 'gzip default value definitions to the top-level usages? That is, moving the compressionAlgorithm = 'gzip' code to the plugin starting point in src/BundleAnalyzerPlugin.js. The CLI starting point in src/bin/analyzer.js already seems to define the default value for the --compression-algorithm to be gzip so we should be covered there.

Then we could get rid of all the compressionAlgorithm = 'gzip' defaulting in other code paths and assume that we have the value filled in in those code paths.

README.md Outdated
Comment on lines 150 to 153
### `gzip`
### `compressed`
Copy link
Member

Choose a reason for hiding this comment

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

Can we make it so that the added option for defaultSizes would be brotli instead of compressed? That way the current, default gzip usage would not have to be changed.

That way we could keep the gzip-related documentation intact and only worry about adding new documentation related to the brotli options.

I could imagine that the fourth size definition documentation here could look something like this:

brotli

This is the size of running the parsed bundles/modules through Brotli compression. Note: This needs the compression algorithm to be configured to use Brotli.

return [
{label: 'Stat', prop: 'statSize'},
{label: 'Parsed', prop: 'parsedSize'},
{label: window.compressedSizeLabel, prop: 'gzipSize'}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we change gzipSize to compressedSize?

src/analyzer.js Outdated
@@ -102,7 +111,7 @@ function getViewerData(bundleStats, bundleDir, opts) {

if (assetSources) {
asset.parsedSize = Buffer.byteLength(assetSources.src);
asset.gzipSize = gzipSize.sync(assetSources.src);
asset.gzipSize = compressedSize(assetSources.src);
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

Suggested change
asset.gzipSize = compressedSize(assetSources.src);
asset.compressedSize = compressedSize(assetSources.src);

src/analyzer.js Outdated
} = opts || {};

const isAssetIncluded = createAssetsFilter(excludeAssets);

const compressedSize = COMPRESSED_SIZE[compressionAlgorithm];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's better call it getCompressedSize please

src/analyzer.js Outdated
@@ -143,7 +152,7 @@ function getViewerData(bundleStats, bundleDir, opts) {
}

asset.modules = assetModules;
asset.tree = createModulesTree(asset.modules);
asset.tree = createModulesTree(asset.modules, {compressedSize});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
asset.tree = createModulesTree(asset.modules, {compressedSize});
asset.tree = createModulesTree(asset.modules, {getCompressedSize});

src/viewer.js Outdated
@@ -22,6 +22,14 @@ function resolveTitle(reportTitle) {
}
}

function resolveDefaultSizes(defaultSizes) {
return defaultSizes === 'compressed' ? 'gzip' : defaultSizes;
Copy link
Collaborator

@th0r th0r Apr 15, 2021

Choose a reason for hiding this comment

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

Can we inverse it please? I mean, deprecate gzip default size and use compressed instead so it will look like this:

Suggested change
return defaultSizes === 'compressed' ? 'gzip' : defaultSizes;
return defaultSizes === 'gzip' ? 'compressed' : defaultSizes;

Copy link
Author

Choose a reason for hiding this comment

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

My current approach, based on #432 (comment), is:

function resolveDefaultSizes(defaultSizes, compressionAlgorithm) {
  if (['gzip', 'brotli'].includes(defaultSizes)) return compressionAlgorithm;
  return defaultSizes;
}

That is, we drop compressed. The config is "forgiving" if the user specifies the wrong defaultSizes.
WDYT?

@dcsaszar
Copy link
Author

@valscion @th0r next round.

@dcsaszar dcsaszar requested review from valscion and th0r April 15, 2021 10:15
@@ -61,7 +61,8 @@ new BundleAnalyzerPlugin(options?: object)
|**`analyzerPort`**|`{Number}` or `auto`|Default: `8888`. Port that will be used in `server` mode to start HTTP server.|
|**`reportFilename`**|`{String}`|Default: `report.html`. Path to bundle report file that will be generated in `static` mode. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).|
|**`reportTitle`**|`{String\|function}`|Default: function that returns pretty printed current date and time. Content of the HTML `title` element; or a function of the form `() => string` that provides the content.|
|**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
|**`defaultSizes`**|One of: `stat`, `parsed`, `gzip`, `brotli`|Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.|
Copy link
Collaborator

Choose a reason for hiding this comment

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

In this case user will have to provide compression algorithm twice: for compressionAlgorithm and defaultSizes and it opens possibilities for invalid option combinations like defaultSizes: 'gzip', compressionAlgorithm: 'brotli' which don't make sense. I personally would change defaultSizes: 'gzip' to defaultSizes: 'compressed' and deprecate the former, but support it (convert to compressed).

Copy link
Author

Choose a reason for hiding this comment

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

The current implementation handles the mismatch gracefully, so it's more of a cosmetic issue on the user side.

Reintroducing compressed would help here. If used, the config always looks "good", even when switching the algorithm. If we support compressed, fail on mismatches between compressionAlgorithm and defaultSizes?

{label: 'Parsed', prop: 'parsedSize'}
];

if (window.compressionAlgorithm === 'gzip') items.push({label: 'Gzipped', prop: 'gzipSize'});
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would change gzipSize/brotliSize to compressedSize but looks like it will be a breaking change.

Copy link
Member

Choose a reason for hiding this comment

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

Would be nice if we could first do this as a new feature, release in a minor version and then do a major release later to consolidate the API on on plain compressedSize. That has been my opinion during this PR review at least ☺️.

What do you think of this plan?

@valscion
Copy link
Member

I'll let you @th0r review this ☺️ — I think this is heading into a good direction and trust your judgement here 👍

@RodrigoTomeES
Copy link

Hi, Is there any update on this functionality? Thanks for your work!

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Sep 6, 2022

CLA Signed

The committers listed above are authorized under a signed CLA.

@pumano
Copy link

pumano commented Aug 4, 2023

Amazing feature not going to production just by personal opinions on how to name it brotli or general word "compression".

@dcsaszar
Copy link
Author

dcsaszar commented Aug 4, 2023

Not sure, maybe a misunderstanding about who's turn it is? @th0r could you clarify the to-dos here?

@dcsaszar
Copy link
Author

@valscion in #432 (comment) you handed the review to @th0r.
I see his last PR was #449 so I'm not sure he's still active/in charge. I'd like to continue working on this, but the responsibilities and todos should be clear.

@valscion
Copy link
Member

Yeah looks like I can take over reviewing this. Now that brotli is used more and more, having this show in webpack-bundle-analyzer would definitely be useful.

Let's first get this PR up-to-date and then I can start reviewing again. I've lost context of this PR so I'll basically have to start the review process from scratch as well

@RuwaidLouisHejare
Copy link

RuwaidLouisHejare commented Aug 22, 2023

Fantastic to see this PR still is active. I would love to see this in production. Keep up the great work!

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

6 participants