main b3f44b0de81e cached
190 files
1.9 MB
566.9k tokens
574 symbols
1 requests
Download .txt
Showing preview only (2,050K chars total). Download the full file or copy to clipboard to get everything.
Repository: webpack/webpack-bundle-analyzer
Branch: main
Commit: b3f44b0de81e
Files: 190
Total size: 1.9 MB

Directory structure:
gitextract_2b7jp3oi/

├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .npm-upgrade.json
├── .nvmrc
├── .prettierignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bin/
│   └── install-test-webpack-versions.sh
├── client/
│   ├── .eslintrc.json
│   ├── components/
│   │   ├── Button.css
│   │   ├── Button.jsx
│   │   ├── Checkbox.css
│   │   ├── Checkbox.jsx
│   │   ├── CheckboxList.css
│   │   ├── CheckboxList.jsx
│   │   ├── CheckboxListItem.jsx
│   │   ├── ContextMenu.css
│   │   ├── ContextMenu.jsx
│   │   ├── ContextMenuItem.css
│   │   ├── ContextMenuItem.jsx
│   │   ├── Dropdown.css
│   │   ├── Dropdown.jsx
│   │   ├── Icon.css
│   │   ├── Icon.jsx
│   │   ├── ModuleItem.css
│   │   ├── ModuleItem.jsx
│   │   ├── ModulesList.css
│   │   ├── ModulesList.jsx
│   │   ├── ModulesTreemap.css
│   │   ├── ModulesTreemap.jsx
│   │   ├── Search.css
│   │   ├── Search.jsx
│   │   ├── Sidebar.css
│   │   ├── Sidebar.jsx
│   │   ├── Switcher.css
│   │   ├── Switcher.jsx
│   │   ├── SwitcherItem.jsx
│   │   ├── ThemeToggle.css
│   │   ├── ThemeToggle.jsx
│   │   ├── Tooltip.css
│   │   ├── Tooltip.jsx
│   │   ├── Treemap.jsx
│   │   └── types.js
│   ├── lib/
│   │   └── PureComponent.jsx
│   ├── localStorage.js
│   ├── store.js
│   ├── utils.js
│   ├── viewer.css
│   └── viewer.jsx
├── eslint.config.mjs
├── jest.config.js
├── package.json
├── prettier.config.mjs
├── src/
│   ├── BundleAnalyzerPlugin.js
│   ├── Logger.js
│   ├── analyzer.js
│   ├── bin/
│   │   └── analyzer.js
│   ├── index.js
│   ├── parseUtils.js
│   ├── sizeUtils.js
│   ├── statsUtils.js
│   ├── template.js
│   ├── tree/
│   │   ├── BaseFolder.js
│   │   ├── ConcatenatedModule.js
│   │   ├── ContentFolder.js
│   │   ├── ContentModule.js
│   │   ├── Folder.js
│   │   ├── Module.js
│   │   ├── Node.js
│   │   └── utils.js
│   ├── utils.js
│   └── viewer.js
├── test/
│   ├── .eslintrc.json
│   ├── .gitignore
│   ├── Logger.js
│   ├── analyzer.js
│   ├── bundles/
│   │   ├── invalidBundle.js
│   │   ├── validBundleWithArrowFunction.js
│   │   ├── validBundleWithArrowFunction.modules.json
│   │   ├── validBundleWithEsNextFeatures.js
│   │   ├── validBundleWithEsNextFeatures.modules.json
│   │   ├── validBundleWithIIFE.js
│   │   ├── validBundleWithIIFE.modules.json
│   │   ├── validCommonBundleWithDedupePlugin.js
│   │   ├── validCommonBundleWithDedupePlugin.modules.json
│   │   ├── validCommonBundleWithModulesAsArray.js
│   │   ├── validCommonBundleWithModulesAsArray.modules.json
│   │   ├── validCommonBundleWithModulesAsObject.js
│   │   ├── validCommonBundleWithModulesAsObject.modules.json
│   │   ├── validExtraBundleWithModulesAsArray.js
│   │   ├── validExtraBundleWithModulesAsArray.modules.json
│   │   ├── validExtraBundleWithModulesInsideArrayConcat.js
│   │   ├── validExtraBundleWithModulesInsideArrayConcat.modules.json
│   │   ├── validExtraBundleWithNamedChunk.js
│   │   ├── validExtraBundleWithNamedChunk.modules.json
│   │   ├── validJsonpWithArrayConcatAndEntryPoint.js
│   │   ├── validJsonpWithArrayConcatAndEntryPoint.modules.json
│   │   ├── validNodeBundle.js
│   │   ├── validNodeBundle.modules.json
│   │   ├── validUmdLibraryBundleWithModulesAsArray.js
│   │   ├── validUmdLibraryBundleWithModulesAsArray.modules.json
│   │   ├── validWebpack4AsyncChunk.js
│   │   ├── validWebpack4AsyncChunk.modules.json
│   │   ├── validWebpack4AsyncChunkAndEntryPoint.js
│   │   ├── validWebpack4AsyncChunkAndEntryPoint.modules.json
│   │   ├── validWebpack4AsyncChunkUsingCustomGlobalObject.js
│   │   ├── validWebpack4AsyncChunkUsingCustomGlobalObject.modules.json
│   │   ├── validWebpack4AsyncChunkUsingSelfInsteadOfWindow.js
│   │   ├── validWebpack4AsyncChunkUsingSelfInsteadOfWindow.modules.json
│   │   ├── validWebpack4AsyncChunkUsingThisInsteadOfWindow.js
│   │   ├── validWebpack4AsyncChunkUsingThisInsteadOfWindow.modules.json
│   │   ├── validWebpack4AsyncChunkWithOptimizedModulesArray.js
│   │   ├── validWebpack4AsyncChunkWithOptimizedModulesArray.modules.json
│   │   ├── validWebpack4AsyncChunkWithWebWorkerChunkTemplatePlugin.js
│   │   ├── validWebpack4AsyncChunkWithWebWorkerChunkTemplatePlugin.modules.json
│   │   ├── validWebpack5LegacyBundle.js
│   │   ├── validWebpack5LegacyBundle.modules.json
│   │   ├── validWebpack5ModernBundle.js
│   │   └── validWebpack5ModernBundle.modules.json
│   ├── dev-server/
│   │   ├── .gitignore
│   │   ├── src.js
│   │   └── webpack.config.js
│   ├── dev-server.js
│   ├── helpers.js
│   ├── parseUtils.js
│   ├── plugin.js
│   ├── src/
│   │   ├── a-clone.js
│   │   ├── a.js
│   │   ├── b.js
│   │   └── index.js
│   ├── stats/
│   │   ├── extremely-optimized-webpack-5-bundle/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── minimal-stats/
│   │   │   └── stats.json
│   │   ├── webpack-5-bundle-with-concatenated-entry-module/
│   │   │   ├── app.js
│   │   │   ├── expected-chart-data.json
│   │   │   └── stats.json
│   │   ├── webpack-5-bundle-with-multiple-entries/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── webpack-5-bundle-with-single-entry/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-array-config/
│   │   │   ├── config-1-main.js
│   │   │   ├── config-2-main.js
│   │   │   └── stats.json
│   │   ├── with-children-array.json
│   │   ├── with-cjs-chunk.json
│   │   ├── with-invalid-chunk/
│   │   │   ├── invalid-chunk.js
│   │   │   ├── stats.json
│   │   │   └── valid-chunk.js
│   │   ├── with-invalid-dynamic-require.json
│   │   ├── with-missing-chunk/
│   │   │   ├── stats.json
│   │   │   └── valid-chunk.js
│   │   ├── with-missing-module-chunks/
│   │   │   ├── stats.json
│   │   │   └── valid-chunk.js
│   │   ├── with-missing-parsed-module/
│   │   │   ├── bundle.js
│   │   │   └── stats.json
│   │   ├── with-module-concatenation-info/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-modules-chunk.json
│   │   ├── with-modules-in-chunks/
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-multiple-entrypoints/
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-no-entrypoints/
│   │   │   └── stats.json
│   │   ├── with-non-asset-asset/
│   │   │   ├── bundle.js
│   │   │   └── stats.json
│   │   ├── with-special-chars/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-worker-loader/
│   │   │   ├── bundle.js
│   │   │   ├── bundle.worker.js
│   │   │   └── stats.json
│   │   └── with-worker-loader-dynamic-import/
│   │       ├── 1.bundle.js
│   │       ├── 1.bundle.worker.js
│   │       ├── bundle.js
│   │       ├── bundle.worker.js
│   │       └── stats.json
│   ├── statsUtils.js
│   ├── utils.js
│   └── viewer.js
├── tsconfig.json
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
// Babel config for Node
// Compiles sources, gulpfile and tests
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": { "node": "16.20.2" }
      }
    ]
  ]
}


================================================
FILE: .browserslistrc
================================================
# Supported browsers

last 2 Chrome major versions
last 2 Firefox major versions
last 1 Safari major version


================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8

indent_style = space
indent_size = 2

end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .github/workflows/main.yml
================================================
name: main
on:
  push:
    branches:
      - main
  pull_request:
jobs:
  build-and-test:
    strategy:
      fail-fast: false
      matrix:
        node:
          - version: 20.x
          - version: 22.x
          - version: 24.x
    runs-on: ubuntu-22.04
    name: Tests on Node.js v${{ matrix.node.version }}
    steps:
      - name: Checkout repo
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup node
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          node-version: ${{ matrix.node.version }}
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build sources
        run: ${{ matrix.node.env }} npm run build

      - name: Run tests
        run: ${{ matrix.node.env }} npm run test:coverage

      - name: Codecov
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup node
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          node-version: "22.x"
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Build sources
        run: npm run build

      - name: Run lint
        run: npm run lint


================================================
FILE: .gitignore
================================================
/lib
/public
/samples
node_modules
npm-debug.log
.eslintcache


================================================
FILE: .npm-upgrade.json
================================================
{
  "ignore": {
    "mobx": {
      "versions": ">=6",
      "reason": "v6 drops decorators"
    },
    "mobx-react": {
      "versions": ">=7",
      "reason": "v7 requires MobX v6"
    },
    "webpack-cli": {
      "versions": ">=4",
      "reason": "Current version of Webpack Dev Server doesn't work with v4"
    }
  }
}


================================================
FILE: .nvmrc
================================================
v22.14.0


================================================
FILE: .prettierignore
================================================
test/bundles/**
test/stats/**
test/output/**
samples/**
CHANGELOG.md


================================================
FILE: CHANGELOG.md
================================================
# Changelog

> **Tags:**
> - [Breaking Change]
> - [New Feature]
> - [Improvement]
> - [Bug Fix]
> - [Internal]
> - [Documentation]

_Note: Gaps between patch versions are faulty, broken or test releases._

## UNRELEASED

* **Bug Fix**
  * Fix a race condition in `writeStats` that could lead to incorrect content in `stats.json` ([#711](https://github.com/webpack/webpack-bundle-analyzer/pull/711) by [@colinaaa](https://github.com/colinaaa))

## 5.2.0

* **New Feature**
  * Add support for Zstandard compression ([#693](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/693) by [@bjohansebas](https://github.com/bjohansebas))

* **Internal**
  * Prettier applied to the code base ([#693](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/694) by [@alexander-akait](https://github.com/alexander-akait))
  * Update `sirv` dependency ([#692](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/692) by [@bjohansebas](https://github.com/bjohansebas))
  * Update `ws` dependency ([#691](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/691) by [@bjohansebas](https://github.com/bjohansebas))

## 5.1.1

* **Bug Fix**
  * Fix tooltip styling in dark mode when using CSS Modules ([#688](https://github.com/webpack/webpack-bundle-analyzer/pull/688) by [@theEquinoxDev](https://github.com/theEquinoxDev))
  * Avoid parse failures for bundles with IIFE ([#685](https://github.com/webpack/webpack-bundle-analyzer/pull/685) by [@hai-x](https://github.com/hai-x))

## 5.1.0

* **Bug Fix**
  * Prevent `TypeError` when `assets` or `modules` are undefined in `analyzer.js`
    ([#679](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/679) by [@Srushti-33](https://github.com/Srushti-33))

* **New Feature**
  * Add optional dark/light mode toggle ([#683](https://github.com/webpack/webpack-bundle-analyzer/pull/683) by [@theEquinoxDev](https://github.com/theEquinoxDev))


## 5.0.1

* **Bug Fix**
  * Restore `@babel/plugin-transform-class-properties` to fix HTML report ([#682](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/682) by [@valscion](https://github.com/valscion))

## 5.0.0

* **Breaking Change**
  * Remove explicit support for Node versions below 20.9.0 ([#676](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/676) by [@valscion](https://github.com/valscion))

* **Improvement**
  * Parse bundles as ES modules based on stats JSON information ([#649](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/649) by [@eamodio](https://github.com/eamodio))

* **New Feature**
  * Add support for Brotli compression ([#663](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/663) by [@dcsaszar](https://github.com/dcsaszar))
  * Add support for React Native ([666](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/666) by [@ilteoood](https://github.com/ilteoood))

## 4.10.2

* **Bug Fix**
  * fix `.cjs` files not being handled ([#512](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/512) by [@Rush](https://github.com/Rush))

* **Internal**
  * Remove `is-plain-object` ([#627](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/627) by [@SukkaW](https://github.com/SukkaW))

## 4.10.1

* **Bug Fix**
  * fix `this.handleValueChange.cancel()` is not a function ([#611](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/625) by [@life2015](https://github.com/life2015))

## 4.10.0

* **Improvement**
  * Allows filtering the list of entrypoints ([#624](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/624) by [@chriskrogh](https://github.com/chriskrogh))

* **Internal**
  * Make module much slimmer by replacing all `lodash.*` packages ([#612](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/612)) by [@sukkaw](https://github.com/sukkaw).

## 4.9.1

* **Internal**
  * Replace some lodash usages with JavaScript native API ([#505](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/505)) by [@sukkaw](https://github.com/sukkaw).
  * Make module much slimmer ([#609](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/609)) by [@sukkaw](https://github.com/sukkaw).

* **Bug Fix**
  * fix `analyzerMode: 'server'` on certain machines ([#611](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/611) by [@panbenson](https://github.com/panbenson))

## 4.9.0

* **Improvement**
  * Display modules included in concatenated entry modules on Webpack 5 when "Show content of concatenated modules" is checked ([#602](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/602) by [@pgoldberg](https://github.com/pgoldberg))

## 4.8.0

 * **Improvement**
   * Support reading large (>500MB) stats.json files ([#423](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/423) by [@henry-alakazhang](https://github.com/henry-alakazhang))
   * Improve search UX by graying out non-matches ([#554](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/554) by [@starpit](https://github.com/starpit))

 * **Internal**
   * Add Node.js v16.x to CI and update GitHub actions ([#539](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/539) by [@amareshsm](https://github.com/amareshsm))

## 4.7.0

 * **New Feature**
   * Add the ability to filter to displaying only initial chunks per entrypoint ([#519](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/519) by [@pas-trop-de-zele](https://github.com/pas-trop-de-zele))

## 4.6.1

* **Bug Fix**
  * fix outputting different URL in cli mode ([#524](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/524) by [@southorange1228](https://github.com/southorange1228))

## 4.6.0

* **New Feature**
  * Support outputting different URL in server mode ([#520](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/520) by [@southorange1228](https://github.com/southorange1228))
  * Use deterministic chunk colors (#[501](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/501) by [@CreativeTechGuy](https://github.com/CreativeTechGuy))

## 4.5.0

 * **Improvement**
   * Stop publishing src folder to npm ([#478](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/478) by [@wood1986](https://github.com/wood1986))

* **Internal**
  * Update some dependencies ([#448](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/448))
  * Replace nightmare with Puppeteer ([#469](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/469) by [@valscion](https://github.com/valscion))
  * Replace Mocha with Jest ([#470](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/470) by [@valscion](https://github.com/valscion))

## 4.4.2

 * **Bug Fix**
   * Fix failure with `compiler.outputFileSystem.constructor` being `undefined` ([#447](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/447) by [@kedarv](https://github.com/kedarv) and [@alexander-akait](https://github.com/alexander-akait))
     * **NOTE:** This fix doesn't have added test coverage so the fix might break in future versions unless test coverage is added later.

## 4.4.1

 * **Bug Fix**
   * Fix missing module chunks ([#433](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/433) by [@deanshub](https://github.com/deanshub))

 * **Internal**
   * Fix tests timing out in CI ([#435](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/435) by [@deanshub](https://github.com/deanshub))
   * Fix command in issue template ([#428](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/428) by [@cncolder](https://github.com/cncolder))

## 4.4.0

 * **Improvement**
   * Keep treemap labels visible during zooming animations for better user experience ([#414](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/414) by [@stanislawosinski](https://github.com/stanislawosinski))

 * **Bug Fix**
   * Don't show an empty tooltip when hovering over the FoamTree attribution group or between top-level groups ([#413](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/413) by [@stanislawosinski](https://github.com/stanislawosinski))

 * **Internal**
   * Upgrade FoamTree to version 3.5.0, replace vendor dependency with an NPM package ([#412](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/412) by [@stanislawosinski](https://github.com/stanislawosinski))

## 4.3.0

 * **Improvement**
   * Replace express with builtin node server, reducing number of dependencies ([#398](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/398) by [@TrySound](https://github.com/TrySound))
   * Move `filesize` to dev dependencies, reducing number of dependencies ([#401](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/401) by [@realityking](https://github.com/realityking))

 * **Internal**
   * Replace Travis with GitHub actions ([#402](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/402) by [@valscion](https://github.com/valscion))

## 4.2.0

 * **Improvement**
   * A  number of improvements to reduce the number of dependencies ([#391](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/391), [#396](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/396), [#397](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/397))

 * **Bug Fix**
   * Prevent crashes for bundles generated from webpack array configs. ([#394](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/394) by [@ctavan](https://github.com/ctavan))
   * Fix `non-asset` assets causing analyze failure. ([#385](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/385) by [@ZKHelloworld](https://github.com/ZKHelloworld))

## 4.1.0

 * **Improvement**
   * Significantly speed up generation of `stats.json` file (see `generateStatsFile` option).

## 4.0.0

 * **Breaking change**
   * Dropped support for Node.js 6 and 8. Minimal required version now is v10.13.0

 * **Improvement**
   * Support for Webpack 5

 * **Bug Fix**
   * Prevent crashes when `openAnalyzer` was set to true in environments where there's no program to handle opening. ([#382](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/382) by [@wbobeirne](https://github.com/wbobeirne))

 * **Internal**
   * Updated dependencies
   * Added support for multiple Webpack versions in tests

## 3.9.0

 * **New Feature**
   * Adds option `reportTitle` to set title in HTML reports; default remains date of report generation ([#354](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/354) by [@eoingroat](https://github.com/eoingroat))

 * **Improvement**
    * Added capability to parse bundles that have child assets generated ([#376](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/376) by [@masterkidan](https://github.com/masterkidan) and [#378](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/378) by [@https://github.com/dabbott](https://github.com/https://github.com/dabbott))

## 3.8.0

 * **Improvement**
   * Added support for exports.modules when webpack target = node ([#345](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/345) by [@Spikef](https://github.com/Spikef))

 * **New Feature**
   * Support [WebWorkerChunkTemplatePlugin](https://github.com/webpack/webpack/blob/c9d4ff7b054fc581c96ce0e53432d44f9dd8ca72/lib/webworker/WebWorkerChunkTemplatePlugin.js) ([#353](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/353) by [@Gongreg](https://github.com/Gongreg))

 * **Bug Fix**
   * Support any custom `globalObject` option in Webpack Config. ([#352](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/352) by [@Gongreg](https://github.com/Gongreg))

## 3.7.0

 * **New Feature**
   * Added JSON output option (`analyzerMode: "json"` in plugin, `--mode json` in CLI) ([#341](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/341) by [@Gongreg](https://github.com/Gongreg))

 * **Improvement**
   * Persist "Show content of concatenated modules" option ([#322](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/322) by [@lorenzos](https://github.com/lorenzos))

## 3.6.1

 * **Bug Fix**
   * Add leading zero to hour & minute on `<title />` when needed ([#314](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/314) by [@mhxbe](https://github.com/mhxbe))

 * **Internal**
   * Update some dependencies to get rid of vulnerability warnings ([#339](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/339))

## 3.6.0

 * **Improvement**
   * Support webpack builds where `output.globalObject` is set to `'self'` ([#323](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/323) by [@lemonmade](https://github.com/lemonmade))
   * Improve readability of tooltips ([#320](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/320) by [@lorenzos](https://github.com/lorenzos))

## 3.5.2

 * **Bug Fix**
   * Fix sidebar not showing visibility status of chunks hidden via popup menu (issue [#316](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/316) by [@gaokun](https://github.com/gaokun), fixed in [#317](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/317) by [@bregenspan](https://github.com/bregenspan))

## 3.5.1

 * **Bug Fix**
   * Fix regression in support of webpack dev server and `webpack --watch` (issue [#312](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/312), fixed in [#313](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/313) by [@gaokun](https://github.com/gaokun))

## 3.5.0

 * **Improvements**
   * Improved report title and added favicon ([#310](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/310), [@gaokun](https://github.com/gaokun))

## 3.4.1

 * **Bug Fix**
   * Fix regression of requiring an object to be passed to `new BundleAnalyzerPlugin()` (issue [#300](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/300), fixed in [#302](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/302) by [@jerryOnlyZRJ](https://github.com/jerryOnlyZRJ))

## 3.4.0

 * **Improvements**
   * Add `port: 'auto'` option ([#290](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/290), [@avin-kavish](https://github.com/avin-kavish))

 * **Bug Fix**
   * Avoid mutation of the generated `stats.json` ([#293](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/293), [@wood1986](https://github.com/wood1986))

 * **Internal**
   * Use Autoprefixer ([#266](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/266), [@bregenspan](https://github.com/bregenspan))
   * Detect `AsyncMFS` to support dev-server of Nuxt 2.5 and above ([#275](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/275), [@amoshydra](https://github.com/amoshydra))

## 3.3.2

 * **Bug Fix**
   * Fix regression with escaping internal assets ([#264](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/264), fixes [#263](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/263))

## 3.3.1

 * **Improvements**
   * Use relative links for serving internal assets ([#261](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/261), fixes [#254](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/254))
   * Properly escape embedded JS/JSON ([#262](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/262))

 * **Bug Fix**
   * Fix showing help message on `-h` flag ([#260](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/260), fixes [#239](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/239))

## 3.3.0

 * **New Feature**
   * Show/hide chunks using context menu ([#246](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/246), [@bregenspan](https://github.com/bregenspan))

 * **Internal**
   * Updated dev dependencies

## 3.2.0

 * **Improvements**
   * Add support for .mjs output files ([#252](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/252), [@jlopezxs](https://github.com/jlopezxs))

## 3.1.0

 * **Bug Fix**
   * Properly determine the size of the modules containing special characters ([#223](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/223), [@hulkish](https://github.com/hulkish))
   * Update acorn to v6 ([#248](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/248), [@realityking](https://github.com/realityking))

## 3.0.4

 * **Bug Fix**
   * Make webpack's done hook wait until analyzer writes report or stat file ([#247](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/247), [@mareolan](https://github.com/mareolan))

## 3.0.3

 * **Bug Fix**
   * Disable viewer websocket connection when report is generated in `static` mode ([#215](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/215), [@sebastianhaeni](https://github.com/sebastianhaeni))

## 3.0.2

 * **Improvements**
   * Drop `@babel/runtime` dependency ([#209](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/209), [@realityking](https://github.com/realityking))
   * Properly specify minimal Node.js version in `.babelrc` ([#209](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/209), [@realityking](https://github.com/realityking))

 * **Bug Fix**
   * Move some "dependencies" to "devDependencies" ([#209](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/209), [@realityking](https://github.com/realityking))

## 3.0.1

 * **Bug Fix**
   * Small UI fixes

## 3.0.0

 * **Breaking change**
   * Dropped support for Node.js v4. Minimal required version now is v6.14.4
   * Contents of concatenated modules are now hidden by default because of a number of related issues ([details](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/188)), but can be shown using a new checkbox in the sidebar.

 * **New Feature**
   * Added modules search
   * Added ability to pin and resize the sidebar
   * Added button to toggle the sidebar
   * Added checkbox to show/hide contents of concatenated modules

 * **Improvements**
   * Nested folders that contain only one child folder are now visually merged i.e. `folder1 => folder2 => file1` is now shown like `folder1/folder2 => file1` (thanks to [@varun-singh-1](https://github.com/varun-singh-1) for the idea)

 * **Internal**
   * Dropped support for Node.js v4
   * Using MobX for state management
   * Updated dependencies

## 2.13.1

 * **Improvement**
   * Pretty-format the generated stats.json ([#180](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/180)) [@edmorley](https://github.com/edmorley))

 * **Bug Fix**
   * Properly parse Webpack 4 async chunk with `Array.concat` optimization ([#184](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/184), fixes [#183](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/183))

 * **Internal**
   * Refactor bundle parsing logic ([#184](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/184))

## 2.13.0

 * **Improvement**
   * Loosen bundle parsing logic ([#181](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/181)). Now analyzer will still show parsed sizes even if:
     * It can't parse some bundle chunks. Those chunks just won't have content in the report. Fixes issues like [#160](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/160).
     * Some bundle chunks are missing (it couldn't find files to parse). Those chunks just won't be visible in the report for parsed/gzipped sizes.

## 2.12.0

 * **New Feature**
   * Add option that allows to exclude assets from the report ([#178](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/178))

## 2.11.3

 * **Bug Fix**
   * Filter out modules that weren't found during bundles parsing ([#177](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/177))

## 2.11.2

 * **Bug Fix**
   * Properly process stat files that contain modules inside of `chunks` array ([#175](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/175))
   * Fix parsing of async chunks that push to `this.webpackJsonp` array ([#176](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/176))

## 2.11.1

 * **Improvement**
   * Add support for parsing Webpack 4's chunked modules ([#159](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/159), [@jdelStrother](https://github.com/jdelStrother))

## 2.11.0

 * **Improvement**
   * Show contents of concatenated module (requires Webpack 4) ([#158](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/158), closes [#157](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/157))

## 2.10.1

 * **Improvement**
   * Support webpack 4 without deprecation warnings. @ai in [#156](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/156), fixes [#154](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/154)

## 2.10.0

 * **Bug Fix**
   * Fix "out of memory" crash when dealing with huge stats objects ([#129](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/129), [@ryan953](https://github.com/ryan953))

 * **Internal**
   * Update dependencies ([#146](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/146))
   * Update gulp to v4 and simplify gulpfile ([#146](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/146), [#149](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/149))
   * Simplify ESLint configs ([#148](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/148))

## 2.9.2

 * **Bug Fix**
   * Add a listener for the 'error' event on the WebSocket server client (#140)

 * **Internal**
   * Clean up .travis.yml (#140)
   * Update ws to version 4.0.0 (#140)

## 2.9.1

 * **Bug Fix**
   * Bump `ws` dependency to fix DoS vulnerability (closes [#130](https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/130))

## 2.9.0
 * **New Feature**
   * Show chunk sizes in sidebar (closes #91)

 * **Bug Fix**
   * Properly parse webpack bundles that use arrow functions as module wrappers (#108, @regiontog)

## 2.8.3
 * **Bug Fix**
   * Correctly advertise port when using a random one (#89, @yannickcr)
   * Add proper support for `multi` entries (fixes #92, #87)
   * Support parsing of ESNext features (fixes #94)

## 2.8.2
 * **Improvement**
   * Greatly improved accuracy of gzip sizes

 * **Bug Fix**
   * Generate report file in the bundle output directory when used with Webpack Dev Server (fixes #75)

## 2.8.1
 * **Improvement**
   * Improve warning message when analyzer client couldn't connect to WebSocket server

## 2.8.0
 * **Improvement**
   * Analyzer now supports `webpack --watch` and Webpack Dev Server!
     It will automatically update modules treemap according to changes in the sources via WebSockets!

 * **Internal**
   * Use `babel-preset-env` and two different Babel configs to compile node and browser code
   * Update deps

## 2.7.0
 * **New Feature**
   * Add control to sidebar that allows to choose shown chunks (closes #71 and partially addresses #38)

## 2.6.0
 * **New Feature**
   * Add `defaultSizes` option (closes #52)

## 2.5.0
 * **New Feature**
   * Added `--host` CLI option (@difelice)

## 2.4.1
 * **Improvement**
   * Support `NamedChunksPlugin` (@valscion)

## 2.4.0
 * **Bug Fix**
   * Fix `TypeError: currentFolder.addModule is not a function`

 * **Internal**
   * Update deps

## 2.3.1
 * **Improvement**
   * Improve compatibility with Webpack 2 (@valscion)

## 2.3.0
 * **Improvement**
   * Add `analyzerHost` option (@freaz)

 * **Internal**
   * Update deps

## 2.2.3
 * **Bug Fix**
   * Support bundles that uses `Array.concat` expression in modules definition (@valscion)

## 2.2.1
 * **Bug Fix**
   * Fix regression in analyzing stats files with non-empty `children` property (@gbakernet)

## 2.2.0
 * **Improvement**
   * Improve treemap sharpness on hi-res displays (fixes #33)
   * Add support for stats files with all the information under `children` property (fixes #10)

 * **Internal**
   * Update deps

## 2.1.1
 * **Improvement**
   * Add support for `output.jsonpFunction` webpack config option (fixes #16)

## 2.1.0
 * **New Feature**
   * Add `logLevel` option (closes #19)

## 2.0.1
 * **Bug Fix**
   * Support query in bundle filenames (fixes #22)

 * **Internal**
   * Minimize CSS for report UI

## 2.0.0
 * **New Feature**
   * Analyzer now also shows gzipped sizes (closes #6)
   * Added switcher that allows to choose what sizes will be used to generate tree map.
     Just move your mouse to the left corner of the browser and settings sidebar will appear.

 * **Bug Fix**
   * Properly show sizes for some asset modules (e.g. CSS files loaded with `css-loader`)

 * **Internal**
   * Completely rewritten analyzer UI. Now uses Preact and Webpack 2.

## 1.5.4

 * **Bug Fix**
   * Fix bug when Webpack build is being controlled by some wrapper like `grunt-webpack` (see #21)

## 1.5.3

 * **Bug Fix**
   * Workaround `Express` bug that caused wrong `ejs` version to be used as view engine (fixes #17)

## 1.5.2

 * **Bug Fix**
   * Support array module descriptors that can be generated if `DedupePlugin` is used (fixes #4)

## 1.5.1

 * **Internal**
   * Plug analyzer to Webpack compiler `done` event instead of `emit`. Should fix #15.

## 1.5.0

 * **New Feature**
   * Add `statsOptions` option for `BundleAnalyzerPlugin`

## 1.4.2

 * **Bug Fix**
   * Fix "Unable to find bundle asset" error when bundle name starts with `/` (fixes #3)

## 1.4.1

 * **Bug Fix**
   * Add partial support for `DedupePlugin` (see #4 for more info)

## 1.4.0

 * **New Feature**
   * Add "static report" mode (closes #2)

## 1.3.0

 * **Improvement**
   * Add `startAnalyzer` option for `BundleAnalyzerPlugin` (fixes #1)
 * **Internal**
   * Make module much slimmer - remove/replace bloated dependencies

## 1.2.5

 * Initial public release


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

To contribute to `webpack-bundle-analyzer`, fork the repository and clone it to your machine. [See this GitHub help page for what forking and cloning means](https://help.github.com/articles/fork-a-repo/)

## Setup packages

Next, install this package's dependencies:

```sh
npm i
```

## Develop with your own project

Run the following to build this library and watch its source files for changes:

```sh
npm run start
```

You will now have a fully functioning local build of this library ready to be used. **Leave the `start` script running**, and continue with a new Terminal/shell window.

Link the local package with `yarn` and/or `npm` to use it in your own projects:

```sh
# Needed if your own project uses `yarn` to handle dependencies:
yarn link
# Needed if your own project uses `npm` to handle dependencies:
npm link
```

Now go to your own project directory, and tell `npm` or `yarn` to use the local copy of `webpack-bundle-analyzer` package:

```sh
cd /path/to/my/own/project
# If you're using yarn, run this:
yarn link webpack-bundle-analyzer
# ...and if you're not, and you're using just npm in your own
# project, run this:
npm link webpack-bundle-analyzer
```

Now when you call `require('webpack-bundle-analyzer')` in your own project, you will actually be using the local copy of the `webpack-bundle-analyzer` project.

If your own project's Webpack config has `BundleAnalyzerPlugin` configured with `analyzerMode: 'server'`, the changes you do inside `client` folder within your local copy of `webpack-bundle-analyzer` should now be immediately visible after you refresh your browser page. Hack away!

## Send your changes back to us! :revolving_hearts:

We'd love for you to contribute your changes back to `webpack-bundle-analyzer`! To do that, it would be ace if you could commit your changes to a separate feature branch and open a Pull Request for those changes.

Point your feature branch to use the `main` branch as the base of this PR. The exact commands used depends on how you've setup your local git copy, but the flow could look like this:

```sh
# Inside your own copy of `webpack-bundle-analyzer` package...
git checkout --branch feature-branch-name-here upstream/main
# Then hack away, and commit your changes:
git add -A
git commit -m "Few words about the changes I did"
# Push your local changes back to your fork
git push --set-upstream origin feature-branch-name-here
```

After these steps, you should be able to create a new Pull Request for this repository. If you hit any issues following these instructions, please open an issue and we'll see if we can improve these instructions even further.

## Add tests for your changes :tada:

It would be really great if the changes you did could be tested somehow. Our tests live inside the `test` directory, and they can be run with the following command:

```sh
npm run test-dev
```

Now whenever you change some files, the tests will be rerun immediately. If you don't want that, and want to run tests as a one-off operation, you can use:

```sh
npm test
```


================================================
FILE: LICENSE
================================================
Copyright JS Foundation and other contributors

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![tests][tests]][tests-url]
[![downloads][downloads]][downloads-url]

<div align="center">
  <a href="https://github.com/webpack/webpack">
    <img width="200" height="200"
      src="https://webpack.js.org/assets/icon-square-big.svg">
  </a>
  <h1>Webpack Bundle Analyzer</h1>
  <p>Visualize size of webpack output files with an interactive zoomable treemap.</p>
</div>

<h2 align="center">Install</h2>

```bash
# NPM
npm install --save-dev webpack-bundle-analyzer
# Yarn
yarn add -D webpack-bundle-analyzer
```

<h2 align="center">Usage (as a plugin)</h2>

```js
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  plugins: [new BundleAnalyzerPlugin()],
};
```

It will create an interactive treemap visualization of the contents of all your bundles.

![webpack bundle analyzer zoomable treemap](https://cloud.githubusercontent.com/assets/302213/20628702/93f72404-b338-11e6-92d4-9a365550a701.gif)

This module will help you:

1. Realize what's _really_ inside your bundle
2. Find out what modules make up the most of its size
3. Find modules that got there by mistake
4. Optimize it!

And the best thing is it supports minified bundles! It parses them to get real size of bundled modules.
And it also shows their gzipped, Brotli, or Zstandard sizes!

<h2 align="center">Options (for plugin)</h2>

<!-- eslint-skip -->

```js
new BundleAnalyzerPlugin(options?: object)
```

|            Name            |                                                                                          Type                                                                                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                             |
| :------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|     **`analyzerMode`**     |                                                                     One of: `server`, `static`, `json`, `disabled`                                                                     | Default: `server`. In `server` mode analyzer will start HTTP server to show bundle report. In `static` mode single HTML file with bundle report will be generated. In `json` mode single JSON file with bundle report will be generated. In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`.                                                                  |
|     **`analyzerHost`**     |                                                                                       `{String}`                                                                                       | Default: `127.0.0.1`. Host that will be used in `server` mode to start HTTP server.                                                                                                                                                                                                                                                                                                                                                     |
|     **`analyzerPort`**     |                                                                                  `{Number}` or `auto`                                                                                  | Default: `8888`. Port that will be used in `server` mode to start HTTP server. If `analyzerPort` is `auto`, the operating system will assign an arbitrary unused port                                                                                                                                                                                                                                                                   |
|     **`analyzerUrl`**      | `{Function}` called with `{ listenHost: string, listenHost: string, boundAddress: server.address}`. [server.address comes from Node.js](https://nodejs.org/api/net.html#serveraddress) | Default: `http://${listenHost}:${boundAddress.port}`. The URL printed to console with server mode.                                                                                                                                                                                                                                                                                                                                      |
|    **`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`, `brotli`                                                                       | Default: `parsed`. Module sizes to show in report by default. [Size definitions](#size-definitions) section describes what these values mean.                                                                                                                                                                                                                                                                                           |
| **`compressionAlgorithm`** |                                                                            One of: `gzip`, `brotli`, `zstd`                                                                            | Default: `gzip`. Compression type used to calculate the compressed module sizes.                                                                                                                                                                                                                                                                                                                                                        |
|     **`openAnalyzer`**     |                                                                                      `{Boolean}`                                                                                       | Default: `true`. Automatically open report in default browser.                                                                                                                                                                                                                                                                                                                                                                          |
|  **`generateStatsFile`**   |                                                                                      `{Boolean}`                                                                                       | Default: `false`. If `true`, webpack stats JSON file will be generated in bundle output directory                                                                                                                                                                                                                                                                                                                                       |
|    **`statsFilename`**     |                                                                                       `{String}`                                                                                       | Default: `stats.json`. Name of webpack stats JSON file that will be generated if `generateStatsFile` is `true`. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config).                                                                                                                                                                                             |
|     **`statsOptions`**     |                                                                                  `null` or `{Object}`                                                                                  | Default: `null`. Options for `stats.toJson()` method. For example you can exclude sources of your modules from stats file with `source: false` option. [See more options here](https://webpack.js.org/configuration/stats/).                                                                                                                                                                                                            |
|    **`excludeAssets`**     |                                                  `{null\|pattern\|pattern[]}` where `pattern` equals to `{String\|RegExp\|function}`                                                   | Default: `null`. Patterns that will be used to match against asset names to exclude them from the report. If pattern is a string it will be converted to RegExp via `new RegExp(str)`. If pattern is a function it should have the following signature `(assetName: string) => boolean` and should return `true` to _exclude_ matching asset. If multiple patterns are provided asset should match at least one of them to be excluded. |
|       **`logLevel`**       |                                                                       One of: `info`, `warn`, `error`, `silent`                                                                        | Default: `info`. Used to control how much details the plugin outputs.                                                                                                                                                                                                                                                                                                                                                                   |

<h2 align="center">Usage (as a CLI utility)</h2>

You can analyze an existing bundle if you have a webpack stats JSON file.

You can generate it using `BundleAnalyzerPlugin` with `generateStatsFile` option set to `true` or with this simple
command:

```bash
webpack --profile --json > stats.json
```

If you're on Windows and using PowerShell, you can generate the stats file with this command to [avoid BOM issues](https://github.com/webpack/webpack-bundle-analyzer/issues/47):

```
webpack --profile --json | Out-file 'stats.json' -Encoding OEM
```

Then you can run the CLI tool.

```
webpack-bundle-analyzer bundle/output/path/stats.json
```

<h2 align="center">Options (for CLI)</h2>

```bash
webpack-bundle-analyzer <bundleStatsFile> [bundleDir] [options]
```

Arguments are documented below:

### `bundleStatsFile`

Path to webpack stats JSON file

### `bundleDir`

Directory containing all generated bundles.

### `options`

```
  -V, --version                   output the version number
  -m, --mode <mode>               Analyzer mode. Should be `server`, `static` or `json`.
                                  In `server` mode analyzer will start HTTP server to show bundle report.
                                  In `static` mode single HTML file with bundle report will be generated.
                                  In `json` mode single JSON file with bundle report will be generated. (default: server)
  -h, --host <host>               Host that will be used in `server` mode to start HTTP server. (default: 127.0.0.1)
  -p, --port <n>                  Port that will be used in `server` mode to start HTTP server. Should be a number or `auto` (default: 8888)
  -r, --report <file>             Path to bundle report file that will be generated in `static` mode. (default: report.html)
  -t, --title <title>             String to use in title element of html report. (default: pretty printed current date)
  -s, --default-sizes <type>      Module sizes to show in treemap by default.
                                  Possible values: stat, parsed, gzip, brotli, zstd (default: parsed)
  --compression-algorithm <type>  Compression algorithm that will be used to calculate the compressed module sizes.
                                  Possible values: gzip, brotli, zstd (default: gzip)
  -O, --no-open                   Don't open report in default browser automatically.
  -e, --exclude <regexp>          Assets that should be excluded from the report.
                                  Can be specified multiple times.
  -l, --log-level <level>         Log level.
                                  Possible values: debug, info, warn, error, silent (default: info)
  -h, --help                      output usage information
```

<h2 align="center" id="size-definitions">Size definitions</h2>

webpack-bundle-analyzer reports three values for sizes. `defaultSizes` can be used to control which of these is shown by default. The different reported sizes are:

### `stat`

This is the "input" size of your files, before any transformations like
minification.

It is called "stat size" because it's obtained from Webpack's
[stats object](https://webpack.js.org/configuration/stats/).

### `parsed`

This is the "output" size of your files. If you're using a Webpack plugin such
as Uglify, then this value will reflect the minified size of your code.

### `gzip`

This is the size of running the parsed bundles/modules through gzip compression.

### `brotli`

This is the size of running the parsed bundles/modules through Brotli compression.

### `zstd`

This is the size of running the parsed bundles/modules through Zstandard compression. (Node.js 22.15.0+ is required for this feature)

<h2 align="center">Selecting Which Chunks to Display</h2>

When opened, the report displays all of the Webpack chunks for your project. It's possible to filter to a more specific list of chunks by using the sidebar or the chunk context menu.

### Sidebar

The Sidebar Menu can be opened by clicking the `>` button at the top left of the report. You can select or deselect chunks to display under the "Show chunks" heading there.

### Chunk Context Menu

The Chunk Context Menu can be opened by right-clicking or `Ctrl`-clicking on a specific chunk in the report. It provides the following options:

- **Hide chunk:** Hides the selected chunk
- **Hide all other chunks:** Hides all chunks besides the selected one
- **Show all chunks:** Un-hides any hidden chunks, returning the report to its initial, unfiltered view

<h2 align="center">Troubleshooting</h2>

### I don't see `gzip` or `parsed` sizes, it only shows `stat` size

It happens when `webpack-bundle-analyzer` analyzes files that don't actually exist in your file system, for example when you work with `webpack-dev-server` that keeps all the files in RAM. If you use `webpack-bundle-analyzer` as a plugin you won't get any errors, however if you run it via CLI you get the error message in terminal:

```
Error parsing bundle asset "your_bundle_name.bundle.js": no such file
No bundles were parsed. Analyzer will show only original module sizes from stats file.
```

To get more information about it you can read [issue #147](https://github.com/webpack/webpack-bundle-analyzer/issues/147).

<h2 align="center">Other tools</h2>

- [Statoscope](https://github.com/smelukov/statoscope/blob/master/packages/ui-webpack/README.md) - Webpack bundle analyzing tool to find out why a certain module was bundled (and more features, including interactive treemap)

<h2 align="center">Maintainers</h2>

<table>
  <tbody>
    <tr>
      <td align="center">
        <img width="150" height="150"
        src="https://avatars3.githubusercontent.com/u/302213?v=4&s=150">
        </br>
        <a href="https://github.com/th0r">Yuriy Grunin</a>
      </td>
      <td align="center">
        <img width="150" height="150"
        src="https://avatars3.githubusercontent.com/u/482561?v=4&s=150">
        </br>
        <a href="https://github.com/valscion">Vesa Laakso</a>
      </td>
    </tr>
  <tbody>
</table>

[npm]: https://img.shields.io/npm/v/webpack-bundle-analyzer.svg
[npm-url]: https://npmjs.com/package/webpack-bundle-analyzer
[node]: https://img.shields.io/node/v/webpack-bundle-analyzer.svg
[node-url]: https://nodejs.org
[tests]: https://github.com/webpack/webpack-bundle-analyzer/actions/workflows/main.yml/badge.svg
[tests-url]: https://github.com/webpack/webpack-bundle-analyzer/actions/workflows/main.yml
[downloads]: https://img.shields.io/npm/dt/webpack-bundle-analyzer.svg
[downloads-url]: https://npmjs.com/package/webpack-bundle-analyzer

<h2 align="center">Contributing</h2>

Check out [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on contributing :tada:


================================================
FILE: bin/install-test-webpack-versions.sh
================================================
#!/usr/bin/env bash

for dir in "$(dirname "$0")"/../test/webpack-versions/*; do (cd "$dir" && npm i); done


================================================
FILE: client/.eslintrc.json
================================================
{
  "extends": ["th0r-react", "../.eslintrc.json"],
  "settings": {
    "react": {
      "version": "16.2"
    }
  },
  "parserOptions": {
    "ecmaFeatures": {
      "legacyDecorators": true
    }
  },
  "rules": {
    "react/jsx-key": "off",
    "react/jsx-no-bind": "off",
    "react/react-in-jsx-scope": "off"
  }
}


================================================
FILE: client/components/Button.css
================================================
.button {
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 4px;
  cursor: pointer;
  display: inline-block;
  font: var(--main-font);
  outline: none;
  padding: 5px 7px;
  transition:
    background 0.3s ease,
    border-color 0.3s ease,
    color 0.3s ease;
  white-space: nowrap;
  color: var(--text-primary);
}

.button:focus,
.button:hover {
  background: var(--hover-bg);
}

.button.active {
  background: #ffa500;
  color: #000;
}

.button[disabled] {
  cursor: default;
}


================================================
FILE: client/components/Button.jsx
================================================
import cls from "classnames";
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";

import * as styles from "./Button.css";

export default class Button extends PureComponent {
  static propTypes = {
    className: PropTypes.string,

    active: PropTypes.bool,
    toggle: PropTypes.bool,
    disabled: PropTypes.bool,

    onClick: PropTypes.func.isRequired,

    children: PropTypes.node,
  };

  render({ active, className, children, ...props }) {
    const classes = cls(className, {
      [styles.button]: true,
      [styles.active]: active,
    });

    return (
      <button
        {...props}
        ref={this.saveRef}
        type="button"
        className={classes}
        disabled={this.disabled}
        onClick={this.handleClick}
      >
        {children}
      </button>
    );
  }

  get disabled() {
    const { disabled, active, toggle } = this.props;
    return disabled || (active && !toggle);
  }

  handleClick = (event) => {
    if (this.elem) {
      this.elem.blur();
    }

    this.props.onClick(event);
  };

  saveRef = (elem) => (this.elem = elem);
}


================================================
FILE: client/components/Checkbox.css
================================================
.label {
  cursor: pointer;
  display: inline-block;
}

.checkbox {
  cursor: pointer;
}

.itemText {
  margin-left: 3px;
  position: relative;
  top: -2px;
  vertical-align: middle;
}


================================================
FILE: client/components/Checkbox.jsx
================================================
import cls from "classnames";
import { Component } from "preact";
import PropTypes from "prop-types";

import * as styles from "./Checkbox.css";

export default class Checkbox extends Component {
  static propTypes = {
    className: PropTypes.string,

    checked: PropTypes.bool,

    onChange: PropTypes.func.isRequired,

    children: PropTypes.node,
  };

  render() {
    const { checked, className, children } = this.props;

    return (
      <label className={cls(styles.label, className)}>
        <input
          className={styles.checkbox}
          type="checkbox"
          checked={checked}
          onChange={this.handleChange}
        />
        {children && <span className={styles.itemText}>{children}</span>}
      </label>
    );
  }

  handleChange = () => {
    this.props.onChange(!this.props.checked);
  };
}


================================================
FILE: client/components/CheckboxList.css
================================================
.container {
  font: var(--main-font);
  white-space: nowrap;
}

.label {
  font-size: 11px;
  font-weight: bold;
  margin-bottom: 7px;
}

.item + .item {
  margin-top: 1px;
}


================================================
FILE: client/components/CheckboxList.jsx
================================================
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import * as styles from "./CheckboxList.css";
import CheckboxListItem from "./CheckboxListItem.jsx";
import { ViewerDataType } from "./types.js";

const ALL_ITEM = Symbol("ALL_ITEM");

export default class CheckboxList extends PureComponent {
  static propTypes = {
    label: PropTypes.string.isRequired,
    renderLabel: PropTypes.func.isRequired,
    items: PropTypes.oneOfType([ViewerDataType, PropTypes.symbol]),
    checkedItems: PropTypes.oneOfType([ViewerDataType, PropTypes.symbol]),

    onChange: PropTypes.func.isRequired,
  };

  static ALL_ITEM = ALL_ITEM;

  constructor(props) {
    super(props);
    this.state = {
      checkedItems: props.checkedItems || props.items,
    };
  }

  componentWillReceiveProps(newProps) {
    if (newProps.items !== this.props.items) {
      if (this.isAllChecked()) {
        // Preserving `all checked` state
        this.setState({ checkedItems: newProps.items });
        this.informAboutChange(newProps.items);
      } else if (this.state.checkedItems.length) {
        // Checking only items that are in the new `items` array
        const checkedItems = newProps.items.filter((item) =>
          this.state.checkedItems.find(
            (checkedItem) => checkedItem.label === item.label,
          ),
        );

        this.setState({ checkedItems });
        this.informAboutChange(checkedItems);
      }
    } else if (newProps.checkedItems !== this.props.checkedItems) {
      this.setState({ checkedItems: newProps.checkedItems });
    }
  }

  render() {
    const { label, items, renderLabel } = this.props;

    return (
      <div className={styles.container}>
        <div className={styles.label}>{label}:</div>
        <div>
          <CheckboxListItem
            item={ALL_ITEM}
            checked={this.isAllChecked()}
            onChange={this.handleToggleAllCheck}
          >
            {renderLabel}
          </CheckboxListItem>
          {items.map((item) => (
            <CheckboxListItem
              key={item.label}
              item={item}
              checked={this.isItemChecked(item)}
              onChange={this.handleItemCheck}
            >
              {renderLabel}
            </CheckboxListItem>
          ))}
        </div>
      </div>
    );
  }

  handleToggleAllCheck = () => {
    const checkedItems = this.isAllChecked() ? [] : this.props.items;
    this.setState({ checkedItems });
    this.informAboutChange(checkedItems);
  };

  handleItemCheck = (item) => {
    let checkedItems;

    if (this.isItemChecked(item)) {
      checkedItems = this.state.checkedItems.filter(
        (checkedItem) => checkedItem !== item,
      );
    } else {
      checkedItems = [...this.state.checkedItems, item];
    }

    this.setState({ checkedItems });
    this.informAboutChange(checkedItems);
  };

  isItemChecked(item) {
    return this.state.checkedItems.includes(item);
  }

  isAllChecked() {
    return this.props.items.length === this.state.checkedItems.length;
  }

  informAboutChange(checkedItems) {
    setTimeout(() => this.props.onChange(checkedItems));
  }
}


================================================
FILE: client/components/CheckboxListItem.jsx
================================================
import { Component } from "preact";
import PropTypes from "prop-types";

import Checkbox from "./Checkbox.jsx";
import * as styles from "./CheckboxList.css";
import CheckboxList from "./CheckboxList.jsx";
import { ViewerDataItemType } from "./types.js";

export default class CheckboxListItem extends Component {
  static propTypes = {
    item: PropTypes.oneOfType([ViewerDataItemType, PropTypes.symbol])
      .isRequired,

    onChange: PropTypes.func.isRequired,

    children: PropTypes.func,
  };

  render() {
    return (
      <div className={styles.item}>
        <Checkbox {...this.props} onChange={this.handleChange}>
          {this.renderLabel()}
        </Checkbox>
      </div>
    );
  }

  renderLabel() {
    const { children, item } = this.props;
    if (children) {
      return children(item);
    }

    return item === CheckboxList.ALL_ITEM ? "All" : item.label;
  }

  handleChange = () => {
    this.props.onChange(this.props.item);
  };
}


================================================
FILE: client/components/ContextMenu.css
================================================
.container {
  font: var(--main-font);
  position: absolute;
  padding: 0;
  border-radius: 4px;
  background: #fff;
  border: 1px solid #aaa;
  list-style: none;
  opacity: 1;
  white-space: nowrap;
  visibility: visible;
  transition:
    opacity 0.2s ease,
    visibility 0.2s ease;
}

.hidden {
  opacity: 0;
  visibility: hidden;
}


================================================
FILE: client/components/ContextMenu.jsx
================================================
import cls from "classnames";
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import { store } from "../store.js";
import { elementIsOutside } from "../utils.js";
import * as styles from "./ContextMenu.css";
import ContextMenuItem from "./ContextMenuItem.jsx";
import { ViewerDataItemType } from "./types.js";

export default class ContextMenu extends PureComponent {
  static propTypes = {
    visible: PropTypes.bool,

    chunk: ViewerDataItemType,
    coords: PropTypes.shape({
      x: PropTypes.number,
      y: PropTypes.number,
    }).isRequired,

    onHide: PropTypes.func,
  };

  componentDidMount() {
    this.boundingRect = this.node.getBoundingClientRect();
  }

  componentDidUpdate(prevProps) {
    if (this.props.visible && !prevProps.visible) {
      document.addEventListener(
        "mousedown",
        this.handleDocumentMousedown,
        true,
      );
    } else if (prevProps.visible && !this.props.visible) {
      document.removeEventListener(
        "mousedown",
        this.handleDocumentMousedown,
        true,
      );
    }
  }

  render() {
    const { visible } = this.props;
    const containerClassName = cls({
      [styles.container]: true,
      [styles.hidden]: !visible,
    });
    const multipleChunksSelected = store.selectedChunks.length > 1;
    return (
      <ul
        ref={this.saveNode}
        className={containerClassName}
        style={this.getStyle()}
      >
        <ContextMenuItem
          disabled={!multipleChunksSelected}
          onClick={this.handleClickHideChunk}
        >
          Hide chunk
        </ContextMenuItem>
        <ContextMenuItem
          disabled={!multipleChunksSelected}
          onClick={this.handleClickFilterToChunk}
        >
          Hide all other chunks
        </ContextMenuItem>
        <hr />
        <ContextMenuItem
          disabled={store.allChunksSelected}
          onClick={this.handleClickShowAllChunks}
        >
          Show all chunks
        </ContextMenuItem>
      </ul>
    );
  }

  handleClickHideChunk = () => {
    const { chunk: selectedChunk } = this.props;
    if (selectedChunk && selectedChunk.label) {
      const filteredChunks = store.selectedChunks.filter(
        (chunk) => chunk.label !== selectedChunk.label,
      );
      store.setSelectedChunks(filteredChunks);
    }
    this.hide();
  };

  handleClickFilterToChunk = () => {
    const { chunk: selectedChunk } = this.props;

    if (selectedChunk && selectedChunk.label) {
      const filteredChunks = store.allChunks.filter(
        (chunk) => chunk.label === selectedChunk.label,
      );
      store.setSelectedChunks(filteredChunks);
    }
    this.hide();
  };

  handleClickShowAllChunks = () => {
    store.setSelectedChunks(store.allChunks);
    this.hide();
  };

  /**
   * Handle document-wide `mousedown` events to detect clicks
   * outside the context menu.
   * @param {MouseEvent} event DOM mouse event object
   * @returns {void}
   */
  handleDocumentMousedown = (event) => {
    const isSecondaryClick = event.ctrlKey || event.button === 2;
    if (!isSecondaryClick && elementIsOutside(event.target, this.node)) {
      event.preventDefault();
      event.stopPropagation();
      this.hide();
    }
  };

  hide() {
    if (this.props.onHide) {
      this.props.onHide();
    }
  }

  saveNode = (node) => (this.node = node);

  getStyle() {
    const { boundingRect } = this;

    // Upon the first render of this component, we don't yet know
    // its dimensions, so can't position it yet
    if (!boundingRect) return;

    const { coords } = this.props;

    const pos = {
      left: coords.x,
      top: coords.y,
    };

    if (pos.left + boundingRect.width > window.innerWidth) {
      // Shifting horizontally
      pos.left = window.innerWidth - boundingRect.width;
    }

    if (pos.top + boundingRect.height > window.innerHeight) {
      // Flipping vertically
      pos.top = coords.y - boundingRect.height;
    }
    return pos;
  }
}


================================================
FILE: client/components/ContextMenuItem.css
================================================
.item {
  cursor: pointer;
  margin: 0;
  padding: 8px 14px;
  user-select: none;
}

.item:hover {
  background: #ffefd7;
}

.disabled {
  cursor: default;
  color: gray;
}

.item.disabled:hover {
  background: transparent;
}


================================================
FILE: client/components/ContextMenuItem.jsx
================================================
import cls from "classnames";
import PropTypes from "prop-types";

import * as styles from "./ContextMenuItem.css";

/**
 * @returns {boolean} nothing
 */
function noop() {
  return false;
}

/**
 * @typedef {object} ContextMenuItemProps
 * @property {React.ReactNode} children children
 * @property {boolean=} disabled - true when disabled, otherwise false
 * @property {React.MouseEventHandler<HTMLLIElement>=} onClick on click handler
 */

/**
 * @param {ContextMenuItemProps} props props
 * @returns {JSX.Element} context menu item
 */
export default function ContextMenuItem({ children, disabled, onClick }) {
  const className = cls({
    [styles.item]: true,
    [styles.disabled]: disabled,
  });
  const handler = disabled ? noop : onClick;
  return (
    <li className={className} onClick={handler}>
      {children}
    </li>
  );
}

ContextMenuItem.propTypes = {
  disabled: PropTypes.bool,

  children: PropTypes.node.isRequired,

  onClick: PropTypes.func,
};


================================================
FILE: client/components/Dropdown.css
================================================
.container {
  font: var(--main-font);
  white-space: nowrap;
}

.label {
  font-size: 11px;
  font-weight: bold;
  margin-bottom: 7px;
}

.input {
  border: 1px solid var(--border-color);
  border-radius: 4px;
  display: block;
  width: 100%;
  color: var(--text-secondary);
  height: 27px;
  background: var(--bg-primary);
  transition:
    background-color 0.3s ease,
    border-color 0.3s ease,
    color 0.3s ease;
}

.option {
  padding: 4px 0;
  cursor: pointer;
}


================================================
FILE: client/components/Dropdown.jsx
================================================
import { createRef } from "preact";
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";

import * as styles from "./Dropdown.css";

export default class Dropdown extends PureComponent {
  static propTypes = {
    label: PropTypes.string.isRequired,
    options: PropTypes.arrayOf(PropTypes.string).isRequired,
    onSelectionChange: PropTypes.func.isRequired,
  };

  input = createRef();

  state = {
    query: "",
    showOptions: false,
  };

  componentDidMount() {
    document.addEventListener("click", this.handleClickOutside, true);
  }

  componentWillUnmount() {
    document.removeEventListener("click", this.handleClickOutside, true);
  }

  render() {
    const { label, options } = this.props;

    const filteredOptions = this.state.query
      ? options.filter((option) =>
          option.toLowerCase().includes(this.state.query.toLowerCase()),
        )
      : options;

    return (
      <div className={styles.container}>
        <div className={styles.label}>{label}:</div>
        <div>
          <input
            ref={this.input}
            className={styles.input}
            type="text"
            value={this.state.query}
            onInput={this.handleInput}
            onFocus={this.handleFocus}
          />
          {this.state.showOptions ? (
            <div>
              {filteredOptions.map((option) => (
                <div
                  key={option}
                  className={styles.option}
                  onClick={this.getOptionClickHandler(option)}
                >
                  {option}
                </div>
              ))}
            </div>
          ) : null}
        </div>
      </div>
    );
  }

  handleClickOutside = (event) => {
    const el = this.input.current;
    if (el && event && !el.contains(event.target)) {
      this.setState({ showOptions: false });
      // If the query is not in the options, reset the selection
      if (this.state.query && !this.props.options.includes(this.state.query)) {
        this.setState({ query: "" });
        this.props.onSelectionChange(undefined);
      }
    }
  };

  handleInput = (event) => {
    const { value } = event.target;
    this.setState({ query: value });
    if (!value) {
      this.props.onSelectionChange(undefined);
    }
  };

  handleFocus = () => {
    // move the cursor to the end of the input
    this.input.current.value = this.state.query;
    this.setState({ showOptions: true });
  };

  getOptionClickHandler = (option) => () => {
    this.props.onSelectionChange(option);
    this.setState({ query: option, showOptions: false });
  };
}


================================================
FILE: client/components/Icon.css
================================================
.icon {
  background: no-repeat center/contain;
  display: inline-block;
  filter: invert(0);
}

[data-theme="dark"] .icon {
  filter: invert(1);
}


================================================
FILE: client/components/Icon.jsx
================================================
import cls from "classnames";
import PropTypes from "prop-types";
import iconArrowRight from "../assets/icon-arrow-right.svg";
import iconMoon from "../assets/icon-moon.svg";
import iconPin from "../assets/icon-pin.svg";
import iconSun from "../assets/icon-sun.svg";
import PureComponent from "../lib/PureComponent.jsx";

import * as styles from "./Icon.css";

const ICONS = {
  "arrow-right": {
    src: iconArrowRight,
    size: [7, 13],
  },
  pin: {
    src: iconPin,
    size: [12, 18],
  },
  moon: {
    src: iconMoon,
    size: [24, 24],
  },
  sun: {
    src: iconSun,
    size: [24, 24],
  },
};

export default class Icon extends PureComponent {
  static propTypes = {
    className: PropTypes.string,

    name: PropTypes.string.isRequired,
    size: PropTypes.number,
    rotate: PropTypes.number,
  };

  render({ className }) {
    return <i className={cls(styles.icon, className)} style={this.style} />;
  }

  get style() {
    const { name, size, rotate } = this.props;
    const icon = ICONS[name];

    if (!icon) throw new TypeError(`Can't find "${name}" icon.`);

    let [width, height] = icon.size;

    if (size) {
      const ratio = size / Math.max(width, height);
      width = Math.min(Math.ceil(width * ratio), size);
      height = Math.min(Math.ceil(height * ratio), size);
    }

    return {
      backgroundImage: `url(${icon.src})`,
      width: `${width}px`,
      height: `${height}px`,
      transform: rotate ? `rotate(${rotate}deg)` : "",
    };
  }
}


================================================
FILE: client/components/ModuleItem.css
================================================
.container {
  background: no-repeat left center;
  cursor: pointer;
  margin-bottom: 4px;
  padding-left: 18px;
  position: relative;
  white-space: nowrap;
}

.container.module {
  background-image: url("../assets/icon-module.svg");
  background-position-x: 1px;
}

.container.folder {
  background-image: url("../assets/icon-folder.svg");
}

.container.chunk {
  background-image: url("../assets/icon-chunk.svg");
}

.container.invisible:hover::before {
  background: url("../assets/icon-invisible.svg") no-repeat left center;
  content: "";
  height: 100%;
  left: 0;
  top: 1px;
  position: absolute;
  width: 13px;
}


================================================
FILE: client/components/ModuleItem.jsx
================================================
import cls from "classnames";
import escapeRegExp from "escape-string-regexp";
import { filesize } from "filesize";
import { escape } from "html-escaper";
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import * as styles from "./ModuleItem.css";
import { ModuleType, SizeType } from "./types.js";

export default class ModuleItem extends PureComponent {
  static propTypes = {
    module: ModuleType.isRequired,
    showSize: SizeType.isRequired,
    highlightedText: PropTypes.instanceOf(RegExp),

    isVisible: PropTypes.func.isRequired,

    onClick: PropTypes.func.isRequired,
  };

  state = {
    visible: true,
  };

  render({ module, showSize }) {
    const invisible = !this.state.visible;
    const classes = cls(styles.container, styles[this.itemType], {
      [styles.invisible]: invisible,
    });

    return (
      <div
        className={classes}
        title={invisible ? this.invisibleHint : null}
        onClick={this.handleClick}
        onMouseEnter={this.handleMouseEnter}
        onMouseLeave={this.handleMouseLeave}
      >
        <span dangerouslySetInnerHTML={{ __html: this.titleHtml }} />
        {showSize && (
          <>
            {" ("}
            <strong>{filesize(module[showSize])}</strong>
            {")"}
          </>
        )}
      </div>
    );
  }

  get itemType() {
    const { module } = this.props;
    if (!module.path) return "chunk";
    return module.groups ? "folder" : "module";
  }

  get titleHtml() {
    let html;
    const { module } = this.props;
    const title = module.path || module.label;
    const term = this.props.highlightedText;

    if (term) {
      const regexp =
        term instanceof RegExp
          ? new RegExp(term.source, "igu")
          : new RegExp(`(?:${escapeRegExp(term)})+`, "iu");
      let match;
      let lastMatch;

      do {
        lastMatch = match;
        match = regexp.exec(title);
      } while (match);

      if (lastMatch) {
        html = `${escape(
          title.slice(0, lastMatch.index),
        )}<strong>${escape(lastMatch[0])}</strong>${escape(
          title.slice(lastMatch.index + lastMatch[0].length),
        )}`;
      }
    }

    if (!html) {
      html = escape(title);
    }

    return html;
  }

  get invisibleHint() {
    const itemType =
      this.itemType.charAt(0).toUpperCase() + this.itemType.slice(1);
    return `${itemType} is not rendered in the treemap because it's too small.`;
  }

  get isVisible() {
    const { isVisible } = this.props;
    return isVisible ? isVisible(this.props.module) : true;
  }

  handleClick = () => this.props.onClick(this.props.module);

  handleMouseEnter = () => {
    if (this.props.isVisible) {
      this.setState({ visible: this.isVisible });
    }
  };
}


================================================
FILE: client/components/ModulesList.css
================================================
.container {
  font: var(--main-font);
}


================================================
FILE: client/components/ModulesList.jsx
================================================
import cls from "classnames";
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import ModuleItem from "./ModuleItem.jsx";
import * as styles from "./ModulesList.css";
import { ModuleType, SizeType } from "./types.js";

export default class ModulesList extends PureComponent {
  static propTypes = {
    className: PropTypes.string,

    modules: PropTypes.arrayOf(ModuleType).isRequired,
    showSize: SizeType.isRequired,
    highlightedText: PropTypes.instanceOf(RegExp),

    isModuleVisible: PropTypes.func.isRequired,
    onModuleClick: PropTypes.func.isRequired,
  };

  render({ modules, showSize, highlightedText, isModuleVisible, className }) {
    return (
      <div className={cls(styles.container, className)}>
        {modules.map((module) => (
          <ModuleItem
            key={module.cid}
            module={module}
            showSize={showSize}
            highlightedText={highlightedText}
            isVisible={isModuleVisible}
            onClick={this.handleModuleClick}
          />
        ))}
      </div>
    );
  }

  handleModuleClick = (module) => this.props.onModuleClick(module);
}


================================================
FILE: client/components/ModulesTreemap.css
================================================
.container {
  align-items: stretch;
  display: flex;
  height: 100%;
  position: relative;
  width: 100%;
}

.map {
  flex: 1;
}

.sidebarGroup {
  font: var(--main-font);
  margin-bottom: 20px;
}

.showOption {
  margin-top: 5px;
}

.activeSize {
  font-weight: bold;
}

.foundModulesInfo {
  display: flex;
  font: var(--main-font);
  margin: 8px 0 0;
}

.foundModulesInfoItem + .foundModulesInfoItem {
  margin-left: 15px;
}

.foundModulesContainer {
  margin-top: 15px;
  max-height: 600px;
  overflow: auto;
}

.foundModulesChunk + .foundModulesChunk {
  margin-top: 15px;
}

.foundModulesChunkName {
  cursor: pointer;
  font: var(--main-font);
  font-weight: bold;
  margin-bottom: 7px;
}

.foundModulesList {
  margin-left: 7px;
}


================================================
FILE: client/components/ModulesTreemap.jsx
================================================
import { filesize } from "filesize";
import { computed, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Component } from "preact";

import localStorage from "../localStorage.js";
import { store } from "../store.js";
import { isChunkParsed } from "../utils.js";
import Checkbox from "./Checkbox.jsx";
import CheckboxList from "./CheckboxList.jsx";
import ContextMenu from "./ContextMenu.jsx";
import Dropdown from "./Dropdown.jsx";
import ModulesList from "./ModulesList.jsx";
import * as styles from "./ModulesTreemap.css";
import Search from "./Search.jsx";
import Sidebar from "./Sidebar.jsx";
import Switcher from "./Switcher.jsx";
import Tooltip from "./Tooltip.jsx";
import Treemap from "./Treemap.jsx";

/** @typedef {"statSize" | "parsedSize" | "gzipSize" | "brotliSize" | "zstdSize"} PropSize */

/**
 * @returns {{ label: string, prop: PropSize }[]} sizes
 */
function getSizeSwitchItems() {
  const items = [
    { label: "Stat", prop: "statSize" },
    { label: "Parsed", prop: "parsedSize" },
  ];

  if (globalThis.compressionAlgorithm === "gzip") {
    items.push({ label: "Gzipped", prop: "gzipSize" });
  }

  if (globalThis.compressionAlgorithm === "brotli") {
    items.push({ label: "Brotli", prop: "brotliSize" });
  }

  if (globalThis.compressionAlgorithm === "zstd") {
    items.push({ label: "Zstandard", prop: "zstdSize" });
  }

  return items;
}

class ModulesTreemap extends Component {
  mouseCoords = {
    x: 0,
    y: 0,
  };

  state = {
    selectedChunk: null,
    selectedMouseCoords: { x: 0, y: 0 },
    sidebarPinned: false,
    showChunkContextMenu: false,
    showTooltip: false,
    tooltipContent: null,
  };

  constructor() {
    super();

    makeObservable(this, {
      sizeSwitchItems: computed,
      activeSizeItem: computed,
      chunkItems: computed,
      highlightedModules: computed,
      foundModulesInfo: computed,
    });
  }

  componentDidMount() {
    document.addEventListener("mousemove", this.handleMouseMove, true);
  }

  componentWillUnmount() {
    document.removeEventListener("mousemove", this.handleMouseMove, true);
  }

  render() {
    const {
      selectedChunk,
      selectedMouseCoords,
      sidebarPinned,
      showChunkContextMenu,
      showTooltip,
      tooltipContent,
    } = this.state;

    return (
      <div className={styles.container}>
        <Sidebar
          pinned={sidebarPinned}
          onToggle={this.handleSidebarToggle}
          onPinStateChange={this.handleSidebarPinStateChange}
          onResize={this.handleSidebarResize}
        >
          <div className={styles.sidebarGroup}>
            <Switcher
              label="Treemap sizes"
              items={this.sizeSwitchItems}
              activeItem={this.activeSizeItem}
              onSwitch={this.handleSizeSwitch}
            />
            {store.hasConcatenatedModules && (
              <div className={styles.showOption}>
                <Checkbox
                  checked={store.showConcatenatedModulesContent}
                  onChange={this.handleConcatenatedModulesContentToggle}
                >
                  {`Show content of concatenated modules${store.activeSize === "statSize" ? "" : " (inaccurate)"}`}
                </Checkbox>
              </div>
            )}
          </div>
          <div className={styles.sidebarGroup}>
            <Dropdown
              label="Filter to initial chunks"
              options={store.entrypoints}
              onSelectionChange={this.handleSelectionChange}
            />
          </div>
          <div className={styles.sidebarGroup}>
            <Search
              label="Search modules"
              query={store.searchQuery}
              autofocus
              onQueryChange={this.handleQueryChange}
            />
            <div className={styles.foundModulesInfo}>
              {this.foundModulesInfo}
            </div>
            {store.isSearching && store.hasFoundModules && (
              <div className={styles.foundModulesContainer}>
                {store.foundModulesByChunk.map(({ chunk, modules }) => (
                  <div key={chunk.cid} className={styles.foundModulesChunk}>
                    <div
                      className={styles.foundModulesChunkName}
                      onClick={() => this.treemap.zoomToGroup(chunk)}
                    >
                      {chunk.label}
                    </div>
                    <ModulesList
                      className={styles.foundModulesList}
                      modules={modules}
                      showSize={store.activeSize}
                      highlightedText={store.searchQueryRegexp}
                      isModuleVisible={this.isModuleVisible}
                      onModuleClick={this.handleFoundModuleClick}
                    />
                  </div>
                ))}
              </div>
            )}
          </div>
          {this.chunkItems.length > 1 && (
            <div className={styles.sidebarGroup}>
              <CheckboxList
                label="Show chunks"
                items={this.chunkItems}
                checkedItems={store.selectedChunks}
                renderLabel={this.renderChunkItemLabel}
                onChange={this.handleSelectedChunksChange}
              />
            </div>
          )}
        </Sidebar>
        <Treemap
          ref={this.saveTreemapRef}
          className={styles.map}
          data={store.visibleChunks}
          highlightGroups={this.highlightedModules}
          weightProp={store.activeSize}
          onMouseLeave={this.handleMouseLeaveTreemap}
          onGroupHover={this.handleTreemapGroupHover}
          onGroupSecondaryClick={this.handleTreemapGroupSecondaryClick}
          onResize={this.handleResize}
        />
        {tooltipContent && (
          <Tooltip visible={showTooltip}>{tooltipContent}</Tooltip>
        )}
        <ContextMenu
          visible={showChunkContextMenu}
          chunk={selectedChunk}
          coords={selectedMouseCoords}
          onHide={this.handleChunkContextMenuHide}
        />
      </div>
    );
  }

  renderModuleSize(module, sizeType) {
    const sizeProp = `${sizeType}Size`;
    const size = module[sizeProp];
    const sizeLabel = getSizeSwitchItems().find(
      (item) => item.prop === sizeProp,
    ).label;
    const isActive = store.activeSize === sizeProp;

    return typeof size === "number" ? (
      <div className={isActive ? styles.activeSize : ""}>
        {sizeLabel} size: <strong>{filesize(size)}</strong>
      </div>
    ) : null;
  }

  renderChunkItemLabel = (item) => {
    const isAllItem = item === CheckboxList.ALL_ITEM;
    const label = isAllItem ? "All" : item.label;
    const size = isAllItem ? store.totalChunksSize : item[store.activeSize];

    return (
      <>
        {label} (<strong>{filesize(size)}</strong>)
      </>
    );
  };

  get sizeSwitchItems() {
    return store.hasParsedSizes
      ? getSizeSwitchItems()
      : getSizeSwitchItems().slice(0, 1);
  }

  get activeSizeItem() {
    return this.sizeSwitchItems.find((item) => item.prop === store.activeSize);
  }

  get chunkItems() {
    const { allChunks, activeSize } = store;
    let chunkItems = [...allChunks];

    if (activeSize !== "statSize") {
      chunkItems = chunkItems.filter(isChunkParsed);
    }

    chunkItems.sort(
      (chunk1, chunk2) => chunk2[activeSize] - chunk1[activeSize],
    );

    return chunkItems;
  }

  get highlightedModules() {
    return new Set(store.foundModules);
  }

  get foundModulesInfo() {
    if (!store.isSearching) {
      // `&nbsp;` to reserve space
      return "\u00A0";
    }

    if (store.hasFoundModules) {
      return (
        <>
          <div className={styles.foundModulesInfoItem}>
            Count: <strong>{store.foundModules.length}</strong>
          </div>
          <div className={styles.foundModulesInfoItem}>
            Total size: <strong>{filesize(store.foundModulesSize)}</strong>
          </div>
        </>
      );
    }

    return `Nothing found${store.allChunksSelected ? "" : " in selected chunks"}`;
  }

  handleSelectionChange = (selected) => {
    if (!selected) {
      store.setSelectedChunks(store.allChunks);
      return;
    }

    store.setSelectedChunks(
      store.allChunks.filter(
        (chunk) => chunk.isInitialByEntrypoint[selected] ?? false,
      ),
    );
  };

  handleConcatenatedModulesContentToggle = (flag) => {
    store.showConcatenatedModulesContent = flag;
    if (flag) {
      localStorage.setItem("showConcatenatedModulesContent", true);
    } else {
      localStorage.removeItem("showConcatenatedModulesContent");
    }
  };

  handleChunkContextMenuHide = () => {
    this.setState({
      showChunkContextMenu: false,
    });
  };

  handleResize = () => {
    // Close any open context menu when the report is resized,
    // so it doesn't show in an incorrect position
    if (this.state.showChunkContextMenu) {
      this.setState({
        showChunkContextMenu: false,
      });
    }
  };

  handleSidebarToggle = () => {
    if (this.state.sidebarPinned) {
      setTimeout(() => this.treemap.resize());
    }
  };

  handleSidebarPinStateChange = (pinned) => {
    this.setState({ sidebarPinned: pinned });
    setTimeout(() => this.treemap.resize());
  };

  handleSidebarResize = () => {
    this.treemap.resize();
  };

  handleSizeSwitch = (sizeSwitchItem) => {
    store.setSelectedSize(sizeSwitchItem.prop);
  };

  handleQueryChange = (query) => {
    store.setSearchQuery(query);
  };

  handleSelectedChunksChange = (selectedChunks) => {
    store.setSelectedSize(selectedChunks);
  };

  handleMouseLeaveTreemap = () => {
    this.setState({ showTooltip: false });
  };

  handleTreemapGroupSecondaryClick = (event) => {
    const { group } = event;

    if (group && group.isAsset) {
      this.setState({
        selectedChunk: group,
        selectedMouseCoords: { ...this.mouseCoords },
        showChunkContextMenu: true,
      });
    } else {
      this.setState({
        selectedChunk: null,
        showChunkContextMenu: false,
      });
    }
  };

  handleTreemapGroupHover = (event) => {
    const { group } = event;

    if (group) {
      this.setState({
        showTooltip: true,
        tooltipContent: this.getTooltipContent(group),
      });
    } else {
      this.setState({ showTooltip: false });
    }
  };

  handleFoundModuleClick = (module) => this.treemap.zoomToGroup(module);

  handleMouseMove = (event) => {
    Object.assign(this.mouseCoords, {
      x: event.pageX,
      y: event.pageY,
    });
  };

  isModuleVisible = (module) => this.treemap.isGroupRendered(module);

  saveTreemapRef = (treemap) => (this.treemap = treemap);

  getTooltipContent(module) {
    if (!module) return null;

    return (
      <div>
        <div>
          <strong>{module.label}</strong>
        </div>
        <br />
        {this.renderModuleSize(module, "stat")}
        {!module.inaccurateSizes && this.renderModuleSize(module, "parsed")}
        {!module.inaccurateSizes &&
          this.renderModuleSize(module, globalThis.compressionAlgorithm)}
        {module.path && (
          <div>
            Path: <strong>{module.path}</strong>
          </div>
        )}
        {module.isAsset && (
          <div>
            <br />
            <strong>
              <em>Right-click to view options related to this chunk</em>
            </strong>
          </div>
        )}
      </div>
    );
  }
}

export default observer(ModulesTreemap);


================================================
FILE: client/components/Search.css
================================================
.container {
  font: var(--main-font);
  white-space: nowrap;
}

.label {
  font-weight: bold;
  margin-bottom: 7px;
}

.row {
  display: flex;
}

.input {
  border: 1px solid var(--border-color);
  border-radius: 4px;
  display: block;
  flex: 1;
  padding: 5px;
  background: var(--bg-primary);
  color: var(--text-primary);
  transition:
    background-color 0.3s ease,
    color 0.3s ease,
    border-color 0.3s ease;
}

.input:focus {
  outline: none;
  border-color: var(--text-secondary);
}

.clear {
  flex: 0 0 auto;
  line-height: 1;
  margin-left: 3px;
  padding: 5px 8px 7px;
}


================================================
FILE: client/components/Search.jsx
================================================
// TODO: switch to a more modern debounce package once we drop Node.js 10 support
import debounce from "debounce";

import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import Button from "./Button.jsx";

import * as styles from "./Search.css";

export default class Search extends PureComponent {
  static propTypes = {
    className: PropTypes.string,

    label: PropTypes.string.isRequired,
    query: PropTypes.string.isRequired,

    autofocus: PropTypes.bool,

    onQueryChange: PropTypes.func.isRequired,
  };

  componentDidMount() {
    if (this.props.autofocus) {
      this.focus();
    }
  }

  componentWillUnmount() {
    this.handleValueChange.clear();
  }

  render() {
    const { label, query } = this.props;

    return (
      <div className={styles.container}>
        <div className={styles.label}>{label}:</div>
        <div className={styles.row}>
          <input
            ref={this.saveInputNode}
            className={styles.input}
            type="text"
            value={query}
            placeholder="Enter regexp"
            onInput={this.handleValueChange}
            onBlur={this.handleInputBlur}
            onKeyDown={this.handleKeyDown}
          />
          <Button className={styles.clear} onClick={this.handleClearClick}>
            x
          </Button>
        </div>
      </div>
    );
  }

  handleValueChange = debounce((event) => {
    this.informChange(event.target.value);
  }, 400);

  handleInputBlur = () => {
    this.handleValueChange.flush();
  };

  handleClearClick = () => {
    this.clear();
    this.focus();
  };

  handleKeyDown = (event) => {
    let handled = true;

    switch (event.key) {
      case "Escape":
        this.clear();
        break;
      case "Enter":
        this.handleValueChange.flush();
        break;
      default:
        handled = false;
    }

    if (handled) {
      event.stopPropagation();
    }
  };

  focus() {
    if (this.input) {
      this.input.focus();
    }
  }

  clear() {
    this.handleValueChange.clear();
    this.informChange("");
    this.input.value = "";
  }

  informChange(value) {
    this.props.onQueryChange(value);
  }

  saveInputNode = (node) => (this.input = node);
}


================================================
FILE: client/components/Sidebar.css
================================================
.container {
  background: var(--bg-primary);
  border: none;
  border-right: 1px solid var(--border-color);
  box-sizing: border-box;
  max-width: calc(50% - 10px);
  opacity: 0.95;
  z-index: 1;
  transition:
    background-color 0.3s ease,
    border-color 0.3s ease;
}

.container:not(.hidden) {
  min-width: 200px;
}

.container:not(.pinned) {
  bottom: 0;
  position: absolute;
  top: 0;
  transition: transform 200ms ease;
}

.container.pinned {
  position: relative;
}

.container.left {
  left: 0;
}

.container.left.hidden {
  transform: translateX(calc(-100% + 7px));
}

.content {
  box-sizing: border-box;
  height: 100%;
  overflow-y: auto;
  padding: 25px 20px 20px;
  width: 100%;
}

.empty.pinned .content {
  padding: 0;
}

.container :global(.themeToggle) {
  position: absolute;
  top: 10px;
  left: 15px;
  z-index: 10;
  height: 26px;
  width: 27px;
  padding: 0;
}

.pinButton,
.toggleButton {
  cursor: pointer;
  height: 26px;
  line-height: 0;
  position: absolute;
  top: 10px;
  width: 27px;
}

.pinButton {
  right: 47px;
}

.toggleButton {
  padding-left: 6px;
  right: 15px;
}

.hidden .toggleButton {
  right: -35px;
  transition: transform 0.2s ease;
}

.hidden .toggleButton:hover {
  transform: translateX(4px);
}

.resizer {
  bottom: 0;
  cursor: col-resize;
  position: absolute;
  right: 0;
  top: 0;
  width: 7px;
}

:export {
  toggleTime: 200ms;
}


================================================
FILE: client/components/Sidebar.jsx
================================================
import cls from "classnames";
import { Component } from "preact";
import PropTypes from "prop-types";
import Button from "./Button.jsx";
import Icon from "./Icon.jsx";
import * as styles from "./Sidebar.css";
import ThemeToggle from "./ThemeToggle.jsx";

const toggleTime = Number.parseInt(styles.toggleTime, 10);

export default class Sidebar extends Component {
  static propTypes = {
    pinned: PropTypes.bool.isRequired,
    position: PropTypes.string,

    onToggle: PropTypes.func.isRequired,
    onResize: PropTypes.func.isRequired,
    onPinStateChange: PropTypes.func.isRequired,

    children: PropTypes.node.isRequired,
  };

  static defaultProps = {
    pinned: false,
    position: "left",
  };

  allowHide = true;

  toggling = false;

  hideContentTimeout = null;

  width = null;

  state = {
    visible: true,
    renderContent: true,
  };

  componentDidMount() {
    this.hideTimeoutId = setTimeout(() => this.toggleVisibility(false), 3000);
  }

  componentWillUnmount() {
    clearTimeout(this.hideTimeoutId);
    clearTimeout(this.hideContentTimeout);
  }

  render() {
    const { position, pinned, children } = this.props;
    const { visible, renderContent } = this.state;

    const className = cls({
      [styles.container]: true,
      [styles.pinned]: pinned,
      [styles.left]: position === "left",
      [styles.hidden]: !visible,
      [styles.empty]: !renderContent,
    });

    return (
      <div
        ref={this.saveNode}
        className={className}
        onClick={this.handleClick}
        onMouseLeave={this.handleMouseLeave}
      >
        <ThemeToggle />
        {visible && (
          <Button
            type="button"
            title="Pin"
            className={styles.pinButton}
            active={pinned}
            toggle
            onClick={this.handlePinButtonClick}
          >
            <Icon name="pin" size={13} />
          </Button>
        )}
        <Button
          type="button"
          title={visible ? "Hide" : "Show sidebar"}
          className={styles.toggleButton}
          onClick={this.handleToggleButtonClick}
        >
          <Icon name="arrow-right" size={10} rotate={visible ? 180 : 0} />
        </Button>
        {pinned && visible && (
          <div
            className={styles.resizer}
            onMouseDown={this.handleResizeStart}
          />
        )}
        <div
          className={styles.content}
          onMouseEnter={this.handleMouseEnter}
          onMouseMove={this.handleMouseMove}
        >
          {renderContent ? children : null}
        </div>
      </div>
    );
  }

  handleClick = () => {
    this.allowHide = false;
  };

  handleMouseEnter = () => {
    if (!this.toggling && !this.props.pinned) {
      clearTimeout(this.hideTimeoutId);
      this.toggleVisibility(true);
    }
  };

  handleMouseMove = () => {
    this.allowHide = true;
  };

  handleMouseLeave = () => {
    if (this.allowHide && !this.toggling && !this.props.pinned) {
      this.toggleVisibility(false);
    }
  };

  handleToggleButtonClick = () => {
    this.toggleVisibility();
  };

  handlePinButtonClick = () => {
    const pinned = !this.props.pinned;
    this.width = pinned ? this.node.getBoundingClientRect().width : null;
    this.updateNodeWidth();
    this.props.onPinStateChange(pinned);
  };

  handleResizeStart = (event) => {
    this.resizeInfo = {
      startPageX: event.pageX,
      initialWidth: this.width,
    };
    document.body.classList.add("resizing", "col");
    document.addEventListener("mousemove", this.handleResize, true);
    document.addEventListener("mouseup", this.handleResizeEnd, true);
  };

  handleResize = (event) => {
    this.width =
      this.resizeInfo.initialWidth + (event.pageX - this.resizeInfo.startPageX);
    this.updateNodeWidth();
  };

  handleResizeEnd = () => {
    document.body.classList.remove("resizing", "col");
    document.removeEventListener("mousemove", this.handleResize, true);
    document.removeEventListener("mouseup", this.handleResizeEnd, true);
    this.props.onResize();
  };

  toggleVisibility(flag) {
    clearTimeout(this.hideContentTimeout);

    const { visible } = this.state;
    const { onToggle, pinned } = this.props;

    if (flag === undefined) {
      flag = !visible;
    } else if (flag === visible) {
      return;
    }

    this.setState({ visible: flag });
    this.toggling = true;
    setTimeout(() => {
      this.toggling = false;
    }, toggleTime);

    if (pinned) {
      this.updateNodeWidth(flag ? this.width : null);
    }

    if (flag || pinned) {
      this.setState({ renderContent: flag });
      onToggle(flag);
    } else if (!flag) {
      // Waiting for the CSS animation to finish and hiding content
      this.hideContentTimeout = setTimeout(() => {
        this.hideContentTimeout = null;
        this.setState({ renderContent: false });
        onToggle(false);
      }, toggleTime);
    }
  }

  saveNode = (node) => (this.node = node);

  updateNodeWidth(width = this.width) {
    this.node.style.width = width ? `${width}px` : "";
  }
}


================================================
FILE: client/components/Switcher.css
================================================
.container {
  font: var(--main-font);
  white-space: nowrap;
}

.label {
  font-weight: bold;
  font-size: 11px;
  margin-bottom: 7px;
}

.item + .item {
  margin-left: 5px;
}


================================================
FILE: client/components/Switcher.jsx
================================================
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import * as styles from "./Switcher.css";
import SwitcherItem from "./SwitcherItem.jsx";
import { SwitcherItemType } from "./types.js";

export default class Switcher extends PureComponent {
  static propTypes = {
    label: PropTypes.string.isRequired,

    items: PropTypes.arrayOf(SwitcherItemType).isRequired,
    activeItem: SwitcherItemType.isRequired,

    onSwitch: PropTypes.func.isRequired,
  };

  render() {
    const { label, items, activeItem, onSwitch } = this.props;

    return (
      <div className={styles.container}>
        <div className={styles.label}>{label}:</div>
        <div>
          {items.map((item) => (
            <SwitcherItem
              key={item.label}
              className={styles.item}
              item={item}
              active={item === activeItem}
              onClick={onSwitch}
            />
          ))}
        </div>
      </div>
    );
  }
}


================================================
FILE: client/components/SwitcherItem.jsx
================================================
import PropTypes from "prop-types";
import PureComponent from "../lib/PureComponent.jsx";
import Button from "./Button.jsx";
import { SwitcherItemType } from "./types.js";

export default class SwitcherItem extends PureComponent {
  static propTypes = {
    active: PropTypes.bool.isRequired,

    item: SwitcherItemType.isRequired,

    onClick: PropTypes.func.isRequired,
  };

  render({ item, ...props }) {
    return (
      <Button {...props} onClick={this.handleClick}>
        {item.label}
      </Button>
    );
  }

  handleClick = () => {
    this.props.onClick(this.props.item);
  };
}


================================================
FILE: client/components/ThemeToggle.css
================================================
.themeToggle {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  transition: background-color 0.2s ease;
}

.themeToggle:hover {
  background: rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] .themeToggle:hover {
  background: rgba(255, 255, 255, 0.1);
}


================================================
FILE: client/components/ThemeToggle.jsx
================================================
import { observer } from "mobx-react";
import { Component } from "preact";

import { store } from "../store.js";
import Button from "./Button.jsx";
import Icon from "./Icon.jsx";

import * as styles from "./ThemeToggle.css";

class ThemeToggle extends Component {
  render() {
    const { darkMode } = store;

    return (
      <Button
        type="button"
        title={darkMode ? "Switch to Light Mode" : "Switch to Dark Mode"}
        className={styles.themeToggle}
        onClick={this.handleToggle}
      >
        <Icon name={darkMode ? "sun" : "moon"} size={16} />
      </Button>
    );
  }

  handleToggle = () => {
    store.toggleDarkMode();
  };
}

export default observer(ThemeToggle);


================================================
FILE: client/components/Tooltip.css
================================================
.container {
  font: var(--main-font);
  position: absolute;
  padding: 5px 10px;
  border-radius: 4px;
  background: #fff;
  border: 1px solid #aaa;
  opacity: 0.9;
  white-space: nowrap;
  visibility: visible;
  transition:
    opacity 0.2s ease,
    visibility 0.2s ease;
}

.hidden {
  opacity: 0;
  visibility: hidden;
}

:global(html[data-theme="dark"]) .container {
  background: var(--bg-primary);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
}


================================================
FILE: client/components/Tooltip.jsx
================================================
import cls from "classnames";
import { Component } from "preact";

import PropTypes from "prop-types";
import * as styles from "./Tooltip.css";

export default class Tooltip extends Component {
  static propTypes = {
    visible: PropTypes.bool.isRequired,

    children: PropTypes.node.isRequired,
  };

  static marginX = 10;

  static marginY = 30;

  mouseCoords = {
    x: 0,
    y: 0,
  };

  state = {
    left: 0,
    top: 0,
  };

  componentDidMount() {
    document.addEventListener("mousemove", this.handleMouseMove, true);
  }

  shouldComponentUpdate(nextProps) {
    return this.props.visible || nextProps.visible;
  }

  componentWillUnmount() {
    document.removeEventListener("mousemove", this.handleMouseMove, true);
  }

  render() {
    const { children, visible } = this.props;

    const className = cls({
      [styles.container]: true,
      [styles.hidden]: !visible,
    });

    return (
      <div ref={this.saveNode} className={className} style={this.getStyle()}>
        {children}
      </div>
    );
  }

  handleMouseMove = (event) => {
    Object.assign(this.mouseCoords, {
      x: event.pageX,
      y: event.pageY,
    });

    if (this.props.visible) {
      this.updatePosition();
    }
  };

  saveNode = (node) => (this.node = node);

  getStyle() {
    return {
      left: this.state.left,
      top: this.state.top,
    };
  }

  updatePosition() {
    if (!this.props.visible) return;

    const pos = {
      left: this.mouseCoords.x + Tooltip.marginX,
      top: this.mouseCoords.y + Tooltip.marginY,
    };

    const boundingRect = this.node.getBoundingClientRect();

    if (pos.left + boundingRect.width > window.innerWidth) {
      // Shifting horizontally
      pos.left = window.innerWidth - boundingRect.width;
    }

    if (pos.top + boundingRect.height > window.innerHeight) {
      // Flipping vertically
      pos.top = this.mouseCoords.y - Tooltip.marginY - boundingRect.height;
    }

    this.setState(pos);
  }
}


================================================
FILE: client/components/Treemap.jsx
================================================
import FoamTree from "@carrotsearch/foamtree";
import { Component } from "preact";
import PropTypes from "prop-types";
import { SizeType, ViewerDataType } from "./types.js";

/**
 * @param {Event} event event
 */
function preventDefault(event) {
  event.preventDefault();
}

/**
 * @param {string} str string
 * @returns {number} hash
 */
function hashCode(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const code = str.charCodeAt(i);
    hash = (hash << 5) - hash + code;
    hash &= hash;
  }
  return hash;
}

export default class Treemap extends Component {
  static propTypes = {
    classname: PropTypes.string,

    data: ViewerDataType.isRequired,
    highlightGroups: PropTypes.instanceOf(Set).isRequired,
    weightProp: SizeType.isRequired,

    onGroupHover: PropTypes.func,
    onGroupSecondaryClick: PropTypes.func,
    onMouseLeave: PropTypes.func,
    onResize: PropTypes.func,
  };

  constructor(props) {
    super(props);
    this.treemap = null;
    this.zoomOutDisabled = false;
    this.findChunkNamePartIndex();
  }

  componentDidMount() {
    this.treemap = this.createTreemap();
    window.addEventListener("resize", this.resize);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.data !== this.props.data) {
      this.findChunkNamePartIndex();
      this.treemap.set({
        dataObject: this.getTreemapDataObject(nextProps.data),
      });
    } else if (nextProps.highlightGroups !== this.props.highlightGroups) {
      setTimeout(() => this.treemap.redraw());
    }
  }

  shouldComponentUpdate() {
    return false;
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.resize);
    this.treemap.dispose();
  }

  render() {
    return <div {...this.props} ref={this.saveNodeRef} />;
  }

  saveNodeRef = (node) => (this.node = node);

  getTreemapDataObject(data = this.props.data) {
    return { groups: data };
  }

  createTreemap() {
    const component = this;
    const { props } = this;

    return new FoamTree({
      element: this.node,
      layout: "squarified",
      stacking: "flattened",
      pixelRatio: window.devicePixelRatio || 1,
      maxGroups: Infinity,
      maxGroupLevelsDrawn: Infinity,
      maxGroupLabelLevelsDrawn: Infinity,
      maxGroupLevelsAttached: Infinity,
      wireframeLabelDrawing: "always",
      groupMinDiameter: 0,
      groupLabelVerticalPadding: 0.2,
      rolloutDuration: 0,
      pullbackDuration: 0,
      fadeDuration: 0,
      groupExposureZoomMargin: 0.2,
      zoomMouseWheelDuration: 300,
      openCloseDuration: 200,
      dataObject: this.getTreemapDataObject(),
      titleBarDecorator(opts, props, vars) {
        vars.titleBarShown = false;
      },
      groupColorDecorator(options, properties, variables) {
        const root = component.getGroupRoot(properties.group);
        const chunkName = component.getChunkNamePart(root.label);
        const hash = /[^0-9]/u.test(chunkName)
          ? hashCode(chunkName)
          : (Number.parseInt(chunkName, 10) / 1000) * 360;
        variables.groupColor = {
          model: "hsla",
          h: Math.round(Math.abs(hash) % 360),
          s: 60,
          l: 50,
          a: 0.9,
        };

        const { highlightGroups } = component.props;
        const module = properties.group;

        if (highlightGroups && highlightGroups.has(module)) {
          variables.groupColor = {
            model: "rgba",
            r: 255,
            g: 0,
            b: 0,
            a: 0.8,
          };
        } else if (highlightGroups && highlightGroups.size > 0) {
          // this means a search (e.g.) is active, but this module
          // does not match; gray it out
          // https://github.com/webpack/webpack-bundle-analyzer/issues/553
          variables.groupColor.s = 10;
        }
      },
      /**
       * Handle Foamtree's "group clicked" event
       * @param {FoamtreeEvent} event foamtree event object (see https://get.carrotsearch.com/foamtree/demo/api/index.html#event-details)
       * @returns {void}
       */
      onGroupClick(event) {
        preventDefault(event);
        if ((event.ctrlKey || event.secondary) && props.onGroupSecondaryClick) {
          props.onGroupSecondaryClick.call(component, event);
          return;
        }
        component.zoomOutDisabled = false;
        this.zoom(event.group);
      },
      onGroupDoubleClick: preventDefault,
      onGroupHover(event) {
        // Ignoring hovering on `FoamTree` branding group and the root group
        if (
          event.group &&
          (event.group.attribution || event.group === this.get("dataObject"))
        ) {
          event.preventDefault();
          if (props.onMouseLeave) {
            props.onMouseLeave.call(component, event);
          }
          return;
        }

        if (props.onGroupHover) {
          props.onGroupHover.call(component, event);
        }
      },
      onGroupMouseWheel(event) {
        const { scale } = this.get("viewport");
        const isZoomOut = event.delta < 0;

        if (isZoomOut) {
          if (component.zoomOutDisabled) return preventDefault(event);
          if (scale < 1) {
            component.zoomOutDisabled = true;
            preventDefault(event);
          }
        } else {
          component.zoomOutDisabled = false;
        }
      },
    });
  }

  getGroupRoot(group) {
    let nextParent;
    while (
      !group.isAsset &&
      (nextParent = this.treemap.get("hierarchy", group).parent)
    ) {
      group = nextParent;
    }
    return group;
  }

  zoomToGroup(group) {
    this.zoomOutDisabled = false;

    while (group && !this.treemap.get("state", group).revealed) {
      group = this.treemap.get("hierarchy", group).parent;
    }

    if (group) {
      this.treemap.zoom(group);
    }
  }

  isGroupRendered(group) {
    const groupState = this.treemap.get("state", group);
    return Boolean(groupState) && groupState.revealed;
  }

  update() {
    this.treemap.update();
  }

  resize = () => {
    const { props } = this;
    this.treemap.resize();

    if (props.onResize) {
      props.onResize();
    }
  };

  /**
   * Finds patterns across all chunk names to identify the unique "name" part.
   */
  findChunkNamePartIndex() {
    const splitChunkNames = this.props.data.map((chunk) =>
      chunk.label.split(/[^a-z0-9]/iu),
    );
    const longestSplitName = Math.max(
      ...splitChunkNames.map((parts) => parts.length),
    );
    const namePart = {
      index: 0,
      votes: 0,
    };
    for (let i = longestSplitName - 1; i >= 0; i--) {
      const identifierVotes = {
        name: 0,
        hash: 0,
        ext: 0,
      };
      let lastChunkPart = "";
      for (const splitChunkName of splitChunkNames) {
        const part = splitChunkName[i];
        if (part === undefined || part === "") {
          continue;
        }
        if (part === lastChunkPart) {
          identifierVotes.ext++;
        } else if (
          /[a-z]/u.test(part) &&
          /[0-9]/u.test(part) &&
          part.length === lastChunkPart.length
        ) {
          identifierVotes.hash++;
        } else if (/^[a-z]+$/iu.test(part) || /^[0-9]+$/u.test(part)) {
          identifierVotes.name++;
        }
        lastChunkPart = part;
      }
      if (identifierVotes.name >= namePart.votes) {
        namePart.index = i;
        namePart.votes = identifierVotes.name;
      }
    }
    this.chunkNamePartIndex = namePart.index;
  }

  getChunkNamePart(chunkLabel) {
    return (
      chunkLabel.split(/[^a-z0-9]/iu)[this.chunkNamePartIndex] || chunkLabel
    );
  }
}


================================================
FILE: client/components/types.js
================================================
import PropTypes from "prop-types";

export const GroupType = PropTypes.shape({
  cid: PropTypes.number.isRequired,
  label: PropTypes.string.isRequired,
  path: PropTypes.string.isRequired,
  // eslint-disable-next-line new-cap
  groups: PropTypes.arrayOf((...args) => GroupType(...args)),
  statSize: PropTypes.number.isRequired,
  parsedSize: PropTypes.number.isRequired,
  gzipSize: PropTypes.number,
  brotliSize: PropTypes.number,
  zstdSize: PropTypes.number,
});

export const ViewerDataItemType = PropTypes.shape({
  cid: PropTypes.number.isRequired,
  label: PropTypes.string.isRequired,
  isAsset: PropTypes.bool,
  statSize: PropTypes.number.isRequired,
  parsedSize: PropTypes.number.isRequired,
  gzipSize: PropTypes.number,
  brotliSize: PropTypes.number,
  zstdSize: PropTypes.number,
  groups: PropTypes.arrayOf(GroupType).isRequired,
  isInitialByEntrypoint: PropTypes.objectOf(PropTypes.bool),
});

export const ViewerDataType = PropTypes.arrayOf(ViewerDataItemType);

export const ModuleType = PropTypes.shape({
  cid: PropTypes.number.isRequired,
  label: PropTypes.string.isRequired,
  path: PropTypes.string,
  statSize: PropTypes.number.isRequired,
  parsedSize: PropTypes.number.isRequired,
  gzipSize: PropTypes.number,
  brotliSize: PropTypes.number,
  zstdSize: PropTypes.number,
  weight: PropTypes.number.isRequired,
});

export const SizeType = PropTypes.oneOf(["statSize", "parsedSize", "gzipSize"]);

export const SwitcherItemType = PropTypes.shape({
  label: PropTypes.string,
  prop: SizeType,
});


================================================
FILE: client/lib/PureComponent.jsx
================================================
import { Component } from "preact";

/**
 * @param {object} obj1 obj1
 * @param {object} obj2 obj2
 * @returns {boolean} true when the same, otherwise false
 */
function isEqual(obj1, obj2) {
  if (obj1 === obj2) return true;
  const keys = Object.keys(obj1);
  if (keys.length !== Object.keys(obj2).length) return false;
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    if (obj1[key] !== obj2[key]) return false;
  }
  return true;
}

export default class PureComponent extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return !isEqual(nextProps, this.props) || !isEqual(this.state, nextState);
  }
}


================================================
FILE: client/localStorage.js
================================================
const KEY_PREFIX = "wba";

export default {
  getItem(key) {
    try {
      return JSON.parse(
        globalThis.localStorage.getItem(`${KEY_PREFIX}.${key}`),
      );
    } catch {
      return null;
    }
  },

  setItem(key, value) {
    try {
      globalThis.localStorage.setItem(
        `${KEY_PREFIX}.${key}`,
        JSON.stringify(value),
      );
    } catch {
      /* ignored */
    }
  },

  removeItem(key) {
    try {
      globalThis.localStorage.removeItem(`${KEY_PREFIX}.${key}`);
    } catch {
      /* ignored */
    }
  },
};


================================================
FILE: client/store.js
================================================
import { action, computed, makeObservable, observable } from "mobx";
import localStorage from "./localStorage.js";
import { isChunkParsed, walkModules } from "./utils.js";

export class Store {
  cid = 0;

  sizes = new Set([
    "statSize",
    "parsedSize",
    "gzipSize",
    "brotliSize",
    "zstdSize",
  ]);

  allChunks;

  selectedChunks;

  searchQuery = "";

  defaultSize;

  selectedSize;

  showConcatenatedModulesContent =
    localStorage.getItem("showConcatenatedModulesContent") === true;

  darkMode = (() => {
    const systemPrefersDark = globalThis.matchMedia(
      "(prefers-color-scheme: dark)",
    ).matches;

    try {
      const saved = localStorage.getItem("darkMode");
      if (saved !== null) return saved === "true";
    } catch {
      // Some browsers might not have localStorage available and we can fail silently
    }

    return systemPrefersDark;
  })();

  constructor() {
    makeObservable(this, {
      allChunks: observable.ref,
      selectedChunks: observable.shallow,
      searchQuery: observable,
      defaultSize: observable,
      selectedSize: observable,
      showConcatenatedModulesContent: observable,
      darkMode: observable,

      toggleDarkMode: action,
      setModules: action,
      setSelectedChunks: action,
      setSelectedSize: action,
      setSearchQuery: action,

      hasParsedSizes: computed,
      activeSize: computed,
      visibleChunks: computed,
      allChunksSelected: computed,
      totalChunksSize: computed,
      searchQueryRegexp: computed,
      isSearching: computed,
      foundModulesByChunk: computed,
      foundModules: computed,
      hasFoundModules: computed,
      hasConcatenatedModules: computed,
      foundModulesSize: computed,
    });
  }

  setModules(modules) {
    walkModules(modules, (module) => {
      module.cid = this.cid++;
    });

    this.allChunks = modules;
    this.selectedChunks = this.allChunks;
  }

  setEntrypoints(entrypoints) {
    this.entrypoints = entrypoints;
  }

  get hasParsedSizes() {
    return this.allChunks.some(isChunkParsed);
  }

  setSelectedSize(selectedSize) {
    this.selectedSize = selectedSize;
  }

  get activeSize() {
    const activeSize = this.selectedSize || this.defaultSize;

    if (!this.hasParsedSizes || !this.sizes.has(activeSize)) {
      return "statSize";
    }

    return activeSize;
  }

  setSelectedChunks(chunks) {
    this.selectedChunks = chunks;
  }

  get visibleChunks() {
    const visibleChunks = this.allChunks.filter((chunk) =>
      this.selectedChunks.includes(chunk),
    );

    return this.filterModulesForSize(visibleChunks, this.activeSize);
  }

  get allChunksSelected() {
    return this.visibleChunks.length === this.allChunks.length;
  }

  get totalChunksSize() {
    return this.allChunks.reduce(
      (totalSize, chunk) => totalSize + (chunk[this.activeSize] || 0),
      0,
    );
  }

  get searchQueryRegexp() {
    const query = this.searchQuery.trim();

    if (!query) {
      return null;
    }

    try {
      return new RegExp(query, "iu");
    } catch {
      return null;
    }
  }

  get isSearching() {
    return Boolean(this.searchQueryRegexp);
  }

  get foundModulesByChunk() {
    if (!this.isSearching) {
      return [];
    }

    const query = this.searchQueryRegexp;

    return this.visibleChunks
      .map((chunk) => {
        let foundGroups = [];

        walkModules(chunk.groups, (module) => {
          let weight = 0;

          /**
           * Splitting found modules/directories into groups:
           *
           * 1) Module with matched label (weight = 4)
           * 2) Directory with matched label (weight = 3)
           * 3) Module with matched path (weight = 2)
           * 4) Directory with matched path (weight = 1)
           */
          if (query.test(module.label)) {
            weight += 3;
          } else if (module.path && query.test(module.path)) {
            weight++;
          }

          if (!weight) return;

          if (!module.groups) {
            weight += 1;
          }

          const foundModules = (foundGroups[weight - 1] =
            foundGroups[weight - 1] || []);
          foundModules.push(module);
        });

        const { activeSize } = this;

        // Filtering out missing groups
        foundGroups = foundGroups.filter(Boolean).reverse();
        // Sorting each group by active size
        for (const modules of foundGroups) {
          modules.sort((m1, m2) => m2[activeSize] - m1[activeSize]);
        }

        return {
          chunk,
          modules: foundGroups.flat(),
        };
      })
      .filter((result) => result.modules.length > 0)
      .toSorted((c1, c2) => c1.modules.length - c2.modules.length);
  }

  setSearchQuery(query) {
    this.searchQuery = query;
  }

  get foundModules() {
    return this.foundModulesByChunk.reduce(
      (arr, chunk) => [...arr, ...chunk.modules],
      [],
    );
  }

  get hasFoundModules() {
    return this.foundModules.length > 0;
  }

  get hasConcatenatedModules() {
    let result = false;

    walkModules(this.visibleChunks, (module) => {
      if (module.concatenated) {
        result = true;
        return false;
      }
    });

    return result;
  }

  get foundModulesSize() {
    return this.foundModules.reduce(
      (summ, module) => summ + module[this.activeSize],
      0,
    );
  }

  filterModulesForSize(modules, sizeProp) {
    return modules.reduce((filteredModules, module) => {
      if (module[sizeProp]) {
        if (module.groups) {
          const showContent =
            !module.concatenated || this.showConcatenatedModulesContent;

          module = {
            ...module,
            groups: showContent
              ? this.filterModulesForSize(module.groups, sizeProp)
              : null,
          };
        }

        module.weight = module[sizeProp];
        filteredModules.push(module);
      }

      return filteredModules;
    }, []);
  }

  toggleDarkMode() {
    this.darkMode = !this.darkMode;
    try {
      localStorage.setItem("darkMode", this.darkMode);
    } catch {
      // Some browsers might not have localStorage available and we can fail silently
    }
    this.updateTheme();
  }

  updateTheme() {
    if (this.darkMode) {
      document.documentElement.dataset.theme = "dark";
    } else {
      delete document.documentElement.dataset.theme;
    }
  }
}

export const store = new Store();


================================================
FILE: client/utils.js
================================================
/**
 * @param {Chunk} chunk chunk
 * @returns {boolean} true when chunk is parser, otherwise false
 */
export function isChunkParsed(chunk) {
  return typeof chunk.parsedSize === "number";
}

/**
 * @param {Module[]} modules modules
 * @param {(module: Module) => boolean} cb callback
 * @returns {boolean} state
 */
export function walkModules(modules, cb) {
  for (const module of modules) {
    if (cb(module) === false) return false;

    if (module.groups && walkModules(module.groups, cb) === false) {
      return false;
    }
  }
}

/**
 * @template T
 * @param {T} elem element
 * @param {T[]} container container
 * @returns {boolean} true when element is outside, otherwise false
 */
export function elementIsOutside(elem, container) {
  return !(elem === container || container.contains(elem));
}


================================================
FILE: client/viewer.css
================================================
:root {
  --main-font: normal 11px Verdana, sans-serif;
  --bg-primary: #fff;
  --bg-secondary: #f5f5f5;
  --text-primary: #000;
  --text-secondary: #666;
  --border-color: #aaa;
  --border-light: #ddd;
  --shadow: rgba(0, 0, 0, 0.1);
  --hover-bg: rgba(0, 0, 0, 0.05);
}

[data-theme="dark"] {
  --bg-primary: #1e1e1e;
  --bg-secondary: #252525;
  --text-primary: #e0e0e0;
  --text-secondary: #a0a0a0;
  --border-color: #404040;
  --border-light: #333;
  --shadow: rgba(0, 0, 0, 0.3);
  --hover-bg: rgba(255, 255, 255, 0.05);
}

:global html,
:global body,
:global #app {
  height: 100%;
  margin: 0;
  overflow: hidden;
  padding: 0;
  width: 100%;
  background: var(--bg-primary);
  color: var(--text-primary);
  transition:
    background-color 0.3s ease,
    color 0.3s ease;
}

:global body.resizing {
  user-select: none !important;
}

:global body.resizing * {
  pointer-events: none;
}

:global body.resizing.col {
  cursor: col-resize !important;
}


================================================
FILE: client/viewer.jsx
================================================
import { render } from "preact";

import ModulesTreemap from "./components/ModulesTreemap.jsx";
import { store } from "./store.js";

import "./viewer.css";

// Initializing WebSocket for live treemap updates
let ws;
try {
  if (globalThis.enableWebSocket) {
    ws = new WebSocket(`ws://${location.host}`);
  }
} catch {
  // eslint-disable-next-line no-console
  console.warn(
    "Couldn't connect to analyzer websocket server so you'll have to reload page manually to see updates in the treemap",
  );
}

window.addEventListener(
  "load",
  () => {
    store.defaultSize = `${globalThis.defaultSizes}Size`;
    store.setModules(globalThis.chartData);
    store.setEntrypoints(globalThis.entrypoints);
    store.updateTheme();
    render(<ModulesTreemap />, document.querySelector("#app"));

    if (ws) {
      ws.addEventListener("message", (event) => {
        const msg = JSON.parse(event.data);

        if (msg.event === "chartDataUpdated") {
          store.setModules(msg.data);
        }
      });
    }
  },
  false,
);


================================================
FILE: eslint.config.mjs
================================================
import { defineConfig, globalIgnores } from "eslint/config";
import config from "eslint-config-webpack";
import configs from "eslint-config-webpack/configs.js";

export default defineConfig([
  globalIgnores([
    // Ignore some test files
    "lib/**/*",
    "public/**/*",
    "test/src/**/*",
    "test/dev-server/**/*",
    "test/bundles/**/*",
    "test/stats/**/*",
    "test/output/**/*",
  ]),
  {
    ignores: ["client/**/*", "src/tree/**/*", "src/sizeUtils.js"],
    extends: [config],
    rules: {
      // We use babel so it will be applied by default
      strict: "off",
    },
  },
  {
    files: ["src/bin/**/*"],
    rules: {
      "no-console": "off",
      "n/hashbang": "off",
      "n/no-process-exit": "off",
      "unicorn/prefer-top-level-await": "off",
    },
  },
  {
    files: ["src/tree/**/*", "src/sizeUtils.js"],
    extends: [configs["node-recommended-module"]],
  },
  {
    files: ["client/**/*"],
    extends: [configs["browser-recommended"]],
    rules: {
      // TODO fix me in future
      "react/no-deprecated": "off",
    },
  },
]);


================================================
FILE: jest.config.js
================================================
"use strict";

// Jest configuration
// Reference: https://jestjs.io/docs/configuration

module.exports = {
  testTimeout: 15000,
  testMatch: ["**/test/*.js"],
  testPathIgnorePatterns: ["<rootDir>/test/helpers.js"],
  setupFilesAfterEnv: ["<rootDir>/test/helpers.js"],
  coveragePathIgnorePatterns: ["<rootDir>/test"],
  watchPathIgnorePatterns: [
    // Ignore the output generated by plugin tests
    // when watching for changes to avoid the test
    // runner continuously re-running tests
    "<rootDir>/test/output",
  ],
};


================================================
FILE: package.json
================================================
{
  "name": "webpack-bundle-analyzer",
  "version": "5.2.0",
  "description": "Webpack plugin and CLI utility that represents bundle content as convenient interactive zoomable treemap",
  "keywords": [
    "webpack",
    "bundle",
    "analyzer",
    "modules",
    "size",
    "interactive",
    "chart",
    "treemap",
    "zoomable",
    "zoom"
  ],
  "homepage": "https://github.com/webpack/webpack-bundle-analyzer",
  "bugs": {
    "url": "https://github.com/webpack/webpack-bundle-analyzer/issues"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/webpack/webpack-bundle-analyzer.git"
  },
  "license": "MIT",
  "author": "Yury Grunin <grunin.ya@ya.ru>",
  "main": "lib/index.js",
  "bin": "lib/bin/analyzer.js",
  "files": [
    "public",
    "lib"
  ],
  "scripts": {
    "clean:analyzer": "del-cli lib",
    "clean:viewer": "del-cli public",
    "clean": "npm run clean:analyzer && npm run clean:viewer",
    "build:analyzer": "npm run clean:analyzer && babel src -d lib --copy-files",
    "build:viewer": "npm run clean:viewer && webpack-cli --node-env=production",
    "build": "npm run build:analyzer && npm run build:viewer",
    "watch:analyzer": "npm run build:analyzer -- --watch",
    "watch:viewer": "npm run build:viewer -- --node-env=development --watch",
    "npm-publish": "npm run lint && npm run build && npm test && npm publish",
    "lint": "npm run lint:code && npm run lint:types && npm run fmt:check",
    "lint:code": "eslint --cache .",
    "lint:types": "tsc --pretty --noEmit",
    "fmt": "npm run fmt:base -- --log-level warn --write",
    "fmt:check": "npm run fmt:base -- --check",
    "fmt:base": "prettier --cache --ignore-unknown .",
    "fix": "npm run fix:code && npm run fmt",
    "test": "NODE_OPTIONS=--openssl-legacy-provider jest --runInBand",
    "test:coverage": "npm run test -- --coverage",
    "test-dev": "NODE_OPTIONS=--openssl-legacy-provider jest --watch --runInBand"
  },
  "dependencies": {
    "@discoveryjs/json-ext": "^0.6.3",
    "acorn": "^8.0.4",
    "acorn-walk": "^8.0.0",
    "commander": "^14.0.2",
    "escape-string-regexp": "^5.0.0",
    "html-escaper": "^3.0.3",
    "opener": "^1.5.2",
    "picocolors": "^1.0.0",
    "sirv": "^3.0.2",
    "ws": "^8.19.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.28.6",
    "@babel/core": "^7.26.9",
    "@babel/plugin-transform-class-properties": "^7.27.1",
    "@babel/plugin-transform-runtime": "^7.26.9",
    "@babel/preset-env": "^7.26.9",
    "@babel/preset-react": "^7.26.3",
    "@babel/runtime": "^7.26.9",
    "@carrotsearch/foamtree": "^3.5.0",
    "@types/html-escaper": "^3.0.4",
    "@types/opener": "^1.4.3",
    "autoprefixer": "^10.2.5",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^10.0.0",
    "classnames": "^2.3.1",
    "core-js": "^3.12.1",
    "css-loader": "^7.1.3",
    "cssnano": "^7.1.2",
    "debounce": "^3.0.0",
    "del-cli": "^7.0.0",
    "eslint": "^9.39.2",
    "eslint-config-webpack": "^4.9.3",
    "filesize": "^11.0.13",
    "jest": "^30.2.0",
    "mobx": "^6.15.0",
    "mobx-react": "^9.2.1",
    "postcss": "^8.3.0",
    "postcss-loader": "^8.2.0",
    "preact": "^10.5.13",
    "prettier": "^3.8.0",
    "prop-types": "^15.8.1",
    "puppeteer": "^24.30.0",
    "style-loader": "^4.0.0",
    "terser-webpack-plugin": "^5.1.2",
    "tinyglobby": "^0.2.15",
    "typescript": "^5.9.3",
    "webpack": "^5.105.2",
    "webpack-4": "npm:webpack@^4",
    "webpack-cli": "^6.0.1",
    "webpack-dev-server": "^5.2.0"
  },
  "packageManager": "npm@10.1.0",
  "engines": {
    "node": ">= 20.9.0"
  },
  "changelog": "https://github.com/webpack/webpack-bundle-analyzer/blob/main/CHANGELOG.md"
}


================================================
FILE: prettier.config.mjs
================================================
export default {
  printWidth: 80,
  tabWidth: 2,
  trailingComma: "all",
  arrowParens: "always",
};


================================================
FILE: src/BundleAnalyzerPlugin.js
================================================
const fs = require("node:fs");
const path = require("node:path");
const { bold } = require("picocolors");

const Logger = require("./Logger");
const { writeStats } = require("./statsUtils");
const utils = require("./utils");
const viewer = require("./viewer");

/** @typedef {import("net").AddressInfo} AddressInfo */
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").OutputFileSystem} OutputFileSystem */
/** @typedef {import("webpack").Stats} Stats */
/** @typedef {import("webpack").StatsOptions} StatsOptions */
/** @typedef {import("webpack").StatsAsset} StatsAsset */
/** @typedef {import("webpack").StatsCompilation} StatsCompilation */
/** @typedef {import("./sizeUtils").Algorithm} CompressionAlgorithm */
/** @typedef {import("./Logger").Level} LogLever */
/** @typedef {import("./viewer").ViewerServerObj} ViewerServerObj */

/** @typedef {string | boolean | StatsOptions} PluginStatsOptions */

// eslint-disable-next-line jsdoc/reject-any-type
/** @typedef {any} EXPECTED_ANY */

/** @typedef {"static" | "json" | "server" | "disabled"} Mode */
/** @typedef {string | RegExp | ((asset: string) => void)} Pattern */
/** @typedef {null | Pattern | Pattern[]} ExcludeAssets */
/** @typedef {"stat" | "parsed" | "gzip" | "brotli" | "zstd"} Sizes */
/** @typedef {string | (() => string)} ReportTitle */
/** @typedef {(options: { listenHost: string, listenPort: number, boundAddress: string | AddressInfo | null }) => string} AnalyzerUrl */

/**
 * @typedef {object} Options
 * @property {Mode=} analyzerMode analyzer mode
 * @property {string=} analyzerHost analyzer host
 * @property {"auto" | number=} analyzerPort analyzer port
 * @property {CompressionAlgorithm=} compressionAlgorithm compression algorithm
 * @property {string | null=} reportFilename report filename
 * @property {ReportTitle=} reportTitle report title
 * @property {Sizes=} defaultSizes default sizes
 * @property {boolean=} openAnalyzer open analyzer
 * @property {boolean=} generateStatsFile generate stats file
 * @property {string=} statsFilename stats filename
 * @property {PluginStatsOptions=} statsOptions stats options
 * @property {ExcludeAssets=} excludeAssets exclude assets
 * @property {LogLever=} logLevel exclude assets
 * @property {boolean=} startAnalyzer start analyzer
 * @property {AnalyzerUrl=} analyzerUrl start analyzer
 */

class BundleAnalyzerPlugin {
  /**
   * @param {Options=} opts options
   */
  constructor(opts = {}) {
    /** @type {Required<Omit<Options, "analyzerPort" | "statsOptions">> & { analyzerPort: number, statsOptions: undefined | PluginStatsOptions }} */
    this.opts = {
      analyzerMode: "server",
      analyzerHost: "127.0.0.1",
      compressionAlgorithm: "gzip",
      reportFilename: null,
      reportTitle: utils.defaultTitle,
      defaultSizes: "parsed",
      openAnalyzer: true,
      generateStatsFile: false,
      statsFilename: "stats.json",
      statsOptions: undefined,
      excludeAssets: null,
      logLevel: "info",
      // TODO deprecated
      startAnalyzer: true,
      analyzerUrl: utils.defaultAnalyzerUrl,
      ...opts,
      analyzerPort:
        opts.analyzerPort === "auto" ? 0 : (opts.analyzerPort ?? 8888),
    };

    /** @type {Compiler | null} */
    this.compiler = null;
    /** @type {Promise<ViewerServerObj> | null} */
    this.server = null;
    this.logger = new Logger(this.opts.logLevel);
  }

  /**
   * @param {Compiler} compiler compiler
   */
  apply(compiler) {
    this.compiler = compiler;

    /**
     * @param {Stats} stats stats
     * @param {(err?: Error) => void} callback callback
     */
    const done = (stats, callback) => {
      callback ||= () => {};

      /** @type {(() => Promise<void>)[]} */
      const actions = [];

      if (this.opts.generateStatsFile) {
        actions.push(() =>
          this.generateStatsFile(stats.toJson(this.opts.statsOptions)),
        );
      }

      // Handling deprecated `startAnalyzer` flag
      if (this.opts.analyzerMode === "server" && !this.opts.startAnalyzer) {
        this.opts.analyzerMode = "disabled";
      }

      if (this.opts.analyzerMode === "server") {
        actions.push(() => this.startAnalyzerServer(stats.toJson()));
      } else if (this.opts.analyzerMode === "static") {
        actions.push(() => this.generateStaticReport(stats.toJson()));
      } else if (this.opts.analyzerMode === "json") {
        actions.push(() => this.generateJSONReport(stats.toJson()));
      }

      if (actions.length) {
        // Making analyzer logs to be after all webpack logs in the console
        setImmediate(async () => {
          try {
            await Promise.all(actions.map((action) => action()));
            callback();
          } catch (err) {
            callback(/** @type {Error} */ (err));
          }
        });
      } else {
        callback();
      }
    };

    if (compiler.hooks) {
      compiler.hooks.done.tapAsync("webpack-bundle-analyzer", done);
    } else {
      // @ts-expect-error old webpack@4 API
      compiler.plugin("done", done);
    }
  }

  /**
   * @param {StatsCompilation} stats stats
   * @returns {Promise<void>}
   */
  async generateStatsFile(stats) {
    const statsFilepath = path.resolve(
      /** @type {Compiler} */
      (this.compiler).outputPath,
      this.opts.statsFilename,
    );
    await fs.promises.mkdir(path.dirname(statsFilepath), { recursive: true });

    try {
      await writeStats(stats, statsFilepath);

      this.logger.info(
        `${bold("Webpack Bundle Analyzer")} saved stats file to ${bold(statsFilepath)}`,
      );
    } catch (error) {
      this.logger.error(
        `${bold("Webpack Bundle Analyzer")} error saving stats file to ${bold(statsFilepath)}: ${error}`,
      );
    }
  }

  /**
   * @param {StatsCompilation} stats stats
   * @returns {Promise<void>}
   */
  async startAnalyzerServer(stats) {
    if (this.server) {
      (await this.server).updateChartData(stats);
    } else {
      this.server = viewer.startServer(stats, {
        openBrowser: this.opts.openAnalyzer,
        host: this.opts.analyzerHost,
        port: this.opts.analyzerPort,
        reportTitle: this.opts.reportTitle,
        compressionAlgorithm: this.opts.compressionAlgorithm,
        bundleDir: this.getBundleDirFromCompiler(),
        logger: this.logger,
        defaultSizes: this.opts.defaultSizes,
        excludeAssets: this.opts.excludeAssets,
        analyzerUrl: this.opts.analyzerUrl,
      });
    }
  }

  /**
   * @param {StatsCompilation} stats stats
   * @returns {Promise<void>}
   */
  async generateJSONReport(stats) {
    await viewer.generateJSONReport(stats, {
      reportFilename: path.resolve(
        /** @type {Compiler} */
        (this.compiler).outputPath,
        this.opts.reportFilename || "report.json",
      ),
      compressionAlgorithm: this.opts.compressionAlgorithm,
      bundleDir: this.getBundleDirFromCompiler(),
      logger: this.logger,
      excludeAssets: this.opts.excludeAssets,
    });
  }

  /**
   * @param {StatsCompilation} stats stats
   * @returns {Promise<void>}
   */
  async generateStaticReport(stats) {
    await viewer.generateReport(stats, {
      openBrowser: this.opts.openAnalyzer,
      reportFilename: path.resolve(
        /** @type {Compiler} */
        (this.compiler).outputPath,
        this.opts.reportFilename || "report.html",
      ),
      reportTitle: this.opts.reportTitle,
      compressionAlgorithm: this.opts.compressionAlgorithm,
      bundleDir: this.getBundleDirFromCompiler(),
      logger: this.logger,
      defaultSizes: this.opts.defaultSizes,
      excludeAssets: this.opts.excludeAssets,
    });
  }

  getBundleDirFromCompiler() {
    const outputFileSystemConstructor =
      /** @type {OutputFileSystem} */
      (/** @type {Compiler} */ (this.compiler).outputFileSystem).constructor;

    if (typeof outputFileSystemConstructor === "undefined") {
      return /** @type {Compiler} */ (this.compiler).outputPath;
    }
    switch (outputFileSystemConstructor.name) {
      case "MemoryFileSystem":
        return null;
      // Detect AsyncMFS used by Nuxt 2.5 that replaces webpack's MFS during development
      // Related: #274
      case "AsyncMFS":
        return null;
      default:
        return /** @type {Compiler} */ (this.compiler).outputPath;
    }
  }
}

module.exports = BundleAnalyzerPlugin;


================================================
FILE: src/Logger.js
================================================
/** @typedef {import("./BundleAnalyzerPlugin").EXPECTED_ANY} EXPECTED_ANY */

/** @typedef {"debug" | "info" | "warn" | "error" | "silent"} Level */

/** @type {Level[]} */
const LEVELS = ["debug", "info", "warn", "error", "silent"];

/** @type {Map<Level, string>} */
const LEVEL_TO_CONSOLE_METHOD = new Map([
  ["debug", "log"],
  ["info", "log"],
  ["warn", "log"],
]);

class Logger {
  /** @type {Level[]} */
  static levels = LEVELS;

  /** @type {Level} */
  static defaultLevel = "info";

  /**
   * @param {Level=} level level
   */
  constructor(level = Logger.defaultLevel) {
    /** @type {Set<Level>} */
    this.activeLevels = new Set();
    this.setLogLevel(level);
  }

  /**
   * @param {Level} level level
   */
  setLogLevel(level) {
    const levelIndex = LEVELS.indexOf(level);

    if (levelIndex === -1) {
      throw new Error(
        `Invalid log level "${level}". Use one of these: ${LEVELS.join(", ")}`,
      );
    }

    this.activeLevels.clear();

    for (const [i, level] of LEVELS.entries()) {
      if (i >= levelIndex) this.activeLevels.add(level);
    }
  }

  /**
   * @template {EXPECTED_ANY[]} T
   * @param {T} args args
   */
  debug(...args) {
    if (!this.activeLevels.has("debug")) return;
    this._log("debug", ...args);
  }

  /**
   * @template {EXPECTED_ANY[]} T
   * @param {T} args args
   */
  info(...args) {
    if (!this.activeLevels.has("info")) return;
    this._log("info", ...args);
  }

  /**
   * @template {EXPECTED_ANY[]} T
   * @param {T} args args
   */
  error(...args) {
    if (!this.activeLevels.has("error")) return;
    this._log("error", ...args);
  }

  /**
   * @template {EXPECTED_ANY[]} T
   * @param {T} args args
   */
  warn(...args) {
    if (!this.activeLevels.has("warn")) return;
    this._log("warn", ...args);
  }

  /**
   * @template {EXPECTED_ANY[]} T
   * @param {Level} level level
   * @param {T} args args
   */
  _log(level, ...args) {
    // eslint-disable-next-line no-console
    console[
      /** @type {Exclude<Level, "silent">} */
      (LEVEL_TO_CONSOLE_METHOD.get(level) || level)
    ](...args);
  }
}

module.exports = Logger;


================================================
FILE: src/analyzer.js
================================================
const fs = require("node:fs");
const path = require("node:path");

const { parseChunked } = require("@discoveryjs/json-ext");

const Logger = require("./Logger");
const { parseBundle } = require("./parseUtils");
const { getCompressedSize } = require("./sizeUtils");
const Folder = require("./tree/Folder").default;
const { createAssetsFilter } = require("./utils");

const FILENAME_QUERY_REGEXP = /\?.*$/u;
const FILENAME_EXTENSIONS = /\.(js|mjs|cjs|bundle)$/iu;

/** @typedef {import("webpack").StatsCompilation} StatsCompilation */
/** @typedef {import("webpack").StatsModule} StatsModule */
/** @typedef {import("webpack").StatsAsset} StatsAsset */
/** @typedef {import("./BundleAnalyzerPlugin").CompressionAlgorithm} CompressionAlgorithm */
/** @typedef {import("./BundleAnalyzerPlugin").ExcludeAssets} ExcludeAssets */

/**
 * @typedef {object} AnalyzerOptions
 * @property {"gzip" | "brotli" | "zstd"} compressionAlgorithm compression algorithm
 */

/**
 * @param {StatsModule[]} modules modules
 * @param {AnalyzerOptions} options options
 * @returns {Folder} a folder class
 */
function createModulesTree(modules, options) {
  const root = new Folder(".", options);

  for (const module of modules) {
    root.addModule(module);
  }

  root.mergeNestedFolders();

  return root;
}

/**
 * arr-flatten <https://github.com/jonschlinkert/arr-flatten>
 *
 * Copyright (c) 2014-2017, Jon Schlinkert.
 * Released under the MIT License.
 *
 * Modified by Sukka <https://skk.moe>
 *
 * Replace recursively flatten with one-level deep flatten to match lodash.flatten
 *
 * TODO: replace with Array.prototype.flat once Node.js 10 support is dropped
 */
/**
 * Flattens an array by one level.
 * @template T
 * @param {(T | T[])[]} arr the array to flatten
 * @returns {T[]} a new array containing the flattened elements
 */
function flatten(arr) {
  if (!arr) return [];
  const len = arr.length;
  if (!len) return [];

  let cur;

  const res = [];
  for (let i = 0; i < len; i++) {
    cur = arr[i];
    if (Array.isArray(cur)) {
      res.push(...cur);
    } else {
      res.push(cur);
    }
  }
  return res;
}

/**
 * @param {StatsCompilation} bundleStats bundle stats
 * @param {string} assetName asset name
 * @returns {boolean} child asset bundlers
 */
function getChildAssetBundles(bundleStats, assetName) {
  return flatten(
    (bundleStats.children || /** @type {StatsCompilation} */ ([])).find(
      /**
       * @param {StatsCompilation} child child stats
       * @returns {string[][]} assets by chunk name
       */
      (child) => Object.values(child.assetsByChunkName || []),
    ),
  ).includes(assetName);
}

/**
 * @param {StatsAsset} statsAsset stats asset
 * @param {StatsModule} statsModule stats modules
 * @returns {boolean} true when asset has a module
 */
function assetHasModule(statsAsset, statsModule) {
  // Checking if this module is the part of asset chunks
  return (statsModule.chunks || []).some(
    (moduleChunk) =>
      statsAsset.chunks && statsAsset.chunks.includes(moduleChunk),
  );
}

/**
 * @param {StatsModule} statsModule stats Module
 * @returns {boolean} true when runtime modules, otherwise false
 */
function isRuntimeModule(statsModule) {
  return statsModule.moduleType === "runtime";
}

/**
 * @param {StatsCompilation} bundleStats bundle stats
 * @returns {StatsModule[]} modules
 */
function getBundleModules(bundleStats) {
  /** @type {Set<string | number>} */
  const seenIds = new Set();
  const modules = /** @type {StatsModule[]} */ ([
    ...(bundleStats.chunks?.map((chunk) => chunk.modules) || []),
    ...(bundleStats.modules || []),
  ]).filter(Boolean);

  return flatten(modules).filter((mod) => {
    // Filtering out Webpack's runtime modules as they don't have ids and can't be parsed (introduced in Webpack 5)
    if (isRuntimeModule(mod)) {
      return false;
    }

    if (seenIds.has(mod.id)) {
      return false;
    }

    seenIds.add(mod.id);

    return true;
  });
}

/** @typedef {Record<string, Record<string, boolean>>} ChunkToInitialByEntrypoint */

/**
 * @param {StatsCompilation} bundleStats bundle stats
 * @returns {ChunkToInitialByEntrypoint} chunk to initial by entrypoint
 */
function getChunkToInitialByEntrypoint(bundleStats) {
  if (bundleStats === null || bundleStats === undefined) {
    return {};
  }
  /** @type {ChunkToInitialByEntrypoint} */
  const chunkToEntrypointInititalMap = {};
  for (const entrypoint of Object.values(bundleStats.entrypoints || {})) {
    for (const asset of entrypoint.assets || []) {
      chunkToEntrypointInititalMap[asset.name] ??= {};
      chunkToEntrypointInititalMap[asset.name][
        /** @type {string} */
        (entrypoint.name)
      ] = true;
    }
  }
  return chunkToEntrypointInititalMap;
}

/**
 * @param {StatsModule} statsModule stats modules
 * @returns {boolean} true when entry module, otherwise false
 */
function isEntryModule(statsModule) {
  return statsModule.depth === 0;
}

/**
 * @typedef {object} ViewerDataOptions
 * @property {Logger} logger logger
 * @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
 * @property {ExcludeAssets} excludeAssets exclude assets
 */

/** @typedef {import("./tree/Module").ModuleChartData} ModuleChartData */
/** @typedef {import("./tree/ContentModule").ContentModuleChartData} ContentModuleChartData */
/** @typedef {import("./tree/ConcatenatedModule").ConcatenatedModuleChartData} ConcatenatedModuleChartData */
/** @typedef {import("./tree/ContentFolder").ContentFolderChartData} ContentFolderChartData */
/** @typedef {import("./tree/Folder").FolderChartData} FolderChartData */

/**
 * @typedef {object} ChartDataItem
 * @property {string} label label
 * @property {true} isAsset true when is asset, otherwise false
 * @property {number} statSize stat size
 * @property {number | undefined} parsedSize stat size
 * @property {number | undefined} gzipSize gzip size
 * @property {number | undefined} brotliSize brotli size
 * @property {number | undefined} zstdSize zstd size
 * @property {(ModuleChartData | ContentModuleChartData | ConcatenatedModuleChartData | ContentFolderChartData | FolderChartData)[]} groups groups
 * @property {Record<string, boolean>} isInitialByEntrypoint record with initial entrypoints
 */

/**
 * @typedef {ChartDataItem[]} ChartData
 */

/**
 * @param {StatsCompilation} bundleStats bundle stats
 * @param {string | null} bundleDir bundle dir
 * @param {ViewerDataOptions=} opts options
 * @returns {ChartData} chart data
 */
function getViewerData(bundleStats, bundleDir, opts) {
  const {
    logger = new Logger(),
    compressionAlgorithm = "gzip",
    excludeAssets = null,
  } = opts || {};

  const isAssetIncluded = createAssetsFilter(excludeAssets);

  // Sometimes all the information is located in `children` array (e.g. problem in #10)
  if (
    (bundleStats.assets === null ||
      bundleStats.assets === undefined ||
      bundleStats.assets.length === 0) &&
    bundleStats.children &&
    bundleStats.children.length > 0
  ) {
    const { children } = bundleStats;
    [bundleStats] = bundleStats.children;
    // Sometimes if there are additional child chunks produced add them as child assets,
    // leave the 1st one as that is considered the 'root' asset.
    for (let i = 1; i < children.length; i++) {
      for (const asset of children[i].assets || []) {
        asset.isChild = true;
        /** @type {StatsAsset[]} */
        (bundleStats.assets).push(asset);
      }
    }
  } else if (bundleStats.children && bundleStats.children.length > 0) {
    // Sometimes if there are additional child chunks produced add them as child assets
    for (const child of bundleStats.children) {
      for (const asset of child.assets || []) {
        asset.isChild = true;
        /** @type {StatsAsset[]} */
        (bundleStats.assets).push(asset);
      }
    }
  }

  // Picking only `*.js, *.cjs or *.mjs` assets from bundle that has non-empty `chunks` array
  bundleStats.assets = (bundleStats.assets || []).filter((asset) => {
    // Filter out non 'asset' type asset if type is provided (Webpack 5 add a type to indicate asset types)
    if (asset.type && asset.type !== "asset") {
      return false;
    }

    // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
    // See #22
    asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, "");

    return (
      FILENAME_EXTENSIONS.test(asset.name) &&
      asset.chunks &&
      asset.chunks.length > 0 &&
      isAssetIncluded(asset.name)
    );
  });

  // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided
  /** @type {Record<string, { src: string, runtimeSrc: string }> | null} */
  let bundlesSources = null;
  /** @type {Record<string | number, boolean> | null} */
  let parsedModules = null;

  if (bundleDir) {
    bundlesSources = {};
    parsedModules = {};

    for (const statAsset of bundleStats.assets) {
      const assetFile = path.join(bundleDir, statAsset.name);
      let bundleInfo;

      try {
        bundleInfo = parseBundle(assetFile, {
          sourceType: statAsset.info.javascriptModule ? "module" : "script",
        });
      } catch (err) {
        const msg =
          /** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT"
            ? "no such file"
            : /** @type {Error} */ (err).message;
        logger.warn(`Error parsing bundle asset "${assetFile}": ${msg}`, {
          cause: err,
        });
        continue;
      }

      bundlesSources[statAsset.name] = {
        src: bundleInfo.src,
        runtimeSrc: bundleInfo.runtimeSrc,
      };
      Object.assign(parsedModules, bundleInfo.modules);
    }

    if (Object.keys(bundlesSources).length === 0) {
      bundlesSources = null;
      parsedModules = null;
      logger.warn(
        "\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n",
      );
    }
  }

  /** @typedef {{ size: number, parsedSize?: number, gzipSize?: number, brotliSize?: number, zstdSize?: number, modules: StatsModule[], tree: Folder }} Asset */

  const assets = bundleStats.assets.reduce((result, statAsset) => {
    // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children
    const assetBundles = statAsset.isChild
      ? getChildAssetBundles(bundleStats, statAsset.name)
      : bundleStats;
    /** @type {StatsModule[]} */
    const modules = assetBundles
      ? // @ts-expect-error TODO looks like we have a bug with child compilation parsing, need to add test cases
        getBundleModules(assetBundles)
      : [];
    const asset = (result[statAsset.name] = /** @type {Asset} */ ({
      size: statAsset.size,
    }));
    const assetSources =
      bundlesSources && Object.hasOwn(bundlesSources, statAsset.name)
        ? bundlesSources[statAsset.name]
        : null;

    if (assetSources) {
      asset.parsedSize = Buffer.byteLength(assetSources.src);

      if (compressionAlgorithm === "gzip") {
        asset.gzipSize = getCompressedSize("gzip", assetSources.src);
      }

      if (compressionAlgorithm === "brotli") {
        asset.brotliSize = getCompressedSize("brotli", assetSources.src);
      }

      if (compressionAlgorithm === "zstd") {
        asset.zstdSize = getCompressedSize("zstd", assetSources.src);
      }
    }

    // Picking modules from current bundle script
    /** @type {StatsModule[]} */
    let assetModules = (modules || []).filter((statModule) =>
      assetHasModule(statAsset, statModule),
    );

    // Adding parsed sources
    if (parsedModules) {
      /** @type {StatsModule[]} */
      const unparsedEntryModules = [];

      for (const statsModule of assetModules) {
        if (
          typeof statsModule.id !== "undefined" &&
          parsedModules[statsModule.id]
        ) {
          statsModule.parsedSrc = parsedModules[statsModule.id];
        } else if (isEntryModule(statsModule)) {
          unparsedEntryModules.push(statsModule);
        }
      }

      // Webpack 5 changed bundle format and now entry modules are concatenated and located at the end of it.
      // Because of this they basically become a concatenated module, for which we can't even precisely determine its
      // parsed source as it's located in the same scope as all Webpack runtime helpers.
      if (unparsedEntryModules.length && assetSources) {
        if (unparsedEntryModules.length === 1) {
          // So if there is only one entry we consider its parsed source to be all the bundle code excluding code
          // from parsed modules.
          unparsedEntryModules[0].parsedSrc = assetSources.runtimeSrc;
        } else {
          // If there are multiple entry points we move all of them under synthetic concatenated module.
          assetModules = (assetModules || []).filter(
            (mod) => !unparsedEntryModules.includes(mod),
          );
          assetModules.unshift({
            identifier: "./entry modules",
            name: "./entry modules",
            modules: unparsedEntryModules,
            size: unparsedEntryModules.reduce(
              (totalSize, module) =>
                totalSize + /** @type {number} */ (module.size),
              0,
            ),
            parsedSrc: assetSources.runtimeSrc,
          });
        }
      }
    }

    asset.modules = assetModules;
    asset.tree = createModulesTree(asset.modules, { compressionAlgorithm });

    return result;
  }, /** @type {Record<string, Asset>} */ ({}));

  const chunkToInitialByEntrypoint = getChunkToInitialByEntrypoint(bundleStats);

  return Object.entries(assets).map(([filename, asset]) => ({
    label: filename,
    isAsset: true,
    // Not using `asset.size` here provided by Webpack because it can be very confusing when `UglifyJsPlugin` is used.
    // In this case all module sizes from stats file will represent unminified module sizes, but `asset.size` will
    // be the size of minified bundle.
    // Using `asset.size` only if current asset doesn't contain any modules (resulting size equals 0)
    statSize: asset.tree.size || asset.size,
    parsedSize: asset.parsedSize,
    gzipSize: asset.gzipSize,
    brotliSize: asset.brotliSize,
    zstdSize: asset.zstdSize,
    groups: Object.values(asset.tree.children).map((i) => i.toChartData()),
    isInitialByEntrypoint: chunkToInitialByEntrypoint[filename] ?? {},
  }));
}

/**
 * @param {string} filename filename
 * @returns {Promise<StatsCompilation>} result
 */
function readStatsFromFile(filename) {
  return parseChunked(fs.createReadStream(filename, { encoding: "utf8" }));
}

module.exports = {
  getViewerData,
  readStatsFromFile,
};


================================================
FILE: src/bin/analyzer.js
================================================
#! /usr/bin/env node

const { dirname, resolve } = require("node:path");

const { program: commanderProgram } = require("commander");
const { magenta } = require("picocolors");

const Logger = require("../Logger");
const analyzer = require("../analyzer");
const { isZstdSupported } = require("../sizeUtils");
const utils = require("../utils");
const viewer = require("../viewer");

const SIZES = new Set(["stat", "parsed", "gzip"]);
const COMPRESSION_ALGORITHMS = new Set(
  isZstdSupported ? ["gzip", "brotli", "zstd"] : ["gzip", "brotli"],
);

/**
 * @param {string} str string
 * @returns {string} break with string
 */
function br(str) {
  return `\n${" ".repeat(32)}${str}`;
}

/**
 * @template T
 * @returns {(val: T) => T[]} array
 */
function array() {
  /** @type {T[]} */
  const arr = [];
  return (val) => {
    arr.push(val);
    return arr;
  };
}

const program = commanderProgram
  .version(require("../../package.json").version)
  .argument("<bundleStatsFile>", "Path to Webpack Stats JSON file.")
  .argument(
    "[bundleDir]",
    "Directory containing all generated bundles. You should provided it if you want analyzer to show you the real parsed module sizes. By default a directory of stats file is used.",
  )
  .option(
    "-m, --mode <mode>",
    `Analyzer mode. Should be \`server\`,\`static\` or \`json\`.${br(
      "In `server` mode analyzer will start HTTP server to show bundle report.",
    )}${br(
      "In `static` mode single HTML file with bundle report will be generated.",
    )}${br(
      "In `json` mode single JSON file with bundle report will be generated.",
    )}`,
    "server",
  )
  .option(
    // Had to make `host` parameter optional in order to let `-h` flag output help message
    // Fixes https://github.com/webpack/webpack-bundle-analyzer/issues/239
    "-h, --host [host]",
    "Host that will be used in `server` mode to start HTTP server.",
    "127.0.0.1",
  )
  .option(
    "-p, --port <n>",
    "Port that will be used in `server` mode to start HTTP server.",
    "8888",
  )
  .option(
    "-r, --report <file>",
    "Path to bundle report file that will be generated in `static` mode.",
  )
  .option(
    "-t, --title <title>",
    "String to use in title element of html report.",
  )
  .option(
    "-s, --default-sizes <type>",
    `Module sizes to show in treemap by default.${br(
      `Possible values: ${[...SIZES].join(", ")}`,
    )}`,
    "parsed",
  )
  .option(
    "--compression-algorithm <type>",
    `Compression algorithm that will be used to calculate the compressed module sizes.${br(
      `Possible values: ${[...COMPRESSION_ALGORITHMS].join(", ")}`,
    )}`,
    "gzip",
  )
  .option(
    "-O, --no-open",
    "Don't open report in default browser automatically.",
  )
  .option(
    "-e, --exclude <regexp>",
    `Assets that should be excluded from the report.${br(
      "Can be specified multiple times.",
    )}`,
    array(),
  )
  .option(
    "-l, --log-level <level>",
    `Log level.${br(`Possible values: ${[...Logger.levels].join(", ")}`)}`,
    Logger.defaultLevel,
  )
  .parse();

let [bundleStatsFile, bundleDir] = program.args;
let {
  mode,
  host,
  port,
  report: reportFilename,
  title: reportTitle,
  defaultSizes,
  compressionAlgorithm,
  logLevel,
  open: openBrowser,
  exclude: excludeAssets,
} = program.opts();
const logger = new Logger(logLevel);

if (typeof reportTitle === "undefined") {
  reportTitle = utils.defaultTitle;
}

/**
 * @param {string} error error message
 */
function showHelp(error) {
  if (error) console.log(`\n  ${magenta(error)}\n`);
  program.outputHelp();
  process.exit(1);
}

if (!bundleStatsFile) {
  showHelp("Provide path to Webpack Stats file as first argument");
}
if (mode !== "server" && mode !== "static" && mode !== "json") {
  showHelp("Invalid mode. Should be either `server`, `static` or `json`.");
}
if (mode === "server") {
  if (!host) showHelp("Invalid host name");

  port = port === "auto" ? 0 : Number(port);
  if (Number.isNaN(port)) {
    showHelp("Invalid port. Should be a number or `auto`");
  }
}
if (!COMPRESSION_ALGORITHMS.has(compressionAlgorithm)) {
  showHelp(
    `Invalid compression algorithm option. Possible values are: ${[...COMPRESSION_ALGORITHMS].join(", ")}`,
  );
}
if (!SIZES.has(defaultSizes)) {
  showHelp(
    `Invalid default sizes option. Possible values are: ${[...SIZES].join(", ")}`,
  );
}

bundleStatsFile = resolve(bundleStatsFile);

if (!bundleDir) bundleDir = dirname(bundleStatsFile);

/**
 * @param {string} bundleStatsFile bundle stats file
 * @returns {Promise<void>}
 */
async function parseAndAnalyse(bundleStatsFile) {
  try {
    const bundleStats = await analyzer.readStatsFromFile(bundleStatsFile);
    if (mode === "server") {
      viewer.startServer(bundleStats, {
        openBrowser,
        port,
        host,
        defaultSizes,
        compressionAlgorithm,
        reportTitle,
        bundleDir,
        excludeAssets,
        logger: new Logger(logLevel),
        analyzerUrl: utils.defaultAnalyzerUrl,
      });
    } else if (mode === "static") {
      viewer.generateReport(bundleStats, {
        openBrowser,
        reportFilename: resolve(reportFilename || "report.html"),
        reportTitle,
        defaultSizes,
        compressionAlgorithm,
        bundleDir,
        excludeAssets,
        logger: new Logger(logLevel),
      });
    } else if (mode === "json") {
      viewer.generateJSONReport(bundleStats, {
        reportFilename: resolve(reportFilename || "report.json"),
        compressionAlgorithm,
        bundleDir,
        excludeAssets,
        logger: new Logger(logLevel),
      });
    }
  } catch (err) {
    logger.error(
      `Couldn't read webpack bundle stats from "${bundleStatsFile}":\n${err}`,
    );
    logger.debug(/** @type {Error} */ (err).stack);
    process.exit(1);
  }
}

parseAndAnalyse(bundleStatsFile);


================================================
FILE: src/index.js
================================================
const { start } = require("./viewer");

module.exports = {
  start,
  BundleAnalyzerPlugin: require("./BundleAnalyzerPlugin"),
};


================================================
FILE: src/parseUtils.js
================================================
/** @typedef {import("acorn").Node} Node */
/** @typedef {import("acorn").CallExpression} CallExpression */
/** @typedef {import("acorn").ExpressionStatement} ExpressionStatement */
/** @typedef {import("acorn").Expression} Expression */
/** @typedef {import("acorn").SpreadElement} SpreadElement */

const fs = require("node:fs");
const acorn = require("acorn");
const walk = require("acorn-walk");

/**
 * @param {Expression} node node
 * @returns {boolean} true when id is numeric, otherwise false
 */
function isNumericId(node) {
  return (
    node.type === "Literal" &&
    node.value !== null &&
    node.value !== undefined &&
    Number.isInteger(node.value) &&
    /** @type {number} */ (node.value) >= 0
  );
}

/**
 * @param {Expression | SpreadElement | null} node node
 * @returns {boolean} true when module id, otherwise false
 */
function isModuleId(node) {
  return (
    node !== null &&
    node.type === "Literal" &&
    (isNumericId(node) || typeof node.value === "string")
  );
}

/**
 * @param {Expression | SpreadElement} node node
 * @returns {boolean} true when module wrapper, otherwise false
 */
function isModuleWrapper(node) {
  return (
    // It's an anonymous function expression that wraps module
    ((node.type === "FunctionExpression" ||
      node.type === "ArrowFunctionExpression") &&
      !node.id) ||
    // If `DedupePlugin` is used it can be an ID of duplicated module...
    isModuleId(node) ||
    // or an array of shape [<module_id>, ...args]
    (node.type === "ArrayExpression" &&
      node.elements.length > 1 &&
      isModuleId(node.elements[0]))
  );
}

/**
 * @param {Expression | SpreadElement | null} node node
 * @returns {boolean} true when module hash, otherwise false
 */
function isModulesHash(node) {
  return (
    node !== null &&
    node.type === "ObjectExpression" &&
    node.properties
      .filter((property) => property.type !== "SpreadElement")
      .map((node) => node.value)
      .every(isModuleWrapper)
  );
}

/**
 * @param {Expression | SpreadElement | null} node node
 * @returns {boolean} true when module array, otherwise false
 */
function isModulesArray(node) {
  return (
    node !== null &&
    node.type === "ArrayExpression" &&
    node.elements.every(
      (elem) =>
        // Some of array items may be skipped because there is no module with such id
        !elem || isModuleWrapper(elem),
    )
  );
}

/**
 * @param {Expression | SpreadElement | null} node node
 * @returns {boolean} true when simple modules list, otherwise false
 */
function isSimpleModulesList(node) {
  return (
    // Modules are contained in hash. Keys are module ids.
    isModulesHash(node) ||
    // Modules are contained in array. Indexes are module ids.
    isModulesArray(node)
  );
}

/**
 * @param {Expression | SpreadElement | null} node node
 * @returns {boolean} true when optimized modules array, otherwise false
 */
function isOptimizedModulesArray(node) {
  // Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
  // https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
  // The `<minimum ID>` + array indexes are module ids
  return (
    node !== null &&
    node.type === "CallExpression" &&
    node.callee.type === "MemberExpression" &&
    // Make sure the object called is `Array(<some number>)`
    node.callee.object.type === "CallExpression" &&
    node.callee.object.callee.type === "Identifier" &&
    node.callee.object.callee.name === "Array" &&
    node.callee.object.arguments.length === 1 &&
    node.callee.object.arguments[0].type !== "SpreadElement" &&
    isNumericId(node.callee.object.arguments[0]) &&
    // Make sure the property X called for `Array(<some number>).X` is `concat`
    node.callee.property.type === "Identifier" &&
    node.callee.property.name === "concat" &&
    // Make sure exactly one array is passed in to `concat`
    node.arguments.length === 1 &&
    isModulesArray(node.arguments[0])
  );
}

/**
 * @param {Expression | SpreadElement | null} node node
 * @returns {boolean} true when modules list, otherwise false
 */
function isModulesList(node) {
  return (
    isSimpleModulesList(node) ||
    // Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
    isOptimizedModulesArray(node)
  );
}

/** @typedef {{ start: number, end: number }} Location */

/**
 * @param {Node} node node
 * @returns {Location} location
 */
function getModuleLocation(node) {
  return {
    start: node.start,
    end: node.end,
  };
}

/** @typedef {Record<number, Location>} ModulesLocations */

/**
 * @param {Expression | SpreadElement} node node
 * @returns {ModulesLocations} modules locations
 */
function getModulesLocations(node) {
  if (node.type === "ObjectExpression") {
    // Modules hash
    const modulesNodes = node.properties;

    return modulesNodes.reduce((result, moduleNode) => {
      if (moduleNode.type !== "Property") {
        return result;
      }

      const moduleId =
        moduleNode.key.type === "Identifier"
          ? moduleNode.key.name
          : // @ts-expect-error need verify why we need it, tests not cover it case
            moduleNode.key.value;

      if (moduleId === "undefined") {
        return result;
      }

      result[moduleId] = getModuleLocation(moduleNode.value);

      return result;
    }, /** @type {ModulesLocations} */ ({}));
  }

  const isOptimizedArray = node.type === "CallExpression";

  if (node.type === "ArrayExpression" || isOptimizedArray) {
    // Modules array or optimized array
    const minId =
      isOptimizedArray &&
      node.callee.type === "MemberExpression" &&
      node.callee.object.type === "CallExpression" &&
      node.callee.object.arguments[0].type === "Literal"
        ? // Get the [minId] value from the Array() call first argument literal value
          /** @type {number} */ (node.callee.object.arguments[0].value)
        : // `0` for simple array
          0;
    const modulesNodes = isOptimizedArray
      ? // The modules reside in the `concat()` function call arguments
        node.arguments[0].type === "ArrayExpression"
        ? node.arguments[0].elements
        : []
      : node.elements;

    return modulesNodes.reduce((result, moduleNode, i) => {
      if (moduleNode) {
        result[i + minId] = getModuleLocation(moduleNode);
      }

      return result;
    }, /** @type {ModulesLocations} */ ({}));
  }

  return {};
}

/**
 * @param {ExpressionStatement} node node
 * @returns {boolean} true when IIFE, otherwise false
 */
function isIIFE(node) {
  return (
    node.type === "ExpressionStatement" &&
    (node.expression.type === "CallExpression" ||
      (node.expression.type === "UnaryExpression" &&
        node.expression.argument.type === "CallExpression"))
  );
}

/**
 * @param {ExpressionStatement} node node
 * @returns {Expression} IIFE call expression
 */
function getIIFECallExpression(node) {
  if (node.expression.type === "UnaryExpression") {
    return node.expression.argument;
  }

  return node.expression;
}

/**
 * @param {Expression} node node
 * @returns {boolean} true when chunks ids, otherwose false
 */
function isChunkIds(node) {
  // Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
  return node.type === "ArrayExpression" && node.elements.every(isModuleId);
}

/**
 * @param {(Expression | SpreadElement | null)[]} args arguments
 * @returns {boolean} true when async chunk arguments, otherwise false
 */
function mayBeAsyncChunkArguments(args) {
  return (
    args.length >= 2 &&
    args[0] !== null &&
    args[0].type !== "SpreadElement" &&
    isChunkIds(args[0])
  );
}

/**
 * Returns bundle source except modules
 * @param {string} content content
 * @param {ModulesLocations | null} modulesLocations modules locations
 * @returns {string} runtime code
 */
function getBundleRuntime(content, modulesLocations) {
  const sortedLocations = Object.values(modulesLocations || {}).toSorted(
    (a, b) => a.start - b.start,
  );

  let result = "";
  let lastIndex = 0;

  for (const { start, end } of sortedLocations) {
    result += content.slice(lastIndex, start);
    lastIndex = end;
  }

  return result + content.slice(lastIndex);
}

/**
 * @param {CallExpression} node node
 * @returns {boolean} true when is async chunk push expression, otheriwse false
 */
function isAsyncChunkPushExpression(node) {
  const { callee, arguments: args } = node;

  return (
    callee.type === "MemberExpression" &&
    callee.property.type === "Identifier" &&
    callee.property.name === "push" &&
    callee.object.type === "AssignmentExpression" &&
    args.length === 1 &&
    args[0].type === "ArrayExpression" &&
    mayBeAsyncChunkArguments(args[0].elements) &&
    isModulesList(args[0].elements[1])
  );
}

/**
 * @param {CallExpression} node node
 * @returns {boolean} true when is async web worker, otherwise false
 */
function isAsyncWebWorkerChunkExpression(node) {
  const { callee, type, arguments: args } = node;

  return (
    type === "CallExpression" &&
    callee.type === "MemberExpression" &&
    args.length === 2 &&
    args[0].type !== "SpreadElement" &&
    isChunkIds(args[0]) &&
    isModulesList(args[1])
  );
}

/** @typedef {Record<string, string>} Modules */

/**
 * @param {string} bundlePath bundle path
 * @param {{ sourceType: "script" | "module" }} opts options
 * @returns {{ modules: Modules, src: string, runtimeSrc: string }} parsed result
 */
module.exports.parseBundle = function parseBundle(bundlePath, opts) {
  const { sourceType = "script" } = opts || {};

  const content = fs.readFileSync(bundlePath, "utf8");
  const ast = acorn.parse(content, {
    sourceType,
    ecmaVersion: "latest",
  });

  /** @type {{ locations: ModulesLocations | null, expressionStatementDepth: number }} */
  const walkState = {
    locations: null,
    expressionStatementDepth: 0,
  };

  walk.recursive(ast, walkState, {
    ExpressionStatement(node, state, callback) {
      if (state.locations) return;

      state.expressionStatementDepth++;

      if (
        // Webpack 5 stores modules in the the top-level IIFE
        state.expressionStatementDepth === 1 &&
        ast.body.includes(node) &&
        isIIFE(node)
      ) {
        const fn = getIIFECallExpression(node);

        if (
          fn.type === "CallExpression" &&
          // It should not contain neither arguments
          fn.arguments.length === 0 &&
          (fn.callee.type === "FunctionExpression" ||
            fn.callee.type === "ArrowFunctionExpression") &&
          // ...nor parameters
          fn.callee.params.length === 0 &&
          fn.callee.body.type === "BlockStatement"
        ) {
          // Modules are stored in the very first variable declaration as hash
          const firstVariableDeclaration = fn.callee.body.body.find(
            (node) => node.type === "VariableDeclaration",
          );

          if (firstVariableDeclaration) {
            for (const declaration of firstVariableDeclaration.declarations) {
              if (declaration.init && isModulesList(declaration.init)) {
                state.locations = getModulesLocations(declaration.init);

                if (state.locations) {
                  break;
                }
              }
            }
          }
        }
      }

      if (!state.locations) {
        callback(node.expression, state);
      }

      state.expressionStatementDepth--;
    },

    AssignmentExpression(node, state) {
      if (state.locations) return;

      // Modules are stored in exports.modules:
      // exports.modules = {};
      const { left, right } = node;

      if (
        left &&
        left.type === "MemberExpression" &&
        left.object &&
        left.object.type === "Identifier" &&
        left.object.name === "exports" &&
        left.property &&
        left.property.type === "Identifier" &&
        left.property.name === "modules" &&
        isModulesHash(right)
      ) {
        state.locations = getModulesLocations(right);
      }
    },

    CallExpression(node, state, callback) {
      if (state.locations) return;

      const args = node.arguments;

      // Main chunk with webpack loader.
      // Modules are stored in first argument:
      // (function (...) {...})(<modules>)
      if (
        node.callee.type === "FunctionExpression" &&
        !node.callee.id &&
        args.length === 1 &&
        isSimpleModulesList(args[0])
      ) {
        state.locations = getModulesLocations(args[0]);
        return;
      }

      // Async Webpack < v4 chunk without webpack loader.
      // webpackJsonp([<chunks>], <modules>, ...)
      // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
      if (
        node.callee.type === "Identifier" &&
        mayBeAsyncChunkArguments(args) &&
        args[1].type !== "SpreadElement" &&
        isModulesList(args[1])
      ) {
        state.locations = getModulesLocations(args[1]);
        return;
      }

      // Async Webpack v4 chunk without webpack loader.
      // (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
      // As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
      if (
        isAsyncChunkPushExpression(node) &&
        args[0].type === "ArrayExpression" &&
        args[0].elements[1]
      ) {
        state.locations = getModulesLocations(args[0].elements[1]);
        return;
      }

      // Webpack v4 WebWorkerChunkTemplatePlugin
      // globalObject.chunkCallbackName([<chunks>],<modules>, ...);
      // Both globalObject and chunkCallbackName can be changed through the config, so we can't check them.
      if (isAsyncWebWorkerChunkExpression(node)) {
        state.locations = getModulesLocations(args[1]);
        return;
      }

      // Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
      // features (e.g. `umd` library output) can wrap modules list into additional IIFE.
      for (const arg of args) {
        callback(arg, state);
      }
    },
  });

  /** @type {Modules} */
  const modules = {};

  if (walkState.locations) {
    for (const [id, loc] of Object.entries(walkState.locations)) {
      modules[id] = content.slice(loc.start, loc.end);
    }
  }

  return {
    modules,
    src: content,
    runtimeSrc: getBundleRuntime(content, walkState.locations),
  };
};


================================================
FILE: src/sizeUtils.js
================================================
import zlib from "node:zlib";

export const isZstdSupported = "createZstdCompress" in zlib;

/** @typedef {"gzip" | "brotli" | "zstd"} Algorithm */

/**
 * @param {Algorithm} algorithm compression algorithm
 * @param {string} input input
 * @returns {number} compressed size
 */
export function getCompressedSize(algorithm, input) {
  if (algorithm === "gzip") {
    return zlib.gzipSync(input, { level: 9 }).length;
  }

  if (algorithm === "brotli") {
    return zlib.brotliCompressSync(input).length;
  }

  if (algorithm === "zstd" && isZstdSupported) {
    // eslint-disable-next-line n/no-unsupported-features/node-builtins
    return zlib.zstdCompressSync(input).length;
  }

  throw new Error(`Unsupported compression algorithm: ${algorithm}.`);
}


================================================
FILE: src/statsUtils.js
================================================
const { createWriteStream } = require("node:fs");
const { Readable } = require("node:stream");
const { pipeline } = require("node:stream/promises");

/** @typedef {import("./BundleAnalyzerPlugin").EXPECTED_ANY} EXPECTED_ANY */
/** @typedef {import("webpack").StatsCompilation} StatsCompilation */

class StatsSerializeStream extends Readable {
  /**
   * @param {StatsCompilation} stats stats
   */
  constructor(stats) {
    super();
    this._indentLevel = 0;
    this._stringifier = this._stringify(stats);
  }

  get _indent() {
    return "  ".repeat(this._indentLevel);
  }

  _read() {
    let readMore = true;

    while (readMore) {
      const { value, done } = this._stringifier.next();

      if (done) {
        this.push(null);
        readMore = false;
      } else {
        readMore = this.push(value);
      }
    }
  }

  /**
   * @param {EXPECTED_ANY} obj obj
   * @returns {Generator<string, undefined, unknown>} stringified result
   * @private
   */
  *_stringify(obj) {
    if (
      typeof obj === "string" ||
      typeof obj === "number" ||
      typeof obj === "boolean" ||
      obj === null
    ) {
      yield JSON.stringify(obj);
    } else if (Array.isArray(obj)) {
      yield "[";
      this._indentLevel++;

      let isFirst = true;
      for (let item of obj) {
        if (item === undefined) {
          item = null;
        }

        yield `${isFirst ? "" : ","}\n${this._indent}`;
        yield* this._stringify(item);
        isFirst = false;
      }

      this._indentLevel--;
      yield obj.length ? `\n${this._indent}]` : "]";
    } else {
      yield "{";
      this._indentLevel++;

      let isFirst = true;
      const entries = Object.entries(obj);
      for (const [itemKey, itemValue] of entries) {
        if (itemValue === undefined) {
          continue;
        }

        yield `${isFirst ? "" : ","}\n${this._indent}${JSON.stringify(itemKey)}: `;
        yield* this._stringify(itemValue);
        isFirst = false;
      }

      this._indentLevel--;
      yield entries.length ? `\n${this._indent}}` : "}";
    }
  }
}

/**
 * @param {StatsCompilation} stats stats
 * @param {string} filepath filepath file path
 * @returns {Promise<void>}
 */
async function writeStats(stats, filepath) {
  await pipeline(new StatsSerializeStream(stats), createWriteStream(filepath));
}

module.exports = { StatsSerializeStream, writeStats };


================================================
FILE: src/template.js
================================================
const fs = require("node:fs");
const path = require("node:path");

const { escape } = require("html-escaper");

const projectRoot = path.resolve(__dirname, "..");
const assetsRoot = path.join(projectRoot, "public");

/** @typedef {import("./BundleAnalyzerPlugin").EXPECTED_ANY} EXPECTED_ANY */
/** @typedef {import("./BundleAnalyzerPlugin").Mode} Mode */
/** @typedef {import("./BundleAnalyzerPlugin").Sizes} Sizes */
/** @typedef {import("./BundleAnalyzerPlugin").CompressionAlgorithm} CompressionAlgorithm */
/** @typedef {import("./analyzer").ChartData} ChartData */
/** @typedef {import("./viewer").Entrypoints} Entrypoints */

/**
 * Escapes `<` characters in JSON to safely use it in `<script>` tag.
 * @param {EXPECTED_ANY} json json
 * @returns {string} escaped json
 */
function escapeJson(json) {
  return JSON.stringify(json).replaceAll("<", "\\u003c");
}

/**
 * @param {string} filename filename
 * @returns {string} content the text content of the specified file.
 */
function getAssetContent(filename) {
  const assetPath = path.join(assetsRoot, filename);

  if (!assetPath.startsWith(assetsRoot)) {
    throw new Error(`"${filename}" is outside of the assets root`);
  }

  return fs.readFileSync(assetPath, "utf8");
}

/**
 * @template {EXPECTED_ANY} T
 * @param {TemplateStringsArray} strings strings
 * @param {...T} values values
 * @returns {string} HTML
 */
function html(strings, ...values) {
  return strings
    .map((string, index) => `${string}${values[index] || ""}`)
    .join("");
}

/**
 * @param {string} filename filename
 * @param {Mode} mode mode
 * @returns {string} script tag
 */
function getScript(filename, mode) {
  if (mode === "static") {
    return `<!-- ${escape(filename)} -->
<script>${getAssetContent(filename)}</script>`;
  }

  return `<script src="${escape(filename)}"></script>`;
}

/**
 * @typedef {object} ViewerOptions
 * @property {string} title title
 * @property {boolean} enableWebSocket true when need to enable, otherwise false
 * @property {ChartData} chartData chart data
 * @property {Entrypoints} entrypoints entrypoints
 * @property {Sizes} defaultSizes default sizes
 * @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
 * @property {Mode} mode mode
 */

/**
 * @param {ViewerOptions} options viewer Options
 * @returns {string} content for viewer
 */
function renderViewer({
  title,
  enableWebSocket,
  chartData,
  entrypoints,
  defaultSizes,
  compressionAlgorithm,
  mode,
}) {
  return html`<!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>${escape(title)}</title>
        <link
          rel="shortcut icon"
          href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABrVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+O1foceMD///+J0/qK1Pr7/v8Xdr/9///W8P4UdL7L7P0Scr2r4Pyj3vwad8D5/f/2/f+55f3E6f34+/2H0/ojfMKpzOd0rNgQcb3F3O/j9f7c8v6g3Pz0/P/w+v/q+P7n9v6T1/uQ1vuE0vqLut/y+v+Z2fvt+f+15Pzv9fuc2/vR7v2V2Pvd6/bg9P7I6/285/2y4/yp3/zp8vk8i8kqgMT7/P31+fyv4vxGkcz6/P6/6P3j7vfS5PNnpNUxhcbO7f7F6v3O4vHK3/DA2u631Ouy0eqXweKJud5wqthfoNMMbLvY8f73+v2dxeR8sNtTmdDx9/zX6PSjyeaCtd1YnNGX2PuQveCGt95Nls42h8dLlM3F4vBtAAAAM3RSTlMAAyOx0/sKBvik8opWGBMOAe3l1snDm2E9LSb06eHcu5JpHbarfHZCN9CBb08zzkdNS0kYaptYAAAFV0lEQVRYw92X51/aYBDHHS2O2qqttVbrqNq9m+TJIAYIShBkWwqIiCgoWvfeq7Z2/s29hyQNyUcR7LveGwVyXy6XH8/9rqxglLfUPLxVduUor3h0rfp2TYvpivk37929TkG037hffoX0+peVtZQc1589rigVUdXS/ABSAyEmGIO/1XfvldSK8vs3OqB6u3m0nxmIrvgB0dj7rr7Y9IbuF68hnfFaiHA/sxqm0wciIG43P60qKv9WXWc1RXGh/mFESFABTSBi0sNAKzqet17eCtOb3kZIDwxEEU0oAIJGYxNBDhBND29e0rtXXbcpuPmED9IhEAAQ/AXEaF8EPmnrrKsv0LvWR3fg5sWDNAFZOgAgaKvZDogHNU9MFwnnYROkc56RD5CjAbQX9Ow4g7upCsvYu55aSI/Nj0H1akgKQEUM94dwK65hYRmFU9MIcH/fqJYOZYcnuJSU/waKDgTOEVaVKhwrTRP5XzgSpAITYzom7UvkhFX5VutmxeNnWDjjswTKTyfgluNDGbUpWissXhF3s7mlSml+czWkg3D0l1nNjGNjz3myOQOa1KM/jOS6ebdbAVTCi4gljHSFrviza7tOgRWcS0MOUX9zdNgag5w7rRqA44Lzw0hr1WqES36dFliSJFlh2rXIae3FFcDDgKdxrUIDePr8jGcSClV1u7A9xeN0ModY/pHMxmR1EzRh8TJiwqsHmKW0l4FCEZI+jHio+JdPPE9qwQtTRxku2D8sIeRL2LnxWSllANCQGOIiqVHAz2ye2JR0DcH+HoxDkaADLjgxjKQ+AwCX/g0+DNgdG0ukYCONAe+dbc2IAc6fwt1ARoDSezNHxV2Cmzwv3O6lDMV55edBGwGK9n1+x2F8EDfAGCxug8MhpsMEcTEAWf3rx2vZhe/LAmtIn/6apE6PN0ULKgywD9mmdxbmFl3OvD5AS5fW5zLbv/YHmcsBTjf/afDz3MaZTVCfAP9z6/Bw6ycv8EUBWJIn9zYcoAWWlW9+OzO3vkTy8H+RANLmdrpOuYWdZYEXpo+TlCJrW5EARb7fF+bWdqf3hhyZI1nWJQHgznErZhbjoEsWqi8dQNoE294aldzFurwSABL2XXMf9+H1VQGke9exw5P/AnA5Pv5ngMul7LOvO922iwACu8WkCwLCafvM4CeWPxfA8lNHcWZSoi8EwMAIciKX2Z4SWCMAa3snCZ/G4EA8D6CMLNFsGQhkkz/gQNEBbPCbWsxGUpYVu3z8IyNAknwJkfPMEhLyrdi5RTyUVACkw4GSFRNWJNEW+fgPGwHD8/JxnRuLabN4CGNRkAE23na2+VmEAUmrYymSGjMAYqH84YUIyzgzs3XC7gNgH36Vcc4zKY9o9fgPBXUAiHHwVboBHGLiX6Zcjp1f2wu4tvzZKo0ecPnDtQYDQvJXaBeNzce45Fp28ZQLrEZVuFqgBwOalArKXnW1UzlnSusQKJqKYNuz4tOnI6sZG4zanpemv+7ySU2jbA9h6uhcgpfy6G2PahirDZ6zvq6zDduMVFTKvzw8wgyEdelwY9in3XkEPs3osJuwRQ4qTkfzifndg9Gfc4pdsu82+tTnHZTBa2EAMrqr2t43pguc8tNm7JQVQ2S0ukj2d22dhXYP0/veWtwKrCkNoNimAN5+Xr/oLrxswKbVJjteWrX7eR63o4j9q0GxnaBdWgGA5VStpanIjQmEhV0/nVt5VOFUvix6awJhPcAaTEShgrG+iGyvb5a0Ndb1YGHFPEwoqAinoaykaID1o1pdPNu7XsnCKQ3R+hwWIIhGvORcJUBYXe3Xa3vq/mF/N9V13ugufMkfXn+KHsRD0B8AAAAASUVORK5CYII="
          type="image/x-icon"
        />

        <script>
          window.enableWebSocket = ${escapeJson(enableWebSocket)};
        </script>
        ${getScript("viewer.js", mode)}
      </head>

      <body>
        <div id="app"></div>
        <script>
          window.chartData = ${escapeJson(chartData)};
          window.entrypoints = ${escapeJson(entrypoints)};
          window.defaultSizes = ${escapeJson(defaultSizes)};
          window.compressionAlgorithm = ${escapeJson(compressionAlgorithm)};
        </script>
      </body>
    </html>`;
}

module.exports = { renderViewer };


================================================
FILE: src/tree/BaseFolder.js
================================================
import Node from "./Node.js";

/** @typedef {import("./Folder").default} Folder */
/** @typedef {import("./Module").default} Module */
/** @typedef {import("./Module").ModuleChartData} ModuleChartData */
/** @typedef {import("./ConcatenatedModule").default} ConcatenatedModule */
/** @typedef {import("./ContentModule").default} ContentModule */
/** @typedef {import("./ContentFolder").default} ContentFolder */
/** @typedef {import("./ContentFolder").ContentFolderChartData} ContentFolderChartData */
/** @typedef {import("./Folder").FolderChartData} FolderChartData */

/**
 * @typedef {object} BaseFolderChartData
 * @property {string} label label
 * @property {string} path path
 * @property {number} statSize stat size
 * @property {(FolderChartData | ModuleChartData | ContentFolderChartData)[]} groups groups
 */

/** @typedef {Module | ContentModule | ConcatenatedModule | ContentFolder | Folder} Children */

export default class BaseFolder extends Node {
  /**
   * @param {string} name name
   * @param {Node=} parent parent
   */
  constructor(name, parent) {
    super(name, parent);
    /** @type {Record<string, Children>} */
    this.children = Object.create(null);
  }

  /**
   * @returns {string} src
   */
  get src() {
    if (!Object.hasOwn(this, "_src")) {
      this._src = this.walk(
        (node, src) => (src += node.src || ""),
        /** @type {string} */ (""),
        false,
      );
    }

    return /** @type {string} */ (this._src);
  }

  /**
   * @returns {number} size
   */
  get size() {
    if (!Object.hasOwn(this, "_size")) {
      this._size = this.walk(
        (node, size) => size + node.size,
        /** @type {number} */ (0),
        false,
      );
    }

    return /** @type {number} */ (this._size);
  }

  /**
   * @param {string} name name
   * @returns {Children} child
   */
  getChild(name) {
    return this.children[name];
  }

  /**
   * @param {Module | ContentModule | ConcatenatedModule} module module
   */
  addChildModule(module) {
    const { name } = module;
    const currentChild = this.children[name];

    // For some reason we already have this node in children and it's a folder.
    if (currentChild && currentChild instanceof BaseFolder) return;

    if (currentChild) {
      // We already have this node in children and it's a module.
      // Merging it's data.
      currentChild.mergeData(module.data);
    } else {
      // Pushing new module
      module.parent = this;
      this.children[name] = module;
    }

    delete this._size;
    delete this._src;
  }

  /**
   * @param {ContentFolder | Folder} folder folder
   * @returns {ContentFolder | Folder} folder
   */
  addChildFolder(folder) {
    folder.parent = this;
    this.children[folder.name] = folder;
    delete this._size;
    delete this._src;

    return folder;
  }

  /**
   * @template T
   * @param {(node: Children, state: T, stop: (state: T) => void) => T} walker walker function
   * @param {T} state state state
   * @param {boolean | ((state: T) => T)=} deep true when need to deep walk, otherwise false
   * @returns {T} state
   */
  walk(walker, state = /** @type T */ ({}), deep = true) {
    let stopped = false;

    /**
     * @param {T} finalState final state
     * @returns {T} final state
     */
    function stop(finalState) {
      stopped = true;
      return finalState;
    }

    for (const child of Object.values(this.children)) {
      state =
        deep && /** @type {BaseFolder} */ (child).walk
          ? /** @type {BaseFolder} */ (child).walk(walker, state, stop)
          : walker(child, state, stop);

      if (stopped) return /** @type {T} */ (false);
    }

    return state;
  }

  mergeNestedFolders() {
    if (!this.isRoot) {
      let childNames;

      while ((childNames = Object.keys(this.children)).length === 1) {
        const [childName] = childNames;
        const onlyChild = this.children[childName];

        if (onlyChild instanceof this.constructor) {
          this.name += `/${onlyChild.name}`;
          this.children = /** @type {BaseFolder} */ (onlyChild).children;
        } else {
          break;
        }
      }
    }

    this.walk(
      (child, state) => {
        child.parent = this;

        if (
          /** @type {Folder | ContentFolder | ConcatenatedModule} */
          (child).mergeNestedFolders
        ) {
          /** @type {Folder | ContentFolder | ConcatenatedModule} */
          (child).mergeNestedFolders();
        }

        return state;
      },
      null,
      false,
    );
  }

  /**
   * @returns {BaseFolderChartData} base folder chart data
   */
  toChartData() {
    return {
      label: this.name,
      path: this.path,
      statSize: this.size,
      groups: Object.values(this.children).map((child) => child.toChartData()),
    };
  }
}


================================================
FILE: src/tree/ConcatenatedModule.js
================================================
import ContentFolder from "./ContentFolder.js";
import ContentModule from "./ContentModule.js";
import Module from "./Module.js";
import { getModulePathParts } from "./utils.js";

/** @typedef {import("webpack").StatsModule} StatsModule */
/** @typedef {import("./Node").default} NodeType */
/** @typedef {import("./Module").ModuleChartData} ModuleChartData */
/** @typedef {import("./Module").SizeType} SizeType */
/** @typedef {import("./Folder").default} Folder */
/** @typedef {import("./BaseFolder").Children} Children */
/** @typedef {import("./ContentFolder").ContentFolderChartData} ContentFolderChartData */
/** @typedef {import("./ContentModule").ContentModuleChartData} ContentModuleChartData */
/** @typedef {import("../sizeUtils").Algorithm} CompressionAlgorithm */

/**
 * @typedef {object} OwnConcatenatedModuleChartData
 * @property {boolean} concatenated true when concatenated, otherwise false
 * @property {(ConcatenatedModuleChartData | ContentFolderChartData | ContentModuleChartData)[]} groups groups
 */

/** @typedef {ModuleChartData & OwnConcatenatedModuleChartData} ConcatenatedModuleChartData */

export default class ConcatenatedModule extends Module {
  /**
   * @param {string} name name
   * @param {StatsModule} data data
   * @param {NodeType} parent parent
   * @param {{ compressionAlgorithm: CompressionAlgorithm }} opts options
   */
  constructor(name, data, parent, opts) {
    super(name, data, parent, opts);
    this.name += " (concatenated)";
    /** @type {Record<string, ConcatenatedModule | ContentModule | ContentFolder>} */
    this.children = Object.create(null);
    this.fillContentModules();
  }

  get parsedSize() {
    return this.getParsedSize() ?? this.getEstimatedSize("parsedSize");
  }

  get gzipSize() {
    return this.getGzipSize() ?? this.getEstimatedSize("gzipSize");
  }

  get brotliSize() {
    return this.getBrotliSize() ?? this.getEstimatedSize("brotliSize");
  }

  get zstdSize() {
    return this.getZstdSize() ?? this.getEstimatedSize("zstdSize");
  }

  /**
   * @param {SizeType} sizeType size type
   * @returns {number | undefined} size
   */
  getEstimatedSize(sizeType) {
    const parentModuleSize = /** @type {Folder} */ (this.parent)[sizeType];

    if (parentModuleSize !== undefined) {
      return Math.floor(
        (this.size / /** @type {Folder} */ (this.parent).size) *
          parentModuleSize,
      );
    }
  }

  fillContentModules() {
    for (const moduleData of this.data.modules || []) {
      this.addContentModule(moduleData);
    }
  }

  /**
   * @param {StatsModule} moduleData module data
   */
  addContentModule(moduleData) {
    const pathParts = getModulePathParts(moduleData);

    if (!pathParts) {
      return;
    }

    const [folders, fileName] = [
      pathParts.slice(0, -1),
      pathParts[pathParts.length - 1],
    ];
    /** @type {ConcatenatedModule | ContentFolder} */
    let currentFolder = this;

    for (const folderName of folders) {
      /** @type {Children} */
      let childFolder = currentFolder.getChild(folderName);

      if (!childFolder) {
        childFolder = currentFolder.addChildFolder(
          new ContentFolder(folderName, this),
        );
      }

      currentFolder =
        /** @type {ConcatenatedModule | ContentFolder} */
        (childFolder);
    }

    const ModuleConstructor = moduleData.modules
      ? ConcatenatedModule
      : ContentModule;
    const module = new ModuleConstructor(fileName, moduleData, this, this.opts);
    currentFolder.addChildModule(module);
  }

  /**
   * @param {string} name name
   * @returns {ConcatenatedModule | ContentModule | ContentFolder} child folder
   */
  getChild(name) {
    return this.children[name];
  }

  /**
   * @param {ConcatenatedModule | ContentModule} module child module
   */
  addChildModule(module) {
    module.parent = this;
    this.children[module.name] = module;
  }

  /**
   * @param {ContentFolder} folder child folder
   * @returns {ContentFolder} child folder
   */
  addChildFolder(folder) {
    folder.parent = this;
    this.children[folder.name] = folder;
    return folder;
  }

  mergeNestedFolders() {
    for (const child of Object.values(this.children)) {
      if (
        /** @type {Folder | ContentFolder | ConcatenatedModule} */
        (child).mergeNestedFolders
      ) {
        /** @type {Folder | ContentFolder | ConcatenatedModule} */
        (child).mergeNestedFolders();
      }
    }
  }

  /**
   * @returns {ConcatenatedModuleChartData} chart data
   */
  toChartData() {
    return {
      ...super.toChartData(),
      concatenated: true,
      groups: Object.values(this.children).map((child) => child.toChartData()),
    };
  }
}


================================================
FILE: src/tree/ContentFolder.js
================================================
import BaseFolder from "./BaseFolder.js";

/** @typedef {import("./Node").default} Node */
/** @typedef {import("./ConcatenatedModule").default} ConcatenatedModule */
/** @typedef {import("./BaseFolder").BaseFolderChartData} BaseFolderChartData */
/** @typedef {import("./Module").SizeType} SizeType */

/**
 * @typedef {object} OwnContentFolderChartData
 * @property {number | undefined} parsedSize parsed size
 * @property {number | undefined} gzipSize gzip size
 * @property {number | undefined} brotliSize brotli size
 * @property {number | undefined} zstdSize zstd size
 * @property {boolean} inaccurateSizes true when inaccurate sizes, otherwise false
 */

/** @typedef {BaseFolderChartData & OwnContentFolderChartData} ContentFolderChartData  */

export default class ContentFolder extends BaseFolder {
  /**
   * @param {string} name name
   * @param {ConcatenatedModule} ownerModule owner module
   * @param {Node=} parent v
   */
  constructor(name, ownerModule, parent) {
    super(name, parent);
    this.ownerModule = ownerModule;
  }

  get parsedSize() {
    return this.getSize("parsedSize");
  }

  get gzipSize() {
    return this.getSize("gzipSize");
  }

  get brotliSize() {
    return this.getSize("brotliSize");
  }

  get zstdSize() {
    return this.getSize
Download .txt
gitextract_2b7jp3oi/

├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .npm-upgrade.json
├── .nvmrc
├── .prettierignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bin/
│   └── install-test-webpack-versions.sh
├── client/
│   ├── .eslintrc.json
│   ├── components/
│   │   ├── Button.css
│   │   ├── Button.jsx
│   │   ├── Checkbox.css
│   │   ├── Checkbox.jsx
│   │   ├── CheckboxList.css
│   │   ├── CheckboxList.jsx
│   │   ├── CheckboxListItem.jsx
│   │   ├── ContextMenu.css
│   │   ├── ContextMenu.jsx
│   │   ├── ContextMenuItem.css
│   │   ├── ContextMenuItem.jsx
│   │   ├── Dropdown.css
│   │   ├── Dropdown.jsx
│   │   ├── Icon.css
│   │   ├── Icon.jsx
│   │   ├── ModuleItem.css
│   │   ├── ModuleItem.jsx
│   │   ├── ModulesList.css
│   │   ├── ModulesList.jsx
│   │   ├── ModulesTreemap.css
│   │   ├── ModulesTreemap.jsx
│   │   ├── Search.css
│   │   ├── Search.jsx
│   │   ├── Sidebar.css
│   │   ├── Sidebar.jsx
│   │   ├── Switcher.css
│   │   ├── Switcher.jsx
│   │   ├── SwitcherItem.jsx
│   │   ├── ThemeToggle.css
│   │   ├── ThemeToggle.jsx
│   │   ├── Tooltip.css
│   │   ├── Tooltip.jsx
│   │   ├── Treemap.jsx
│   │   └── types.js
│   ├── lib/
│   │   └── PureComponent.jsx
│   ├── localStorage.js
│   ├── store.js
│   ├── utils.js
│   ├── viewer.css
│   └── viewer.jsx
├── eslint.config.mjs
├── jest.config.js
├── package.json
├── prettier.config.mjs
├── src/
│   ├── BundleAnalyzerPlugin.js
│   ├── Logger.js
│   ├── analyzer.js
│   ├── bin/
│   │   └── analyzer.js
│   ├── index.js
│   ├── parseUtils.js
│   ├── sizeUtils.js
│   ├── statsUtils.js
│   ├── template.js
│   ├── tree/
│   │   ├── BaseFolder.js
│   │   ├── ConcatenatedModule.js
│   │   ├── ContentFolder.js
│   │   ├── ContentModule.js
│   │   ├── Folder.js
│   │   ├── Module.js
│   │   ├── Node.js
│   │   └── utils.js
│   ├── utils.js
│   └── viewer.js
├── test/
│   ├── .eslintrc.json
│   ├── .gitignore
│   ├── Logger.js
│   ├── analyzer.js
│   ├── bundles/
│   │   ├── invalidBundle.js
│   │   ├── validBundleWithArrowFunction.js
│   │   ├── validBundleWithArrowFunction.modules.json
│   │   ├── validBundleWithEsNextFeatures.js
│   │   ├── validBundleWithEsNextFeatures.modules.json
│   │   ├── validBundleWithIIFE.js
│   │   ├── validBundleWithIIFE.modules.json
│   │   ├── validCommonBundleWithDedupePlugin.js
│   │   ├── validCommonBundleWithDedupePlugin.modules.json
│   │   ├── validCommonBundleWithModulesAsArray.js
│   │   ├── validCommonBundleWithModulesAsArray.modules.json
│   │   ├── validCommonBundleWithModulesAsObject.js
│   │   ├── validCommonBundleWithModulesAsObject.modules.json
│   │   ├── validExtraBundleWithModulesAsArray.js
│   │   ├── validExtraBundleWithModulesAsArray.modules.json
│   │   ├── validExtraBundleWithModulesInsideArrayConcat.js
│   │   ├── validExtraBundleWithModulesInsideArrayConcat.modules.json
│   │   ├── validExtraBundleWithNamedChunk.js
│   │   ├── validExtraBundleWithNamedChunk.modules.json
│   │   ├── validJsonpWithArrayConcatAndEntryPoint.js
│   │   ├── validJsonpWithArrayConcatAndEntryPoint.modules.json
│   │   ├── validNodeBundle.js
│   │   ├── validNodeBundle.modules.json
│   │   ├── validUmdLibraryBundleWithModulesAsArray.js
│   │   ├── validUmdLibraryBundleWithModulesAsArray.modules.json
│   │   ├── validWebpack4AsyncChunk.js
│   │   ├── validWebpack4AsyncChunk.modules.json
│   │   ├── validWebpack4AsyncChunkAndEntryPoint.js
│   │   ├── validWebpack4AsyncChunkAndEntryPoint.modules.json
│   │   ├── validWebpack4AsyncChunkUsingCustomGlobalObject.js
│   │   ├── validWebpack4AsyncChunkUsingCustomGlobalObject.modules.json
│   │   ├── validWebpack4AsyncChunkUsingSelfInsteadOfWindow.js
│   │   ├── validWebpack4AsyncChunkUsingSelfInsteadOfWindow.modules.json
│   │   ├── validWebpack4AsyncChunkUsingThisInsteadOfWindow.js
│   │   ├── validWebpack4AsyncChunkUsingThisInsteadOfWindow.modules.json
│   │   ├── validWebpack4AsyncChunkWithOptimizedModulesArray.js
│   │   ├── validWebpack4AsyncChunkWithOptimizedModulesArray.modules.json
│   │   ├── validWebpack4AsyncChunkWithWebWorkerChunkTemplatePlugin.js
│   │   ├── validWebpack4AsyncChunkWithWebWorkerChunkTemplatePlugin.modules.json
│   │   ├── validWebpack5LegacyBundle.js
│   │   ├── validWebpack5LegacyBundle.modules.json
│   │   ├── validWebpack5ModernBundle.js
│   │   └── validWebpack5ModernBundle.modules.json
│   ├── dev-server/
│   │   ├── .gitignore
│   │   ├── src.js
│   │   └── webpack.config.js
│   ├── dev-server.js
│   ├── helpers.js
│   ├── parseUtils.js
│   ├── plugin.js
│   ├── src/
│   │   ├── a-clone.js
│   │   ├── a.js
│   │   ├── b.js
│   │   └── index.js
│   ├── stats/
│   │   ├── extremely-optimized-webpack-5-bundle/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── minimal-stats/
│   │   │   └── stats.json
│   │   ├── webpack-5-bundle-with-concatenated-entry-module/
│   │   │   ├── app.js
│   │   │   ├── expected-chart-data.json
│   │   │   └── stats.json
│   │   ├── webpack-5-bundle-with-multiple-entries/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── webpack-5-bundle-with-single-entry/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-array-config/
│   │   │   ├── config-1-main.js
│   │   │   ├── config-2-main.js
│   │   │   └── stats.json
│   │   ├── with-children-array.json
│   │   ├── with-cjs-chunk.json
│   │   ├── with-invalid-chunk/
│   │   │   ├── invalid-chunk.js
│   │   │   ├── stats.json
│   │   │   └── valid-chunk.js
│   │   ├── with-invalid-dynamic-require.json
│   │   ├── with-missing-chunk/
│   │   │   ├── stats.json
│   │   │   └── valid-chunk.js
│   │   ├── with-missing-module-chunks/
│   │   │   ├── stats.json
│   │   │   └── valid-chunk.js
│   │   ├── with-missing-parsed-module/
│   │   │   ├── bundle.js
│   │   │   └── stats.json
│   │   ├── with-module-concatenation-info/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-modules-chunk.json
│   │   ├── with-modules-in-chunks/
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-multiple-entrypoints/
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-no-entrypoints/
│   │   │   └── stats.json
│   │   ├── with-non-asset-asset/
│   │   │   ├── bundle.js
│   │   │   └── stats.json
│   │   ├── with-special-chars/
│   │   │   ├── bundle.js
│   │   │   ├── expected-chart-data.js
│   │   │   └── stats.json
│   │   ├── with-worker-loader/
│   │   │   ├── bundle.js
│   │   │   ├── bundle.worker.js
│   │   │   └── stats.json
│   │   └── with-worker-loader-dynamic-import/
│   │       ├── 1.bundle.js
│   │       ├── 1.bundle.worker.js
│   │       ├── bundle.js
│   │       ├── bundle.worker.js
│   │       └── stats.json
│   ├── statsUtils.js
│   ├── utils.js
│   └── viewer.js
├── tsconfig.json
└── webpack.config.js
Download .txt
SYMBOL INDEX (574 symbols across 70 files)

FILE: client/components/Button.jsx
  class Button (line 7) | class Button extends PureComponent {
    method render (line 20) | render({ active, className, children, ...props }) {
    method disabled (line 40) | get disabled() {

FILE: client/components/Checkbox.jsx
  class Checkbox (line 7) | class Checkbox extends Component {
    method render (line 18) | render() {

FILE: client/components/CheckboxList.jsx
  constant ALL_ITEM (line 7) | const ALL_ITEM = Symbol("ALL_ITEM");
  class CheckboxList (line 9) | class CheckboxList extends PureComponent {
    method constructor (line 21) | constructor(props) {
    method componentWillReceiveProps (line 28) | componentWillReceiveProps(newProps) {
    method render (line 50) | render() {
    method isItemChecked (line 100) | isItemChecked(item) {
    method isAllChecked (line 104) | isAllChecked() {
    method informAboutChange (line 108) | informAboutChange(checkedItems) {

FILE: client/components/CheckboxListItem.jsx
  class CheckboxListItem (line 9) | class CheckboxListItem extends Component {
    method render (line 19) | render() {
    method renderLabel (line 29) | renderLabel() {

FILE: client/components/ContextMenu.jsx
  class ContextMenu (line 10) | class ContextMenu extends PureComponent {
    method componentDidMount (line 23) | componentDidMount() {
    method componentDidUpdate (line 27) | componentDidUpdate(prevProps) {
    method render (line 43) | render() {
    method hide (line 122) | hide() {
    method getStyle (line 130) | getStyle() {

FILE: client/components/ContextMenuItem.jsx
  function noop (line 9) | function noop() {
  function ContextMenuItem (line 24) | function ContextMenuItem({ children, disabled, onClick }) {

FILE: client/components/Dropdown.jsx
  class Dropdown (line 7) | class Dropdown extends PureComponent {
    method componentDidMount (line 21) | componentDidMount() {
    method componentWillUnmount (line 25) | componentWillUnmount() {
    method render (line 29) | render() {

FILE: client/components/Icon.jsx
  constant ICONS (line 11) | const ICONS = {
  class Icon (line 30) | class Icon extends PureComponent {
    method render (line 39) | render({ className }) {
    method style (line 43) | get style() {

FILE: client/components/ModuleItem.jsx
  class ModuleItem (line 10) | class ModuleItem extends PureComponent {
    method render (line 25) | render({ module, showSize }) {
    method itemType (line 51) | get itemType() {
    method titleHtml (line 57) | get titleHtml() {
    method invisibleHint (line 92) | get invisibleHint() {
    method isVisible (line 98) | get isVisible() {

FILE: client/components/ModulesList.jsx
  class ModulesList (line 8) | class ModulesList extends PureComponent {
    method render (line 20) | render({ modules, showSize, highlightedText, isModuleVisible, classNam...

FILE: client/components/ModulesTreemap.jsx
  function getSizeSwitchItems (line 26) | function getSizeSwitchItems() {
  class ModulesTreemap (line 47) | class ModulesTreemap extends Component {
    method constructor (line 62) | constructor() {
    method componentDidMount (line 74) | componentDidMount() {
    method componentWillUnmount (line 78) | componentWillUnmount() {
    method render (line 82) | render() {
    method renderModuleSize (line 194) | renderModuleSize(module, sizeType) {
    method sizeSwitchItems (line 221) | get sizeSwitchItems() {
    method activeSizeItem (line 227) | get activeSizeItem() {
    method chunkItems (line 231) | get chunkItems() {
    method highlightedModules (line 246) | get highlightedModules() {
    method foundModulesInfo (line 250) | get foundModulesInfo() {
    method getTooltipContent (line 384) | getTooltipContent(module) {

FILE: client/components/Search.jsx
  class Search (line 10) | class Search extends PureComponent {
    method componentDidMount (line 22) | componentDidMount() {
    method componentWillUnmount (line 28) | componentWillUnmount() {
    method render (line 32) | render() {
    method focus (line 89) | focus() {
    method clear (line 95) | clear() {
    method informChange (line 101) | informChange(value) {

FILE: client/components/Sidebar.jsx
  class Sidebar (line 11) | class Sidebar extends Component {
    method componentDidMount (line 41) | componentDidMount() {
    method componentWillUnmount (line 45) | componentWillUnmount() {
    method render (line 50) | render() {
    method toggleVisibility (line 162) | toggleVisibility(flag) {
    method updateNodeWidth (line 199) | updateNodeWidth(width = this.width) {

FILE: client/components/Switcher.jsx
  class Switcher (line 7) | class Switcher extends PureComponent {
    method render (line 17) | render() {

FILE: client/components/SwitcherItem.jsx
  class SwitcherItem (line 6) | class SwitcherItem extends PureComponent {
    method render (line 15) | render({ item, ...props }) {

FILE: client/components/ThemeToggle.jsx
  class ThemeToggle (line 10) | class ThemeToggle extends Component {
    method render (line 11) | render() {

FILE: client/components/Tooltip.jsx
  class Tooltip (line 7) | class Tooltip extends Component {
    method componentDidMount (line 28) | componentDidMount() {
    method shouldComponentUpdate (line 32) | shouldComponentUpdate(nextProps) {
    method componentWillUnmount (line 36) | componentWillUnmount() {
    method render (line 40) | render() {
    method getStyle (line 68) | getStyle() {
    method updatePosition (line 75) | updatePosition() {

FILE: client/components/Treemap.jsx
  function preventDefault (line 9) | function preventDefault(event) {
  function hashCode (line 17) | function hashCode(str) {
  class Treemap (line 27) | class Treemap extends Component {
    method constructor (line 41) | constructor(props) {
    method componentDidMount (line 48) | componentDidMount() {
    method componentWillReceiveProps (line 53) | componentWillReceiveProps(nextProps) {
    method shouldComponentUpdate (line 64) | shouldComponentUpdate() {
    method componentWillUnmount (line 68) | componentWillUnmount() {
    method render (line 73) | render() {
    method getTreemapDataObject (line 79) | getTreemapDataObject(data = this.props.data) {
    method createTreemap (line 83) | createTreemap() {
    method getGroupRoot (line 190) | getGroupRoot(group) {
    method zoomToGroup (line 201) | zoomToGroup(group) {
    method isGroupRendered (line 213) | isGroupRendered(group) {
    method update (line 218) | update() {
    method findChunkNamePartIndex (line 234) | findChunkNamePartIndex() {
    method getChunkNamePart (line 278) | getChunkNamePart(chunkLabel) {

FILE: client/lib/PureComponent.jsx
  function isEqual (line 8) | function isEqual(obj1, obj2) {
  class PureComponent (line 19) | class PureComponent extends Component {
    method shouldComponentUpdate (line 20) | shouldComponentUpdate(nextProps, nextState) {

FILE: client/localStorage.js
  constant KEY_PREFIX (line 1) | const KEY_PREFIX = "wba";
  method getItem (line 4) | getItem(key) {
  method setItem (line 14) | setItem(key, value) {
  method removeItem (line 25) | removeItem(key) {

FILE: client/store.js
  class Store (line 5) | class Store {
    method constructor (line 44) | constructor() {
    method setModules (line 75) | setModules(modules) {
    method setEntrypoints (line 84) | setEntrypoints(entrypoints) {
    method hasParsedSizes (line 88) | get hasParsedSizes() {
    method setSelectedSize (line 92) | setSelectedSize(selectedSize) {
    method activeSize (line 96) | get activeSize() {
    method setSelectedChunks (line 106) | setSelectedChunks(chunks) {
    method visibleChunks (line 110) | get visibleChunks() {
    method allChunksSelected (line 118) | get allChunksSelected() {
    method totalChunksSize (line 122) | get totalChunksSize() {
    method searchQueryRegexp (line 129) | get searchQueryRegexp() {
    method isSearching (line 143) | get isSearching() {
    method foundModulesByChunk (line 147) | get foundModulesByChunk() {
    method setSearchQuery (line 204) | setSearchQuery(query) {
    method foundModules (line 208) | get foundModules() {
    method hasFoundModules (line 215) | get hasFoundModules() {
    method hasConcatenatedModules (line 219) | get hasConcatenatedModules() {
    method foundModulesSize (line 232) | get foundModulesSize() {
    method filterModulesForSize (line 239) | filterModulesForSize(modules, sizeProp) {
    method toggleDarkMode (line 262) | toggleDarkMode() {
    method updateTheme (line 272) | updateTheme() {

FILE: client/utils.js
  function isChunkParsed (line 5) | function isChunkParsed(chunk) {
  function walkModules (line 14) | function walkModules(modules, cb) {
  function elementIsOutside (line 30) | function elementIsOutside(elem, container) {

FILE: src/BundleAnalyzerPlugin.js
  class BundleAnalyzerPlugin (line 52) | class BundleAnalyzerPlugin {
    method constructor (line 56) | constructor(opts = {}) {
    method apply (line 89) | apply(compiler) {
    method generateStatsFile (line 148) | async generateStatsFile(stats) {
    method startAnalyzerServer (line 173) | async startAnalyzerServer(stats) {
    method generateJSONReport (line 196) | async generateJSONReport(stats) {
    method generateStaticReport (line 214) | async generateStaticReport(stats) {
    method getBundleDirFromCompiler (line 231) | getBundleDirFromCompiler() {

FILE: src/Logger.js
  constant LEVELS (line 6) | const LEVELS = ["debug", "info", "warn", "error", "silent"];
  constant LEVEL_TO_CONSOLE_METHOD (line 9) | const LEVEL_TO_CONSOLE_METHOD = new Map([
  class Logger (line 15) | class Logger {
    method constructor (line 25) | constructor(level = Logger.defaultLevel) {
    method setLogLevel (line 34) | setLogLevel(level) {
    method debug (line 54) | debug(...args) {
    method info (line 63) | info(...args) {
    method error (line 72) | error(...args) {
    method warn (line 81) | warn(...args) {
    method _log (line 91) | _log(level, ...args) {

FILE: src/analyzer.js
  constant FILENAME_QUERY_REGEXP (line 12) | const FILENAME_QUERY_REGEXP = /\?.*$/u;
  constant FILENAME_EXTENSIONS (line 13) | const FILENAME_EXTENSIONS = /\.(js|mjs|cjs|bundle)$/iu;
  function createModulesTree (line 31) | function createModulesTree(modules, options) {
  function flatten (line 61) | function flatten(arr) {
  function getChildAssetBundles (line 85) | function getChildAssetBundles(bundleStats, assetName) {
  function assetHasModule (line 102) | function assetHasModule(statsAsset, statsModule) {
  function isRuntimeModule (line 114) | function isRuntimeModule(statsModule) {
  function getBundleModules (line 122) | function getBundleModules(bundleStats) {
  function getChunkToInitialByEntrypoint (line 152) | function getChunkToInitialByEntrypoint(bundleStats) {
  function isEntryModule (line 174) | function isEntryModule(statsModule) {
  function getViewerData (line 214) | function getViewerData(bundleStats, bundleDir, opts) {
  function readStatsFromFile (line 432) | function readStatsFromFile(filename) {

FILE: src/bin/analyzer.js
  constant SIZES (line 14) | const SIZES = new Set(["stat", "parsed", "gzip"]);
  constant COMPRESSION_ALGORITHMS (line 15) | const COMPRESSION_ALGORITHMS = new Set(
  function br (line 23) | function br(str) {
  function array (line 31) | function array() {
  function showHelp (line 132) | function showHelp(error) {
  function parseAndAnalyse (line 171) | async function parseAndAnalyse(bundleStatsFile) {

FILE: src/parseUtils.js
  function isNumericId (line 15) | function isNumericId(node) {
  function isModuleId (line 29) | function isModuleId(node) {
  function isModuleWrapper (line 41) | function isModuleWrapper(node) {
  function isModulesHash (line 60) | function isModulesHash(node) {
  function isModulesArray (line 75) | function isModulesArray(node) {
  function isSimpleModulesList (line 91) | function isSimpleModulesList(node) {
  function isOptimizedModulesArray (line 104) | function isOptimizedModulesArray(node) {
  function isModulesList (line 132) | function isModulesList(node) {
  function getModuleLocation (line 146) | function getModuleLocation(node) {
  function getModulesLocations (line 159) | function getModulesLocations(node) {
  function isIIFE (line 221) | function isIIFE(node) {
  function getIIFECallExpression (line 234) | function getIIFECallExpression(node) {
  function isChunkIds (line 246) | function isChunkIds(node) {
  function mayBeAsyncChunkArguments (line 255) | function mayBeAsyncChunkArguments(args) {
  function getBundleRuntime (line 270) | function getBundleRuntime(content, modulesLocations) {
  function isAsyncChunkPushExpression (line 290) | function isAsyncChunkPushExpression(node) {
  function isAsyncWebWorkerChunkExpression (line 309) | function isAsyncWebWorkerChunkExpression(node) {
  method ExpressionStatement (line 345) | ExpressionStatement(node, state, callback) {
  method AssignmentExpression (line 394) | AssignmentExpression(node, state) {
  method CallExpression (line 416) | CallExpression(node, state, callback) {

FILE: src/sizeUtils.js
  function getCompressedSize (line 12) | function getCompressedSize(algorithm, input) {

FILE: src/statsUtils.js
  class StatsSerializeStream (line 8) | class StatsSerializeStream extends Readable {
    method constructor (line 12) | constructor(stats) {
    method _indent (line 18) | get _indent() {
    method _read (line 22) | _read() {
    method _stringify (line 42) | *_stringify(obj) {
  function writeStats (line 94) | async function writeStats(stats, filepath) {

FILE: src/template.js
  function escapeJson (line 21) | function escapeJson(json) {
  function getAssetContent (line 29) | function getAssetContent(filename) {
  function html (line 45) | function html(strings, ...values) {
  function getScript (line 56) | function getScript(filename, mode) {
  function renderViewer (line 80) | function renderViewer({

FILE: src/tree/BaseFolder.js
  class BaseFolder (line 22) | class BaseFolder extends Node {
    method constructor (line 27) | constructor(name, parent) {
    method src (line 36) | get src() {
    method size (line 51) | get size() {
    method getChild (line 67) | getChild(name) {
    method addChildModule (line 74) | addChildModule(module) {
    method addChildFolder (line 99) | addChildFolder(folder) {
    method walk (line 115) | walk(walker, state = /** @type T */ ({}), deep = true) {
    method mergeNestedFolders (line 139) | mergeNestedFolders() {
    method toChartData (line 178) | toChartData() {

FILE: src/tree/ConcatenatedModule.js
  class ConcatenatedModule (line 24) | class ConcatenatedModule extends Module {
    method constructor (line 31) | constructor(name, data, parent, opts) {
    method parsedSize (line 39) | get parsedSize() {
    method gzipSize (line 43) | get gzipSize() {
    method brotliSize (line 47) | get brotliSize() {
    method zstdSize (line 51) | get zstdSize() {
    method getEstimatedSize (line 59) | getEstimatedSize(sizeType) {
    method fillContentModules (line 70) | fillContentModules() {
    method addContentModule (line 79) | addContentModule(moduleData) {
    method getChild (line 119) | getChild(name) {
    method addChildModule (line 126) | addChildModule(module) {
    method addChildFolder (line 135) | addChildFolder(folder) {
    method mergeNestedFolders (line 141) | mergeNestedFolders() {
    method toChartData (line 156) | toChartData() {

FILE: src/tree/ContentFolder.js
  class ContentFolder (line 19) | class ContentFolder extends BaseFolder {
    method constructor (line 25) | constructor(name, ownerModule, parent) {
    method parsedSize (line 30) | get parsedSize() {
    method gzipSize (line 34) | get gzipSize() {
    method brotliSize (line 38) | get brotliSize() {
    method zstdSize (line 42) | get zstdSize() {
    method getSize (line 50) | getSize(sizeType) {
    method toChartData (line 61) | toChartData() {

FILE: src/tree/ContentModule.js
  class ContentModule (line 17) | class ContentModule extends Module {
    method constructor (line 24) | constructor(name, data, ownerModule, opts) {
    method parsedSize (line 30) | get parsedSize() {
    method gzipSize (line 34) | get gzipSize() {
    method brotliSize (line 38) | get brotliSize() {
    method zstdSize (line 42) | get zstdSize() {
    method getSize (line 50) | getSize(sizeType) {
    method toChartData (line 61) | toChartData() {

FILE: src/tree/Folder.js
  class Folder (line 23) | class Folder extends BaseFolder {
    method constructor (line 28) | constructor(name, opts) {
    method parsedSize (line 34) | get parsedSize() {
    method gzipSize (line 38) | get gzipSize() {
    method brotliSize (line 44) | get brotliSize() {
    method zstdSize (line 50) | get zstdSize() {
    method getCompressedSize (line 60) | getCompressedSize(compressionAlgorithm) {
    method addModule (line 78) | addModule(moduleData) {
    method toChartData (line 120) | toChartData() {

FILE: src/tree/Module.js
  class Module (line 30) | class Module extends Node {
    method constructor (line 37) | constructor(name, data, parent, opts) {
    method src (line 45) | get src() {
    method src (line 49) | set src(value) {
    method size (line 60) | get size() {
    method size (line 64) | set size(value) {
    method parsedSize (line 68) | get parsedSize() {
    method gzipSize (line 72) | get gzipSize() {
    method brotliSize (line 76) | get brotliSize() {
    method zstdSize (line 80) | get zstdSize() {
    method getParsedSize (line 84) | getParsedSize() {
    method getGzipSize (line 88) | getGzipSize() {
    method getBrotliSize (line 94) | getBrotliSize() {
    method getZstdSize (line 100) | getZstdSize() {
    method getCompressedSize (line 110) | getCompressedSize(compressionAlgorithm) {
    method mergeData (line 127) | mergeData(data) {
    method toChartData (line 141) | toChartData() {

FILE: src/tree/Node.js
  class Node (line 1) | class Node {
    method constructor (line 6) | constructor(name, parent) {
    method path (line 13) | get path() {
    method isRoot (line 27) | get isRoot() {

FILE: src/tree/utils.js
  constant MULTI_MODULE_REGEXP (line 1) | const MULTI_MODULE_REGEXP = /^multi /u;
  function getModulePathParts (line 9) | function getModulePathParts(moduleData) {

FILE: src/utils.js
  constant MONTHS (line 11) | const MONTHS = [
  function createAssetsFilter (line 30) | function createAssetsFilter(excludePatterns) {
  function defaultAnalyzerUrl (line 68) | function defaultAnalyzerUrl(options) {
  function defaultTitle (line 77) | function defaultTitle() {
  function open (line 95) | function open(uri, logger) {

FILE: src/viewer.js
  function resolveTitle (line 31) | function resolveTitle(reportTitle) {
  function resolveDefaultSizes (line 44) | function resolveDefaultSizes(defaultSizes, compressionAlgorithm) {
  function getEntrypoints (line 58) | function getEntrypoints(bundleStats) {
  function getChartData (line 78) | function getChartData(analyzerOpts, bundleStats, bundleDir) {
  function startServer (line 125) | async function startServer(bundleStats, opts) {
  function generateReport (line 254) | async function generateReport(bundleStats, opts) {
  function generateJSONReport (line 315) | async function generateJSONReport(bundleStats, opts) {

FILE: test/Logger.js
  class TestLogger (line 3) | class TestLogger extends Logger {
    method constructor (line 4) | constructor(level) {
    method clear (line 9) | clear() {
    method _log (line 13) | _log(level, ...args) {
  function expectLoggerLevel (line 18) | function expectLoggerLevel(logger, level) {
  function invalidLogLevelMessage (line 37) | function invalidLogLevelMessage(level) {

FILE: test/analyzer.js
  function generateReportFrom (line 10) | function generateReportFrom(statsFilename, additionalOptions = "") {
  function getTitleFromReport (line 19) | async function getTitleFromReport() {
  function forEachChartItem (line 27) | function forEachChartItem(chartData, cb) {
  function getChartData (line 37) | async function getChartData() {
  function getCompressionAlgorithm (line 45) | async function getCompressionAlgorithm() {
  function expectValidReport (line 53) | async function expectValidReport(opts) {
  function generateJSONReportFrom (line 66) | function generateJSONReportFrom(statsFilename) {

FILE: test/bundles/validBundleWithEsNextFeatures.js
  function asyncFn (line 2) | async function asyncFn() {
  class TestClass (line 12) | class TestClass {
    method staticMethod (line 13) | static staticMethod() {}
    method constructor (line 14) | constructor() {}
    method testMethod (line 15) | testMethod() {}
  method func (line 24) | func() {}

FILE: test/bundles/validCommonBundleWithDedupePlugin.js
  function r (line 1) | function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{},id:n,lo...

FILE: test/bundles/validCommonBundleWithModulesAsArray.js
  function t (line 1) | function t(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,lo...

FILE: test/bundles/validCommonBundleWithModulesAsObject.js
  function e (line 1) | function e(n){if(i[n])return i[n].exports;var r=i[n]={exports:{},id:n,lo...

FILE: test/bundles/validExtraBundleWithModulesInsideArrayConcat.js
  function n (line 1) | function n(e){return null==e}
  function r (line 1) | function r(e){if("number"==typeof e)return e;if(i(e))return a;if(o(e)){v...

FILE: test/bundles/validUmdLibraryBundleWithModulesAsArray.js
  function o (line 1) | function o(n){if(t[n])return t[n].exports;var r=t[n]={exports:{},id:n,lo...

FILE: test/bundles/validWebpack5LegacyBundle.js
  function r (line 1) | function r(e){if(t[e])return t[e].exports;var n=t[e]={exports:{}};return...

FILE: test/bundles/validWebpack5ModernBundle.js
  function o (line 1) | function o(t){if(e[t])return e[t].exports;var p=e[t]={exports:{}};return...

FILE: test/dev-server.js
  constant ROOT (line 5) | const ROOT = path.resolve(__dirname, "./dev-server");
  constant WEBPACK_CONFIG_PATH (line 6) | const WEBPACK_CONFIG_PATH = `${ROOT}/webpack.config.js`;
  function deleteOutputDirectory (line 12) | async function deleteOutputDirectory() {
  function finish (line 35) | function finish(errorMessage) {

FILE: test/helpers.js
  function wait (line 12) | function wait(ms) {
  function webpackCompile (line 28) | async function webpackCompile(config, version) {
  function makeWebpackConfig (line 72) | function makeWebpackConfig(opts = {}) {
  function forEachWebpackVersion (line 126) | function forEachWebpackVersion(versions, cb) {

FILE: test/parseUtils.js
  constant BUNDLES_DIR (line 6) | const BUNDLES_DIR = path.resolve(__dirname, "./bundles");

FILE: test/plugin.js
  function getChartDataFromJSONReport (line 13) | function getChartDataFromJSONReport(reportFilename = "report.json") {
  function getTitleFromReport (line 28) | async function getTitleFromReport(reportFilename = "report.html") {
  function getChartDataFromReport (line 36) | async function getChartDataFromReport(reportFilename = "report.html") {
  function expectValidReport (line 44) | async function expectValidReport(opts) {

FILE: test/stats/webpack-5-bundle-with-multiple-entries/bundle.js
  function n (line 1) | function n(o){if(t[o])return t[o].exports;var e=t[o]={exports:{}};return...

FILE: test/stats/webpack-5-bundle-with-single-entry/bundle.js
  function e (line 1) | function e(o){if(r[o])return r[o].exports;var t=r[o]={exports:{}};return...

FILE: test/stats/with-array-config/config-1-main.js
  function n (line 1) | function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{...

FILE: test/stats/with-array-config/config-2-main.js
  function n (line 1) | function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{...

FILE: test/stats/with-invalid-chunk/valid-chunk.js
  function t (line 1) | function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{...

FILE: test/stats/with-missing-chunk/valid-chunk.js
  function t (line 1) | function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{...

FILE: test/stats/with-missing-module-chunks/valid-chunk.js
  function t (line 1) | function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{...

FILE: test/stats/with-missing-parsed-module/bundle.js
  function __webpack_require__ (line 6) | function __webpack_require__(moduleId) {

FILE: test/stats/with-module-concatenation-info/bundle.js
  function t (line 1) | function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{...

FILE: test/stats/with-non-asset-asset/bundle.js
  function o (line 1) | function o(t){if(e[t])return e[t].exports;var p=e[t]={exports:{}};return...

FILE: test/stats/with-special-chars/bundle.js
  function r (line 1) | function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{...

FILE: test/stats/with-worker-loader-dynamic-import/bundle.js
  function r (line 1) | function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{...

FILE: test/stats/with-worker-loader-dynamic-import/bundle.worker.js
  function n (line 1) | function n(t){if(r[t])return r[t].exports;var o=r[t]={i:t,l:!1,exports:{...

FILE: test/stats/with-worker-loader/bundle.js
  function n (line 1) | function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{...

FILE: test/stats/with-worker-loader/bundle.worker.js
  function r (line 1) | function r(e){if(t[e])return t[e].exports;var u=t[e]={i:e,l:!1,exports:{...
  function Kt (line 9) | function Kt(n,t,r){switch(r.length){case 0:return n.call(t);case 1:retur...
  function Vt (line 9) | function Vt(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u...
  function Gt (line 9) | function Gt(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&!1!==t(n[r],r,...
  function Ht (line 9) | function Ht(n,t){for(var r=null==n?0:n.length;r--&&!1!==t(n[r],r,n););re...
  function Jt (line 9) | function Jt(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n...
  function Yt (line 9) | function Yt(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var ...
  function Qt (line 9) | function Qt(n,t){return!!(null==n?0:n.length)&&ar(n,t,0)>-1}
  function Xt (line 9) | function Xt(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e])...
  function nr (line 9) | function nr(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r...
  function tr (line 9) | function tr(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];r...
  function rr (line 9) | function rr(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);...
  function er (line 9) | function er(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)...
  function ur (line 9) | function ur(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n)...
  function or (line 9) | function or(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e...
  function fr (line 9) | function fr(n,t,r,e){for(var u=n.length,i=r+(e?1:-1);e?i--:++i<u;)if(t(n...
  function ar (line 9) | function ar(n,t,r){return t==t?function(n,t,r){var e=r-1,u=n.length;for(...
  function cr (line 9) | function cr(n,t,r,e){for(var u=r-1,i=n.length;++u<i;)if(e(n[u],t))return...
  function lr (line 9) | function lr(n){return n!=n}
  function sr (line 9) | function sr(n,t){var r=null==n?0:n.length;return r?_r(n,t)/r:U}
  function hr (line 9) | function hr(n){return function(t){return null==t?i:t[n]}}
  function pr (line 9) | function pr(n){return function(t){return null==n?i:n[t]}}
  function vr (line 9) | function vr(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=!1,n):t(r,n,u,i...
  function _r (line 9) | function _r(n,t){for(var r,e=-1,u=n.length;++e<u;){var o=t(n[e]);o!==i&&...
  function gr (line 9) | function gr(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}
  function yr (line 9) | function yr(n){return function(t){return n(t)}}
  function dr (line 9) | function dr(n,t){return nr(t,function(t){return n[t]})}
  function br (line 9) | function br(n,t){return n.has(t)}
  function wr (line 9) | function wr(n,t){for(var r=-1,e=n.length;++r<e&&ar(t,n[r],0)>-1;);return r}
  function mr (line 9) | function mr(n,t){for(var r=n.length;r--&&ar(t,n[r],0)>-1;);return r}
  function Ar (line 9) | function Ar(n){return"\\"+Et[n]}
  function Or (line 9) | function Or(n){return jt.test(n)}
  function kr (line 9) | function kr(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r...
  function Ir (line 9) | function Ir(n,t){return function(r){return n(t(r))}}
  function Rr (line 9) | function Rr(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!=...
  function Er (line 9) | function Er(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[+...
  function zr (line 9) | function zr(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[+...
  function Sr (line 9) | function Sr(n){return Or(n)?function(n){var t=mt.lastIndex=0;for(;mt.tes...
  function Lr (line 9) | function Lr(n){return Or(n)?function(n){return n.match(mt)||[]}(n):funct...
  function he (line 9) | function he(n){if(Rf(n)&&!yf(n)&&!(n instanceof ge)){if(n instanceof _e)...
  function n (line 9) | function n(){}
  function ve (line 9) | function ve(){}
  function _e (line 9) | function _e(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!...
  function ge (line 9) | function ge(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,thi...
  function ye (line 9) | function ye(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){va...
  function de (line 9) | function de(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){va...
  function be (line 9) | function be(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){va...
  function we (line 9) | function we(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new be;++...
  function me (line 9) | function me(n){var t=this.__data__=new de(n);this.size=t.size}
  function xe (line 9) | function xe(n,t){var r=yf(n),e=!r&&gf(n),u=!r&&!e&&mf(n),i=!r&&!e&&!u&&U...
  function je (line 9) | function je(n){var t=n.length;return t?n[wu(0,t-1)]:i}
  function Ae (line 9) | function Ae(n,t){return oo(ti(n),We(t,0,n.length))}
  function Oe (line 9) | function Oe(n){return oo(ti(n))}
  function ke (line 9) | function ke(n,t,r){(r===i||pf(n[t],r))&&(r!==i||t in n)||Se(n,t,r)}
  function Ie (line 9) | function Ie(n,t,r){var e=n[t];lt.call(n,t)&&pf(e,r)&&(r!==i||t in n)||Se...
  function Re (line 9) | function Re(n,t){for(var r=n.length;r--;)if(pf(n[r][0],t))return r;retur...
  function Ee (line 9) | function Ee(n,t,r,e){return $e(n,function(n,u,i){t(e,n,r(n),i)}),e}
  function ze (line 9) | function ze(n,t){return n&&ri(t,ua(t),n)}
  function Se (line 9) | function Se(n,t,r){"__proto__"==t&&ir?ir(n,t,{configurable:!0,enumerable...
  function Le (line 9) | function Le(n,t){for(var e=-1,u=t.length,o=r(u),f=null==n;++e<u;)o[e]=f?...
  function We (line 9) | function We(n,t,r){return n==n&&(r!==i&&(n=n<=r?n:r),t!==i&&(n=n>=t?n:t)...
  function Ce (line 9) | function Ce(n,t,r,e,u,o){var f,a=t&h,c=t&p,l=t&v;if(r&&(f=u?r(n,e,u,o):r...
  function Te (line 9) | function Te(n,t,r){var e=r.length;if(null==n)return!e;for(n=tt(n);e--;){...
  function Ue (line 9) | function Ue(n,t,r){if("function"!=typeof n)throw new ut(a);return ro(fun...
  function Be (line 9) | function Be(n,t,r,e){var u=-1,i=Qt,f=!0,a=n.length,c=[],l=t.length;if(!a...
  function Me (line 9) | function Me(n,t){var r=!0;return $e(n,function(n,e,u){return r=!!t(n,e,u...
  function Pe (line 9) | function Pe(n,t,r){for(var e=-1,u=n.length;++e<u;){var o=n[e],f=t(o);if(...
  function Fe (line 9) | function Fe(n,t){var r=[];return $e(n,function(n,e,u){t(n,e,u)&&r.push(n...
  function Ne (line 9) | function Ne(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=Ni),u||(u=[]);++i<o...
  function Ke (line 9) | function Ke(n,t){return n&&qe(n,t,ua)}
  function Ve (line 9) | function Ve(n,t){return n&&Ze(n,t,ua)}
  function Ge (line 9) | function Ge(n,t){return Yt(t,function(t){return Af(n[t])})}
  function He (line 9) | function He(n,t){for(var r=0,e=(t=Zu(t,n)).length;null!=n&&r<e;)n=n[ao(t...
  function Je (line 9) | function Je(n,t,r){var e=t(n);return yf(n)?e:tr(e,r(n))}
  function Ye (line 9) | function Ye(n){return null==n?n===i?on:Q:Dt&&Dt in tt(n)?function(n){var...
  function Qe (line 9) | function Qe(n,t){return n>t}
  function Xe (line 9) | function Xe(n,t){return null!=n&&lt.call(n,t)}
  function nu (line 9) | function nu(n,t){return null!=n&&t in tt(n)}
  function tu (line 9) | function tu(n,t,e){for(var u=e?Xt:Qt,o=n[0].length,f=n.length,a=f,c=r(f)...
  function ru (line 9) | function ru(n,t,r){var e=null==(n=Xi(n,t=Zu(t,n)))?n:n[ao(xo(t))];return...
  function eu (line 9) | function eu(n){return Rf(n)&&Ye(n)==P}
  function uu (line 9) | function uu(n,t,r,e,u){return n===t||(null==n||null==t||!Rf(n)&&!Rf(t)?n...
  function iu (line 9) | function iu(n,t,r,e){var u=r.length,o=u,f=!e;if(null==n)return!o;for(n=t...
  function ou (line 9) | function ou(n){return!(!If(n)||function(n){return!!ht&&ht in n}(n))&&(Af...
  function fu (line 9) | function fu(n){return"function"==typeof n?n:null==n?za:"object"==typeof ...
  function au (line 9) | function au(n){if(!Hi(n))return Nr(n);var t=[];for(var r in tt(n))lt.cal...
  function cu (line 9) | function cu(n){if(!If(n))return function(n){var t=[];if(null!=n)for(var ...
  function lu (line 9) | function lu(n,t){return n<t}
  function su (line 9) | function su(n,t){var e=-1,u=bf(n)?r(n.length):[];return $e(n,function(n,...
  function hu (line 9) | function hu(n){var t=Ui(n);return 1==t.length&&t[0][2]?Yi(t[0][0],t[0][1...
  function pu (line 9) | function pu(n,t){return Ki(n)&&Ji(t)?Yi(ao(n),t):function(r){var e=Xf(r,...
  function vu (line 9) | function vu(n,t,r,e,u){n!==t&&qe(t,function(o,f){if(u||(u=new me),If(o))...
  function _u (line 9) | function _u(n,t){var r=n.length;if(r)return qi(t+=t<0?r:0,r)?n[t]:i}
  function gu (line 9) | function gu(n,t,r){t=t.length?nr(t,function(n){return yf(n)?function(t){...
  function yu (line 9) | function yu(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=He(...
  function du (line 9) | function du(n,t,r,e){var u=e?cr:ar,i=-1,o=t.length,f=n;for(n===t&&(t=ti(...
  function bu (line 9) | function bu(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];if(r==e||...
  function wu (line 9) | function wu(n,t){return n+$r(Gr()*(t-n+1))}
  function mu (line 9) | function mu(n,t){var r="";if(!n||t<1||t>C)return r;do{t%2&&(r+=n),(t=$r(...
  function xu (line 9) | function xu(n,t){return eo(Qi(n,t,za),n+"")}
  function ju (line 9) | function ju(n){return je(pa(n))}
  function Au (line 9) | function Au(n,t){var r=pa(n);return oo(r,We(t,0,r.length))}
  function Ou (line 9) | function Ou(n,t,r,e){if(!If(n))return n;for(var u=-1,o=(t=Zu(t,n)).lengt...
  function Ru (line 9) | function Ru(n){return oo(pa(n))}
  function Eu (line 9) | function Eu(n,t,e){var u=-1,i=n.length;t<0&&(t=-t>i?0:i+t),(e=e>i?i:e)<0...
  function zu (line 9) | function zu(n,t){var r;return $e(n,function(n,e,u){return!(r=t(n,e,u))})...
  function Su (line 9) | function Su(n,t,r){var e=0,u=null==n?e:n.length;if("number"==typeof t&&t...
  function Lu (line 9) | function Lu(n,t,r,e){var u=0,o=null==n?0:n.length;if(0===o)return 0;for(...
  function Wu (line 9) | function Wu(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r],f=t...
  function Cu (line 9) | function Cu(n){return"number"==typeof n?n:Tf(n)?U:+n}
  function Tu (line 9) | function Tu(n){if("string"==typeof n)return n;if(yf(n))return nr(n,Tu)+"...
  function Uu (line 9) | function Uu(n,t,r){var e=-1,u=Qt,i=n.length,f=!0,a=[],c=a;if(r)f=!1,u=Xt...
  function Bu (line 9) | function Bu(n,t){return null==(n=Xi(n,t=Zu(t,n)))||delete n[ao(xo(t))]}
  function $u (line 9) | function $u(n,t,r,e){return Ou(n,t,r(He(n,t)),e)}
  function Du (line 9) | function Du(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i...
  function Mu (line 9) | function Mu(n,t){var r=n;return r instanceof ge&&(r=r.value()),rr(t,func...
  function Pu (line 9) | function Pu(n,t,e){var u=n.length;if(u<2)return u?Uu(n[0]):[];for(var i=...
  function Fu (line 9) | function Fu(n,t,r){for(var e=-1,u=n.length,o=t.length,f={};++e<u;){var a...
  function Nu (line 9) | function Nu(n){return wf(n)?n:[]}
  function qu (line 9) | function qu(n){return"function"==typeof n?n:za}
  function Zu (line 9) | function Zu(n,t){return yf(n)?n:Ki(n,t)?[n]:fo(Zf(n))}
  function Vu (line 9) | function Vu(n,t,r){var e=n.length;return r=r===i?e:r,!t&&r>=e?n:Eu(n,t,r)}
  function Hu (line 9) | function Hu(n,t){if(t)return n.slice();var r=n.length,e=jt?jt(r):new n.c...
  function Ju (line 9) | function Ju(n){var t=new n.constructor(n.byteLength);return new mt(t).se...
  function Yu (line 9) | function Yu(n,t){var r=t?Ju(n.buffer):n.buffer;return new n.constructor(...
  function Qu (line 9) | function Qu(n,t){if(n!==t){var r=n!==i,e=null===n,u=n==n,o=Tf(n),f=t!==i...
  function Xu (line 9) | function Xu(n,t,e,u){for(var i=-1,o=n.length,f=e.length,a=-1,c=t.length,...
  function ni (line 9) | function ni(n,t,e,u){for(var i=-1,o=n.length,f=-1,a=e.length,c=-1,l=t.le...
  function ti (line 9) | function ti(n,t){var e=-1,u=n.length;for(t||(t=r(u));++e<u;)t[e]=n[e];re...
  function ri (line 9) | function ri(n,t,r,e){var u=!r;r||(r={});for(var o=-1,f=t.length;++o<f;){...
  function ei (line 9) | function ei(n,t){return function(r,e){var u=yf(r)?Vt:Ee,i=t?t():{};retur...
  function ui (line 9) | function ui(n){return xu(function(t,r){var e=-1,u=r.length,o=u>1?r[u-1]:...
  function ii (line 9) | function ii(n,t){return function(r,e){if(null==r)return r;if(!bf(r))retu...
  function oi (line 9) | function oi(n){return function(t,r,e){for(var u=-1,i=tt(t),o=e(t),f=o.le...
  function fi (line 9) | function fi(n){return function(t){var r=Or(t=Zf(t))?Lr(t):i,e=r?r[0]:t.c...
  function ai (line 9) | function ai(n){return function(t){return rr(Aa(ga(t).replace(bt,"")),n,"...
  function ci (line 9) | function ci(n){return function(){var t=arguments;switch(t.length){case 0...
  function li (line 9) | function li(n){return function(t,r,e){var u=tt(t);if(!bf(t)){var o=Ci(r,...
  function si (line 9) | function si(n){return Ri(function(t){var r=t.length,e=r,u=_e.prototype.t...
  function hi (line 9) | function hi(n,t,e,u,o,f,a,c,l,s){var h=t&A,p=t&y,v=t&d,_=t&(w|m),g=t&k,b...
  function pi (line 9) | function pi(n,t){return function(r,e){return function(n,t,r,e){return Ke...
  function vi (line 9) | function vi(n,t){return function(r,e){var u;if(r===i&&e===i)return t;if(...
  function _i (line 9) | function _i(n){return Ri(function(t){return t=nr(t,yr(Ci())),xu(function...
  function gi (line 9) | function gi(n,t){var r=(t=t===i?" ":Tu(t)).length;if(r<2)return r?mu(t,n...
  function yi (line 9) | function yi(n){return function(t,e,u){return u&&"number"!=typeof u&&Zi(t...
  function di (line 9) | function di(n){return function(t,r){return"string"==typeof t&&"string"==...
  function bi (line 9) | function bi(n,t,r,e,u,o,f,a,c,l){var s=t&w;t|=s?x:j,(t&=~(s?j:x))&b||(t&...
  function wi (line 9) | function wi(n){var t=nt[n];return function(n,r){if(n=Nf(n),(r=null==r?0:...
  function xi (line 9) | function xi(n){return function(t){var r=Mi(t);return r==J?kr(t):r==rn?zr...
  function ji (line 9) | function ji(n,t,e,u,o,f,c,l){var h=t&d;if(!h&&"function"!=typeof n)throw...
  function Ai (line 9) | function Ai(n,t,r,e){return n===i||pf(n,ft[r])&&!lt.call(e,r)?t:n}
  function Oi (line 9) | function Oi(n,t,r,e,u,o){return If(n)&&If(t)&&(o.set(t,n),vu(n,t,i,Oi,o)...
  function ki (line 9) | function ki(n){return Sf(n)?i:n}
  function Ii (line 9) | function Ii(n,t,r,e,u,o){var f=r&_,a=n.length,c=t.length;if(a!=c&&!(f&&c...
  function Ri (line 9) | function Ri(n){return eo(Qi(n,i,go),n+"")}
  function Ei (line 9) | function Ei(n){return Je(n,ua,$i)}
  function zi (line 9) | function zi(n){return Je(n,ia,Di)}
  function Li (line 9) | function Li(n){for(var t=n.name+"",r=ee[t],e=lt.call(ee,t)?r.length:0;e-...
  function Wi (line 9) | function Wi(n){return(lt.call(he,"placeholder")?he:n).placeholder}
  function Ci (line 9) | function Ci(){var n=he.iteratee||Sa;return n=n===Sa?fu:n,arguments.lengt...
  function Ti (line 9) | function Ti(n,t){var r=n.__data__;return function(n){var t=typeof n;retu...
  function Ui (line 9) | function Ui(n){for(var t=ua(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[...
  function Bi (line 9) | function Bi(n,t){var r=function(n,t){return null==n?i:n[t]}(n,t);return ...
  function Pi (line 9) | function Pi(n,t,r){for(var e=-1,u=(t=Zu(t,n)).length,i=!1;++e<u;){var o=...
  function Fi (line 9) | function Fi(n){return"function"!=typeof n.constructor||Hi(n)?{}:pe(Et(n))}
  function Ni (line 9) | function Ni(n){return yf(n)||gf(n)||!!(Ut&&n&&n[Ut])}
  function qi (line 9) | function qi(n,t){var r=typeof n;return!!(t=null==t?C:t)&&("number"==r||"...
  function Zi (line 9) | function Zi(n,t,r){if(!If(r))return!1;var e=typeof t;return!!("number"==...
  function Ki (line 9) | function Ki(n,t){if(yf(n))return!1;var r=typeof n;return!("number"!=r&&"...
  function Vi (line 9) | function Vi(n){var t=Li(n),r=he[t];if("function"!=typeof r||!(t in ge.pr...
  function Hi (line 9) | function Hi(n){var t=n&&n.constructor;return n===("function"==typeof t&&...
  function Ji (line 9) | function Ji(n){return n==n&&!If(n)}
  function Yi (line 9) | function Yi(n,t){return function(r){return null!=r&&r[n]===t&&(t!==i||n ...
  function Qi (line 9) | function Qi(n,t,e){return t=qr(t===i?n.length-1:t,0),function(){for(var ...
  function Xi (line 9) | function Xi(n,t){return t.length<2?n:He(n,Eu(t,0,-1))}
  function no (line 9) | function no(n,t){if(("constructor"!==t||"function"!=typeof n[t])&&"__pro...
  function uo (line 9) | function uo(n,t,r){var e=t+"";return eo(n,function(n,t){var r=t.length;i...
  function io (line 9) | function io(n){var t=0,r=0;return function(){var e=Kr(),u=z-(e-r);if(r=e...
  function oo (line 9) | function oo(n,t){var r=-1,e=n.length,u=e-1;for(t=t===i?e:t;++r<t;){var o...
  function ao (line 9) | function ao(n){if("string"==typeof n||Tf(n))return n;var t=n+"";return"0...
  function co (line 9) | function co(n){if(null!=n){try{return ct.call(n)}catch(n){}try{return n+...
  function lo (line 9) | function lo(n){if(n instanceof ge)return n.clone();var t=new _e(n.__wrap...
  function vo (line 9) | function vo(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=null==r...
  function _o (line 9) | function _o(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;ret...
  function go (line 9) | function go(n){return null!=n&&n.length?Ne(n,1):[]}
  function yo (line 9) | function yo(n){return n&&n.length?n[0]:i}
  function xo (line 9) | function xo(n){var t=null==n?0:n.length;return t?n[t-1]:i}
  function Ao (line 9) | function Ao(n,t){return n&&n.length&&t&&t.length?du(n,t):n}
  function ko (line 9) | function ko(n){return null==n?n:Hr.call(n)}
  function zo (line 9) | function zo(n){if(!n||!n.length)return[];var t=0;return n=Yt(n,function(...
  function So (line 9) | function So(n,t){if(!n||!n.length)return[];var r=zo(n);return null==t?r:...
  function $o (line 9) | function $o(n){var t=he(n);return t.__chain__=!0,t}
  function Do (line 9) | function Do(n,t){return t(n)}
  function qo (line 9) | function qo(n,t){return(yf(n)?Gt:$e)(n,Ci(t,3))}
  function Zo (line 9) | function Zo(n,t){return(yf(n)?Ht:De)(n,Ci(t,3))}
  function Ho (line 9) | function Ho(n,t){return(yf(n)?nr:su)(n,Ci(t,3))}
  function Xo (line 9) | function Xo(n,t,r){return t=r?i:t,t=n&&null==t?n.length:t,ji(n,A,i,i,i,i...
  function nf (line 9) | function nf(n,t){var r;if("function"!=typeof t)throw new ut(a);return n=...
  function ef (line 9) | function ef(n,t,r){var e,u,o,f,c,l,s=0,h=!1,p=!1,v=!0;if("function"!=typ...
  function ff (line 9) | function ff(n,t){if("function"!=typeof n||null!=t&&"function"!=typeof t)...
  function af (line 9) | function af(n){if("function"!=typeof n)throw new ut(a);return function()...
  function pf (line 9) | function pf(n,t){return n===t||n!=n&&t!=t}
  function bf (line 9) | function bf(n){return null!=n&&kf(n.length)&&!Af(n)}
  function wf (line 9) | function wf(n){return Rf(n)&&bf(n)}
  function jf (line 9) | function jf(n){if(!Rf(n))return!1;var t=Ye(n);return t==V||t==K||"string...
  function Af (line 9) | function Af(n){if(!If(n))return!1;var t=Ye(n);return t==G||t==H||t==N||t...
  function Of (line 9) | function Of(n){return"number"==typeof n&&n==Pf(n)}
  function kf (line 9) | function kf(n){return"number"==typeof n&&n>-1&&n%1==0&&n<=C}
  function If (line 9) | function If(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}
  function Rf (line 9) | function Rf(n){return null!=n&&"object"==typeof n}
  function zf (line 9) | function zf(n){return"number"==typeof n||Rf(n)&&Ye(n)==Y}
  function Sf (line 9) | function Sf(n){if(!Rf(n)||Ye(n)!=X)return!1;var t=Et(n);if(null===t)retu...
  function Cf (line 9) | function Cf(n){return"string"==typeof n||!yf(n)&&Rf(n)&&Ye(n)==en}
  function Tf (line 9) | function Tf(n){return"symbol"==typeof n||Rf(n)&&Ye(n)==un}
  function Df (line 9) | function Df(n){if(!n)return[];if(bf(n))return Cf(n)?Lr(n):ti(n);if($t&&n...
  function Mf (line 9) | function Mf(n){return n?(n=Nf(n))===W||n===-W?(n<0?-1:1)*T:n==n?n:0:0===...
  function Pf (line 9) | function Pf(n){var t=Mf(n),r=t%1;return t==t?r?t-r:t:0}
  function Ff (line 9) | function Ff(n){return n?We(Pf(n),0,B):0}
  function Nf (line 9) | function Nf(n){if("number"==typeof n)return n;if(Tf(n))return U;if(If(n)...
  function qf (line 9) | function qf(n){return ri(n,ia(n))}
  function Zf (line 9) | function Zf(n){return null==n?"":Tu(n)}
  function Xf (line 9) | function Xf(n,t,r){var e=null==n?i:He(n,t);return e===i?r:e}
  function na (line 9) | function na(n,t){return null!=n&&Pi(n,t,nu)}
  function ua (line 9) | function ua(n){return bf(n)?xe(n):au(n)}
  function ia (line 9) | function ia(n){return bf(n)?xe(n,!0):cu(n)}
  function la (line 9) | function la(n,t){if(null==n)return{};var r=nr(zi(n),function(n){return[n...
  function pa (line 9) | function pa(n){return null==n?[]:dr(n,ua(n))}
  function _a (line 9) | function _a(n){return ja(Zf(n).toLowerCase())}
  function ga (line 9) | function ga(n){return(n=Zf(n))&&n.replace(Jn,xr).replace(wt,"")}
  function Aa (line 9) | function Aa(n,t,r){return n=Zf(n),(t=r?i:t)===i?function(n){return At.te...
  function Ia (line 9) | function Ia(n){return function(){return n}}
  function za (line 9) | function za(n){return n}
  function Sa (line 9) | function Sa(n){return fu("function"==typeof n?n:Ce(n,h))}
  function Ca (line 9) | function Ca(n,t,r){var e=ua(t),u=Ge(t,e);null!=r||If(t)&&(u.length||!e.l...
  function Ta (line 9) | function Ta(){}
  function Da (line 9) | function Da(n){return Ki(n)?hr(ao(n)):function(n){return function(t){ret...
  function Fa (line 9) | function Fa(){return[]}
  function Na (line 9) | function Na(){return!1}

FILE: test/statsUtils.js
  function stringify (line 6) | async function stringify(json) {
  function expectProperJson (line 17) | async function expectProperJson(json) {
Condensed preview — 190 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,130K chars).
[
  {
    "path": ".babelrc",
    "chars": 184,
    "preview": "// Babel config for Node\n// Compiles sources, gulpfile and tests\n{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n     "
  },
  {
    "path": ".browserslistrc",
    "chars": 109,
    "preview": "# Supported browsers\n\nlast 2 Chrome major versions\nlast 2 Firefox major versions\nlast 1 Safari major version\n"
  },
  {
    "path": ".editorconfig",
    "chars": 190,
    "preview": "root = true\n\n[*]\ncharset = utf-8\n\nindent_style = space\nindent_size = 2\n\nend_of_line = lf\ninsert_final_newline = true\ntri"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 1517,
    "preview": "name: main\non:\n  push:\n    branches:\n      - main\n  pull_request:\njobs:\n  build-and-test:\n    strategy:\n      fail-fast:"
  },
  {
    "path": ".gitignore",
    "chars": 62,
    "preview": "/lib\n/public\n/samples\nnode_modules\nnpm-debug.log\n.eslintcache\n"
  },
  {
    "path": ".npm-upgrade.json",
    "chars": 325,
    "preview": "{\n  \"ignore\": {\n    \"mobx\": {\n      \"versions\": \">=6\",\n      \"reason\": \"v6 drops decorators\"\n    },\n    \"mobx-react\": {\n"
  },
  {
    "path": ".nvmrc",
    "chars": 9,
    "preview": "v22.14.0\n"
  },
  {
    "path": ".prettierignore",
    "chars": 69,
    "preview": "test/bundles/**\ntest/stats/**\ntest/output/**\nsamples/**\nCHANGELOG.md\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 26027,
    "preview": "# Changelog\n\n> **Tags:**\n> - [Breaking Change]\n> - [New Feature]\n> - [Improvement]\n> - [Bug Fix]\n> - [Internal]\n> - [Doc"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3066,
    "preview": "# Contributing\n\nTo contribute to `webpack-bundle-analyzer`, fork the repository and clone it to your machine. [See this "
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "Copyright JS Foundation and other contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na "
  },
  {
    "path": "README.md",
    "chars": 18473,
    "preview": "[![npm][npm]][npm-url]\n[![node][node]][node-url]\n[![tests][tests]][tests-url]\n[![downloads][downloads]][downloads-url]\n\n"
  },
  {
    "path": "bin/install-test-webpack-versions.sh",
    "chars": 108,
    "preview": "#!/usr/bin/env bash\n\nfor dir in \"$(dirname \"$0\")\"/../test/webpack-versions/*; do (cd \"$dir\" && npm i); done\n"
  },
  {
    "path": "client/.eslintrc.json",
    "chars": 320,
    "preview": "{\n  \"extends\": [\"th0r-react\", \"../.eslintrc.json\"],\n  \"settings\": {\n    \"react\": {\n      \"version\": \"16.2\"\n    }\n  },\n  "
  },
  {
    "path": "client/components/Button.css",
    "chars": 522,
    "preview": ".button {\n  background: var(--bg-primary);\n  border: 1px solid var(--border-color);\n  border-radius: 4px;\n  cursor: poin"
  },
  {
    "path": "client/components/Button.jsx",
    "chars": 1124,
    "preview": "import cls from \"classnames\";\nimport PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent.jsx\";\n"
  },
  {
    "path": "client/components/Checkbox.css",
    "chars": 185,
    "preview": ".label {\n  cursor: pointer;\n  display: inline-block;\n}\n\n.checkbox {\n  cursor: pointer;\n}\n\n.itemText {\n  margin-left: 3px"
  },
  {
    "path": "client/components/Checkbox.jsx",
    "chars": 836,
    "preview": "import cls from \"classnames\";\nimport { Component } from \"preact\";\nimport PropTypes from \"prop-types\";\n\nimport * as style"
  },
  {
    "path": "client/components/CheckboxList.css",
    "chars": 176,
    "preview": ".container {\n  font: var(--main-font);\n  white-space: nowrap;\n}\n\n.label {\n  font-size: 11px;\n  font-weight: bold;\n  marg"
  },
  {
    "path": "client/components/CheckboxList.jsx",
    "chars": 3168,
    "preview": "import PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent.jsx\";\nimport * as styles from \"./Che"
  },
  {
    "path": "client/components/CheckboxListItem.jsx",
    "chars": 966,
    "preview": "import { Component } from \"preact\";\nimport PropTypes from \"prop-types\";\n\nimport Checkbox from \"./Checkbox.jsx\";\nimport *"
  },
  {
    "path": "client/components/ContextMenu.css",
    "chars": 337,
    "preview": ".container {\n  font: var(--main-font);\n  position: absolute;\n  padding: 0;\n  border-radius: 4px;\n  background: #fff;\n  b"
  },
  {
    "path": "client/components/ContextMenu.jsx",
    "chars": 4015,
    "preview": "import cls from \"classnames\";\nimport PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent.jsx\";\n"
  },
  {
    "path": "client/components/ContextMenuItem.css",
    "chars": 226,
    "preview": ".item {\n  cursor: pointer;\n  margin: 0;\n  padding: 8px 14px;\n  user-select: none;\n}\n\n.item:hover {\n  background: #ffefd7"
  },
  {
    "path": "client/components/ContextMenuItem.jsx",
    "chars": 974,
    "preview": "import cls from \"classnames\";\nimport PropTypes from \"prop-types\";\n\nimport * as styles from \"./ContextMenuItem.css\";\n\n/**"
  },
  {
    "path": "client/components/Dropdown.css",
    "chars": 472,
    "preview": ".container {\n  font: var(--main-font);\n  white-space: nowrap;\n}\n\n.label {\n  font-size: 11px;\n  font-weight: bold;\n  marg"
  },
  {
    "path": "client/components/Dropdown.jsx",
    "chars": 2635,
    "preview": "import { createRef } from \"preact\";\nimport PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent."
  },
  {
    "path": "client/components/Icon.css",
    "chars": 148,
    "preview": ".icon {\n  background: no-repeat center/contain;\n  display: inline-block;\n  filter: invert(0);\n}\n\n[data-theme=\"dark\"] .ic"
  },
  {
    "path": "client/components/Icon.jsx",
    "chars": 1493,
    "preview": "import cls from \"classnames\";\nimport PropTypes from \"prop-types\";\nimport iconArrowRight from \"../assets/icon-arrow-right"
  },
  {
    "path": "client/components/ModuleItem.css",
    "chars": 623,
    "preview": ".container {\n  background: no-repeat left center;\n  cursor: pointer;\n  margin-bottom: 4px;\n  padding-left: 18px;\n  posit"
  },
  {
    "path": "client/components/ModuleItem.jsx",
    "chars": 2788,
    "preview": "import cls from \"classnames\";\nimport escapeRegExp from \"escape-string-regexp\";\nimport { filesize } from \"filesize\";\nimpo"
  },
  {
    "path": "client/components/ModulesList.css",
    "chars": 41,
    "preview": ".container {\n  font: var(--main-font);\n}\n"
  },
  {
    "path": "client/components/ModulesList.jsx",
    "chars": 1162,
    "preview": "import cls from \"classnames\";\nimport PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent.jsx\";\n"
  },
  {
    "path": "client/components/ModulesTreemap.css",
    "chars": 740,
    "preview": ".container {\n  align-items: stretch;\n  display: flex;\n  height: 100%;\n  position: relative;\n  width: 100%;\n}\n\n.map {\n  f"
  },
  {
    "path": "client/components/ModulesTreemap.jsx",
    "chars": 11543,
    "preview": "import { filesize } from \"filesize\";\nimport { computed, makeObservable } from \"mobx\";\nimport { observer } from \"mobx-rea"
  },
  {
    "path": "client/components/Search.css",
    "chars": 590,
    "preview": ".container {\n  font: var(--main-font);\n  white-space: nowrap;\n}\n\n.label {\n  font-weight: bold;\n  margin-bottom: 7px;\n}\n\n"
  },
  {
    "path": "client/components/Search.jsx",
    "chars": 2242,
    "preview": "// TODO: switch to a more modern debounce package once we drop Node.js 10 support\nimport debounce from \"debounce\";\n\nimpo"
  },
  {
    "path": "client/components/Sidebar.css",
    "chars": 1390,
    "preview": ".container {\n  background: var(--bg-primary);\n  border: none;\n  border-right: 1px solid var(--border-color);\n  box-sizin"
  },
  {
    "path": "client/components/Sidebar.jsx",
    "chars": 5076,
    "preview": "import cls from \"classnames\";\nimport { Component } from \"preact\";\nimport PropTypes from \"prop-types\";\nimport Button from"
  },
  {
    "path": "client/components/Switcher.css",
    "chars": 177,
    "preview": ".container {\n  font: var(--main-font);\n  white-space: nowrap;\n}\n\n.label {\n  font-weight: bold;\n  font-size: 11px;\n  marg"
  },
  {
    "path": "client/components/Switcher.jsx",
    "chars": 995,
    "preview": "import PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent.jsx\";\nimport * as styles from \"./Swi"
  },
  {
    "path": "client/components/SwitcherItem.jsx",
    "chars": 598,
    "preview": "import PropTypes from \"prop-types\";\nimport PureComponent from \"../lib/PureComponent.jsx\";\nimport Button from \"./Button.j"
  },
  {
    "path": "client/components/ThemeToggle.css",
    "chars": 368,
    "preview": ".themeToggle {\n  background: transparent;\n  border: none;\n  cursor: pointer;\n  padding: 8px;\n  display: flex;\n  align-it"
  },
  {
    "path": "client/components/ThemeToggle.jsx",
    "chars": 703,
    "preview": "import { observer } from \"mobx-react\";\nimport { Component } from \"preact\";\n\nimport { store } from \"../store.js\";\nimport "
  },
  {
    "path": "client/components/Tooltip.css",
    "chars": 479,
    "preview": ".container {\n  font: var(--main-font);\n  position: absolute;\n  padding: 5px 10px;\n  border-radius: 4px;\n  background: #f"
  },
  {
    "path": "client/components/Tooltip.jsx",
    "chars": 1979,
    "preview": "import cls from \"classnames\";\nimport { Component } from \"preact\";\n\nimport PropTypes from \"prop-types\";\nimport * as style"
  },
  {
    "path": "client/components/Treemap.jsx",
    "chars": 7602,
    "preview": "import FoamTree from \"@carrotsearch/foamtree\";\nimport { Component } from \"preact\";\nimport PropTypes from \"prop-types\";\ni"
  },
  {
    "path": "client/components/types.js",
    "chars": 1533,
    "preview": "import PropTypes from \"prop-types\";\n\nexport const GroupType = PropTypes.shape({\n  cid: PropTypes.number.isRequired,\n  la"
  },
  {
    "path": "client/lib/PureComponent.jsx",
    "chars": 646,
    "preview": "import { Component } from \"preact\";\n\n/**\n * @param {object} obj1 obj1\n * @param {object} obj2 obj2\n * @returns {boolean}"
  },
  {
    "path": "client/localStorage.js",
    "chars": 550,
    "preview": "const KEY_PREFIX = \"wba\";\n\nexport default {\n  getItem(key) {\n    try {\n      return JSON.parse(\n        globalThis.local"
  },
  {
    "path": "client/store.js",
    "chars": 6430,
    "preview": "import { action, computed, makeObservable, observable } from \"mobx\";\nimport localStorage from \"./localStorage.js\";\nimpor"
  },
  {
    "path": "client/utils.js",
    "chars": 809,
    "preview": "/**\n * @param {Chunk} chunk chunk\n * @returns {boolean} true when chunk is parser, otherwise false\n */\nexport function i"
  },
  {
    "path": "client/viewer.css",
    "chars": 959,
    "preview": ":root {\n  --main-font: normal 11px Verdana, sans-serif;\n  --bg-primary: #fff;\n  --bg-secondary: #f5f5f5;\n  --text-primar"
  },
  {
    "path": "client/viewer.jsx",
    "chars": 1033,
    "preview": "import { render } from \"preact\";\n\nimport ModulesTreemap from \"./components/ModulesTreemap.jsx\";\nimport { store } from \"."
  },
  {
    "path": "eslint.config.mjs",
    "chars": 1075,
    "preview": "import { defineConfig, globalIgnores } from \"eslint/config\";\nimport config from \"eslint-config-webpack\";\nimport configs "
  },
  {
    "path": "jest.config.js",
    "chars": 533,
    "preview": "\"use strict\";\n\n// Jest configuration\n// Reference: https://jestjs.io/docs/configuration\n\nmodule.exports = {\n  testTimeou"
  },
  {
    "path": "package.json",
    "chars": 3678,
    "preview": "{\n  \"name\": \"webpack-bundle-analyzer\",\n  \"version\": \"5.2.0\",\n  \"description\": \"Webpack plugin and CLI utility that repre"
  },
  {
    "path": "prettier.config.mjs",
    "chars": 102,
    "preview": "export default {\n  printWidth: 80,\n  tabWidth: 2,\n  trailingComma: \"all\",\n  arrowParens: \"always\",\n};\n"
  },
  {
    "path": "src/BundleAnalyzerPlugin.js",
    "chars": 8386,
    "preview": "const fs = require(\"node:fs\");\nconst path = require(\"node:path\");\nconst { bold } = require(\"picocolors\");\n\nconst Logger "
  },
  {
    "path": "src/Logger.js",
    "chars": 2134,
    "preview": "/** @typedef {import(\"./BundleAnalyzerPlugin\").EXPECTED_ANY} EXPECTED_ANY */\n\n/** @typedef {\"debug\" | \"info\" | \"warn\" | "
  },
  {
    "path": "src/analyzer.js",
    "chars": 14684,
    "preview": "const fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst { parseChunked } = require(\"@discoveryjs/json-e"
  },
  {
    "path": "src/bin/analyzer.js",
    "chars": 5879,
    "preview": "#! /usr/bin/env node\n\nconst { dirname, resolve } = require(\"node:path\");\n\nconst { program: commanderProgram } = require("
  },
  {
    "path": "src/index.js",
    "chars": 130,
    "preview": "const { start } = require(\"./viewer\");\n\nmodule.exports = {\n  start,\n  BundleAnalyzerPlugin: require(\"./BundleAnalyzerPlu"
  },
  {
    "path": "src/parseUtils.js",
    "chars": 14504,
    "preview": "/** @typedef {import(\"acorn\").Node} Node */\n/** @typedef {import(\"acorn\").CallExpression} CallExpression */\n/** @typedef"
  },
  {
    "path": "src/sizeUtils.js",
    "chars": 756,
    "preview": "import zlib from \"node:zlib\";\n\nexport const isZstdSupported = \"createZstdCompress\" in zlib;\n\n/** @typedef {\"gzip\" | \"bro"
  },
  {
    "path": "src/statsUtils.js",
    "chars": 2391,
    "preview": "const { createWriteStream } = require(\"node:fs\");\nconst { Readable } = require(\"node:stream\");\nconst { pipeline } = requ"
  },
  {
    "path": "src/template.js",
    "chars": 5935,
    "preview": "const fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst { escape } = require(\"html-escaper\");\n\nconst pr"
  },
  {
    "path": "src/tree/BaseFolder.js",
    "chars": 4804,
    "preview": "import Node from \"./Node.js\";\n\n/** @typedef {import(\"./Folder\").default} Folder */\n/** @typedef {import(\"./Module\").defa"
  },
  {
    "path": "src/tree/ConcatenatedModule.js",
    "chars": 4699,
    "preview": "import ContentFolder from \"./ContentFolder.js\";\nimport ContentModule from \"./ContentModule.js\";\nimport Module from \"./Mo"
  },
  {
    "path": "src/tree/ContentFolder.js",
    "chars": 1903,
    "preview": "import BaseFolder from \"./BaseFolder.js\";\n\n/** @typedef {import(\"./Node\").default} Node */\n/** @typedef {import(\"./Conca"
  },
  {
    "path": "src/tree/ContentModule.js",
    "chars": 1756,
    "preview": "import Module from \"./Module.js\";\n\n/** @typedef {import(\"webpack\").StatsModule} StatsModule */\n/** @typedef {import(\"./N"
  },
  {
    "path": "src/tree/Folder.js",
    "chars": 3725,
    "preview": "import { getCompressedSize } from \"../sizeUtils.js\";\nimport BaseFolder from \"./BaseFolder.js\";\nimport ConcatenatedModule"
  },
  {
    "path": "src/tree/Module.js",
    "chars": 3682,
    "preview": "import { getCompressedSize } from \"../sizeUtils.js\";\nimport Node from \"./Node.js\";\n\n/** @typedef {import(\"webpack\").Stat"
  },
  {
    "path": "src/tree/Node.js",
    "chars": 542,
    "preview": "export default class Node {\n  /**\n   * @param {string} name name\n   * @param {Node=} parent parent\n   */\n  constructor(n"
  },
  {
    "path": "src/tree/utils.js",
    "chars": 880,
    "preview": "const MULTI_MODULE_REGEXP = /^multi /u;\n\n/** @typedef {import(\"webpack\").StatsModule} StatsModule */\n\n/**\n * @param {Sta"
  },
  {
    "path": "src/utils.js",
    "chars": 2697,
    "preview": "/** @typedef {import(\"net\").AddressInfo} AddressInfo */\n/** @typedef {import(\"webpack\").StatsAsset} StatsAsset */\n\nconst"
  },
  {
    "path": "src/viewer.js",
    "chars": 9674,
    "preview": "const fs = require(\"node:fs\");\nconst http = require(\"node:http\");\nconst path = require(\"node:path\");\n\nconst { bold } = r"
  },
  {
    "path": "test/.eslintrc.json",
    "chars": 204,
    "preview": "{\n  \"extends\": \"../.eslintrc.json\",\n  \"env\": {\n    \"jest\": true,\n    \"browser\": true\n  },\n  \"globals\": {\n    \"makeWebpac"
  },
  {
    "path": "test/.gitignore",
    "chars": 77,
    "preview": "output\n\n# Sandbox config\n/webpack.config.js\n# Output of sandbox config\n/dist\n"
  },
  {
    "path": "test/Logger.js",
    "chars": 2574,
    "preview": "const Logger = require(\"../src/Logger\");\n\nclass TestLogger extends Logger {\n  constructor(level) {\n    super(level);\n   "
  },
  {
    "path": "test/analyzer.js",
    "chars": 12395,
    "preview": "const childProcess = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\nco"
  },
  {
    "path": "test/bundles/invalidBundle.js",
    "chars": 35,
    "preview": "module.exports = 'invalid bundle';\n"
  },
  {
    "path": "test/bundles/validBundleWithArrowFunction.js",
    "chars": 65,
    "preview": "webpackJsonp([0],[(t,e,r)=>{\n  console.log(\"Hello world!\");\n}]);\n"
  },
  {
    "path": "test/bundles/validBundleWithArrowFunction.modules.json",
    "chars": 82,
    "preview": "{\n  \"modules\": {\n    \"0\": \"(t,e,r)=>{\\n  console.log(\\\"Hello world!\\\");\\n}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validBundleWithEsNextFeatures.js",
    "chars": 428,
    "preview": "webpackJsonp([0],[function(t,e,r){\n  async function asyncFn() {\n    return await Promise.resolve(1);\n  }\n\n  const arrowF"
  },
  {
    "path": "test/bundles/validBundleWithEsNextFeatures.modules.json",
    "chars": 468,
    "preview": "{\n  \"modules\": {\n    \"0\": \"function(t,e,r){\\n  async function asyncFn() {\\n    return await Promise.resolve(1);\\n  }\\n\\n"
  },
  {
    "path": "test/bundles/validBundleWithIIFE.js",
    "chars": 39,
    "preview": "(()=>{const e=console.log(\"foo\");})();\n"
  },
  {
    "path": "test/bundles/validBundleWithIIFE.modules.json",
    "chars": 20,
    "preview": "{\n  \"modules\": {}\n}\n"
  },
  {
    "path": "test/bundles/validCommonBundleWithDedupePlugin.js",
    "chars": 573,
    "preview": "!function(t){function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports"
  },
  {
    "path": "test/bundles/validCommonBundleWithDedupePlugin.modules.json",
    "chars": 179,
    "preview": "{\n  \"modules\": {\n    \"0\": \"function(t,r,e){e(1),e(2)}\",\n    \"1\": \"function(t,r){t.exports=1}\",\n    \"2\": \"1\",\n    \"4\": \"["
  },
  {
    "path": "test/bundles/validCommonBundleWithModulesAsArray.js",
    "chars": 906,
    "preview": "!function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports"
  },
  {
    "path": "test/bundles/validCommonBundleWithModulesAsArray.modules.json",
    "chars": 219,
    "preview": "{\n  \"modules\": {\n    \"0\": \"function(e,t,n){n(1),n(21),n(96),n(306),n(23),n(150),n(57),n(56),n(34),n(138),e.exports=n(348"
  },
  {
    "path": "test/bundles/validCommonBundleWithModulesAsObject.js",
    "chars": 1117,
    "preview": "!function(t){function e(n){if(i[n])return i[n].exports;var r=i[n]={exports:{},id:n,loaded:!1};return t[n].call(r.exports"
  },
  {
    "path": "test/bundles/validCommonBundleWithModulesAsObject.modules.json",
    "chars": 407,
    "preview": "{\n  \"modules\": {\n    \"2\": \"function(t,e,n){n(3),n(173),n(15),n(11),n(148),n(150),n(152),n(143),n(5),n(151),n(4),n(1),n(1"
  },
  {
    "path": "test/bundles/validExtraBundleWithModulesAsArray.js",
    "chars": 74,
    "preview": "webpackJsonp([0,2],[,,function(t,e,r){'123'},,,function(t,e){'12345'},]);\n"
  },
  {
    "path": "test/bundles/validExtraBundleWithModulesAsArray.modules.json",
    "chars": 92,
    "preview": "{\n  \"modules\": {\n    \"2\": \"function(t,e,r){'123'}\",\n    \"5\": \"function(t,e){'12345'}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validExtraBundleWithModulesInsideArrayConcat.js",
    "chars": 516,
    "preview": "webpackJsonplmn_ui_obe__name_Init([0],Array(104).concat([function(e,t){function n(e){return null==e}e.exports=n},,,funct"
  },
  {
    "path": "test/bundles/validExtraBundleWithModulesInsideArrayConcat.modules.json",
    "chars": 515,
    "preview": "{\n  \"modules\": {\n    \"104\": \"function(e,t){function n(e){return null==e}e.exports=n}\",\n    \"107\": \"function(e,t,n){funct"
  },
  {
    "path": "test/bundles/validExtraBundleWithNamedChunk.js",
    "chars": 77,
    "preview": "webpackJsonp([\"app\"],{125:function(n,e,t){console.log(\"it works\");}},[125]);\n"
  },
  {
    "path": "test/bundles/validExtraBundleWithNamedChunk.modules.json",
    "chars": 80,
    "preview": "{\n  \"modules\": {\n    \"125\": \"function(n,e,t){console.log(\\\"it works\\\");}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validJsonpWithArrayConcatAndEntryPoint.js",
    "chars": 95,
    "preview": "webpackJsonp([10],Array(10).concat([function(e,t,n){'abcd'},,,,function(e,t,n){'efgh'}]),[11]);"
  },
  {
    "path": "test/bundles/validJsonpWithArrayConcatAndEntryPoint.modules.json",
    "chars": 96,
    "preview": "{\n  \"modules\": {\n    \"10\": \"function(e,t,n){'abcd'}\",\n    \"14\": \"function(e,t,n){'efgh'}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validNodeBundle.js",
    "chars": 225,
    "preview": "exports.ids = [\"common\"];\nexports.modules = {\n  0: function(e,t,n){n(1),n(21),n(96),n(306),n(23),n(150),n(57),n(56),n(34"
  },
  {
    "path": "test/bundles/validNodeBundle.modules.json",
    "chars": 219,
    "preview": "{\n  \"modules\": {\n    \"0\": \"function(e,t,n){n(1),n(21),n(96),n(306),n(23),n(150),n(57),n(56),n(34),n(138),e.exports=n(348"
  },
  {
    "path": "test/bundles/validUmdLibraryBundleWithModulesAsArray.js",
    "chars": 555,
    "preview": "(function(e,o){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=o():\"function\"==typeof define&&define.am"
  },
  {
    "path": "test/bundles/validUmdLibraryBundleWithModulesAsArray.modules.json",
    "chars": 216,
    "preview": "{\n  \"modules\": {\n    \"0\": \"function(e,o,t){t(1),t(2),t(3)}\",\n    \"1\": \"function(e,o){e.exports=\\\"module a\\\"}\",\n    \"2\": "
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunk.js",
    "chars": 113,
    "preview": "(window.webpackJsonp=window.webpackJsonp||[]).push([[27],{\"57iH\":function(e,n,t){console.log(\"hello world\")}}]);\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunk.modules.json",
    "chars": 83,
    "preview": "{\n  \"modules\": {\n    \"57iH\": \"function(e,n,t){console.log(\\\"hello world\\\")}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkAndEntryPoint.js",
    "chars": 133,
    "preview": "(window.webpackJsonp=window.webpackJsonp||[]).push([[27],{\"57iH\":function(e,n,t){console.log(\"hello world\")}},[[\"57iH\",1"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkAndEntryPoint.modules.json",
    "chars": 83,
    "preview": "{\n  \"modules\": {\n    \"57iH\": \"function(e,n,t){console.log(\\\"hello world\\\")}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkUsingCustomGlobalObject.js",
    "chars": 215,
    "preview": "((\"undefined\" != typeof self ? self : this).webpackJsonp_someCustomName = (\"undefined\" != typeof self ? self : this).web"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkUsingCustomGlobalObject.modules.json",
    "chars": 82,
    "preview": "{\n  \"modules\": {\n    \"1\": \"function(e,n,t){console.log(\\\"Chuck Norris\\\")}\"\n  }\n}\n\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkUsingSelfInsteadOfWindow.js",
    "chars": 105,
    "preview": "(self.webpackJsonp=self.webpackJsonp||[]).push([[27],{1:function(e,n,t){console.log(\"Chuck Norris\")}}]);\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkUsingSelfInsteadOfWindow.modules.json",
    "chars": 81,
    "preview": "{\n  \"modules\": {\n    \"1\": \"function(e,n,t){console.log(\\\"Chuck Norris\\\")}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkUsingThisInsteadOfWindow.js",
    "chars": 105,
    "preview": "(this.webpackJsonp=this.webpackJsonp||[]).push([[27],{1:function(e,n,t){console.log(\"Chuck Norris\")}}]);\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkUsingThisInsteadOfWindow.modules.json",
    "chars": 81,
    "preview": "{\n  \"modules\": {\n    \"1\": \"function(e,n,t){console.log(\\\"Chuck Norris\\\")}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkWithOptimizedModulesArray.js",
    "chars": 145,
    "preview": "(window.webpackJsonp=window.webpackJsonp||[]).push([['chunkId1', 'chunkId2'],Array(549).concat([function(e,n,t){console."
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkWithOptimizedModulesArray.modules.json",
    "chars": 82,
    "preview": "{\n  \"modules\": {\n    \"549\": \"function(e,n,t){console.log(\\\"hello world\\\")}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkWithWebWorkerChunkTemplatePlugin.js",
    "chars": 79,
    "preview": "self.chunkCallbackName([27],{1:function(e,n,t){console.log(\"Chuck Norris\")}});\n"
  },
  {
    "path": "test/bundles/validWebpack4AsyncChunkWithWebWorkerChunkTemplatePlugin.modules.json",
    "chars": 81,
    "preview": "{\n  \"modules\": {\n    \"1\": \"function(e,n,t){console.log(\\\"Chuck Norris\\\")}\"\n  }\n}\n"
  },
  {
    "path": "test/bundles/validWebpack5LegacyBundle.js",
    "chars": 265,
    "preview": "!function(){var o={631:function(o){o.exports=\"module a\"},85:function(o){o.exports=\"module a\"},326:function(o){o.exports="
  },
  {
    "path": "test/bundles/validWebpack5LegacyBundle.modules.json",
    "chars": 171,
    "preview": "{\n  \"modules\": {\n    \"631\": \"function(o){o.exports=\\\"module a\\\"}\",\n    \"85\": \"function(o){o.exports=\\\"module a\\\"}\",\n    "
  },
  {
    "path": "test/bundles/validWebpack5ModernBundle.js",
    "chars": 236,
    "preview": "(()=>{var r={631:r=>{r.exports=\"module a\"},85:r=>{r.exports=\"module a\"},326:r=>{r.exports=\"module b\"}},e={};function o(t"
  },
  {
    "path": "test/bundles/validWebpack5ModernBundle.modules.json",
    "chars": 147,
    "preview": "{\n  \"modules\": {\n    \"631\": \"r=>{r.exports=\\\"module a\\\"}\",\n    \"85\": \"r=>{r.exports=\\\"module a\\\"}\",\n    \"326\": \"r=>{r.ex"
  },
  {
    "path": "test/dev-server/.gitignore",
    "chars": 7,
    "preview": "output\n"
  },
  {
    "path": "test/dev-server/src.js",
    "chars": 31,
    "preview": "export const chuck = \"norris\";\n"
  },
  {
    "path": "test/dev-server/webpack.config.js",
    "chars": 456,
    "preview": "\"use strict\";\n\nconst path = require(\"node:path\");\nconst BundleAnalyzerPlugin = require(\"../../src/BundleAnalyzerPlugin\")"
  },
  {
    "path": "test/dev-server.js",
    "chars": 1480,
    "preview": "const { spawn } = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\ncons"
  },
  {
    "path": "test/helpers.js",
    "chars": 4051,
    "preview": "const path = require(\"node:path\");\nconst webpack = require(\"webpack\");\n\nconst BundleAnalyzerPlugin = require(\"../src/Bun"
  },
  {
    "path": "test/parseUtils.js",
    "chars": 1220,
    "preview": "const fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst { parseBundle } = require(\"../src/parseUtils\");"
  },
  {
    "path": "test/plugin.js",
    "chars": 8169,
    "preview": "const fs = require(\"node:fs\");\nconst path = require(\"node:path\");\nconst url = require(\"node:url\");\nconst puppeteer = req"
  },
  {
    "path": "test/src/a-clone.js",
    "chars": 29,
    "preview": "module.exports = \"module a\";\n"
  },
  {
    "path": "test/src/a.js",
    "chars": 29,
    "preview": "module.exports = \"module a\";\n"
  },
  {
    "path": "test/src/b.js",
    "chars": 29,
    "preview": "module.exports = \"module b\";\n"
  },
  {
    "path": "test/src/index.js",
    "chars": 54,
    "preview": "require(\"./a\");\nrequire(\"./b\");\nrequire(\"./a-clone\");\n"
  },
  {
    "path": "test/stats/extremely-optimized-webpack-5-bundle/bundle.js",
    "chars": 58,
    "preview": "(()=>{\"use strict\";console.log(\"module a\",\"module b\")})();"
  },
  {
    "path": "test/stats/extremely-optimized-webpack-5-bundle/expected-chart-data.js",
    "chars": 2031,
    "preview": "module.exports = [\n  {\n    'label': 'bundle.js',\n    'isAsset': true,\n    'statSize': 142,\n    'parsedSize': 58,\n    'gz"
  },
  {
    "path": "test/stats/extremely-optimized-webpack-5-bundle/stats.json",
    "chars": 24032,
    "preview": "{\n  \"hash\": \"3d86243b5bbeac1fe1cc\",\n  \"version\": \"5.3.2\",\n  \"time\": 151,\n  \"builtAt\": 1604593377239,\n  \"publicPath\": \"au"
  },
  {
    "path": "test/stats/minimal-stats/stats.json",
    "chars": 5733,
    "preview": "{\"logging\":{\"./node_modules/babel-loader/lib/index.js babel-loader ./node_modules/babel-loader/lib/index.js??ruleSet[1]."
  },
  {
    "path": "test/stats/webpack-5-bundle-with-concatenated-entry-module/app.js",
    "chars": 90,
    "preview": "(()=>{\"use strict\";console.log(\"foo.js\"),console.log(\"bar.js\")})(),console.log(\"baz.js\");\n"
  },
  {
    "path": "test/stats/webpack-5-bundle-with-concatenated-entry-module/expected-chart-data.json",
    "chars": 1669,
    "preview": "[{\"groups\": [{\"concatenated\": true, \"groups\": [{\"gzipSize\": 8, \"id\": 613, \"inaccurateSizes\": true, \"label\": \"baz.js\", \"p"
  },
  {
    "path": "test/stats/webpack-5-bundle-with-concatenated-entry-module/stats.json",
    "chars": 33180,
    "preview": "{\n  \"hash\": \"9b3a9bf7f15684e8eb22\",\n  \"version\": \"5.72.1\",\n  \"time\": 116,\n  \"builtAt\": 1685518072786,\n  \"publicPath\": \"a"
  },
  {
    "path": "test/stats/webpack-5-bundle-with-multiple-entries/bundle.js",
    "chars": 488,
    "preview": "(()=>{\"use strict\";var o,e,r={85:(o,e,r)=>{r.d(e,{Z:()=>t});const t=\"module a\"},326:(o,e,r)=>{r.d(e,{Z:()=>t});const t=\""
  },
  {
    "path": "test/stats/webpack-5-bundle-with-multiple-entries/expected-chart-data.js",
    "chars": 1836,
    "preview": "module.exports = [\n  {\n    'label': 'bundle.js',\n    'isAsset': true,\n    'statSize': 204,\n    'parsedSize': 488,\n    'g"
  },
  {
    "path": "test/stats/webpack-5-bundle-with-multiple-entries/stats.json",
    "chars": 28030,
    "preview": "{\n  \"hash\": \"36d4270b59839025be6f\",\n  \"version\": \"5.3.2\",\n  \"time\": 173,\n  \"builtAt\": 1604594140532,\n  \"publicPath\": \"au"
  },
  {
    "path": "test/stats/webpack-5-bundle-with-single-entry/bundle.js",
    "chars": 253,
    "preview": "!function(){\"use strict\";var o,t,n={85:function(o,t){t.Z=\"module a\"},326:function(o,t){t.Z=\"module b\"}},r={};function e("
  },
  {
    "path": "test/stats/webpack-5-bundle-with-single-entry/expected-chart-data.js",
    "chars": 912,
    "preview": "module.exports = [\n  {\n    'label': 'bundle.js',\n    'isAsset': true,\n    'statSize': 142,\n    'parsedSize': 253,\n    'g"
  },
  {
    "path": "test/stats/webpack-5-bundle-with-single-entry/stats.json",
    "chars": 15725,
    "preview": "{\n  \"hash\": \"a27a519140b2590a60d9\",\n  \"version\": \"5.3.2\",\n  \"time\": 152,\n  \"builtAt\": 1604594809350,\n  \"publicPath\": \"au"
  },
  {
    "path": "test/stats/with-array-config/config-1-main.js",
    "chars": 948,
    "preview": "!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.expo"
  },
  {
    "path": "test/stats/with-array-config/config-2-main.js",
    "chars": 948,
    "preview": "!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.expo"
  },
  {
    "path": "test/stats/with-array-config/stats.json",
    "chars": 9133,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.44.2\",\n  \"hash\": \"a30aaa779dd06e8979b1a30aaa779dd06e8979b1\",\n  \"chil"
  },
  {
    "path": "test/stats/with-children-array.json",
    "chars": 9957,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"1.14.0\",\n  \"hash\": \"4e39ab22a848116a4c15\",\n  \"children\": [\n    {\n     "
  },
  {
    "path": "test/stats/with-cjs-chunk.json",
    "chars": 9640,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"1.14.0\",\n  \"hash\": \"4e39ab22a848116a4c15\",\n  \"children\": [\n    {\n     "
  },
  {
    "path": "test/stats/with-invalid-chunk/invalid-chunk.js",
    "chars": 30,
    "preview": "console.log('invalid chunk');\n"
  },
  {
    "path": "test/stats/with-invalid-chunk/stats.json",
    "chars": 13431,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.8.3\",\n  \"hash\": \"9deae6a8259cab8aa857\",\n  \"time\": 563,\n  \"builtAt\": "
  },
  {
    "path": "test/stats/with-invalid-chunk/valid-chunk.js",
    "chars": 590,
    "preview": "!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.expo"
  },
  {
    "path": "test/stats/with-invalid-dynamic-require.json",
    "chars": 5253,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [\n    \"./src/invalid-require-usage.js\\n2:9-28 Critical dependency: the request of a depe"
  },
  {
    "path": "test/stats/with-missing-chunk/stats.json",
    "chars": 13431,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.8.3\",\n  \"hash\": \"9deae6a8259cab8aa857\",\n  \"time\": 563,\n  \"builtAt\": "
  },
  {
    "path": "test/stats/with-missing-chunk/valid-chunk.js",
    "chars": 590,
    "preview": "!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.expo"
  },
  {
    "path": "test/stats/with-missing-module-chunks/stats.json",
    "chars": 13382,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.8.3\",\n  \"hash\": \"9deae6a8259cab8aa857\",\n  \"time\": 563,\n  \"builtAt\": "
  },
  {
    "path": "test/stats/with-missing-module-chunks/valid-chunk.js",
    "chars": 590,
    "preview": "!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.expo"
  },
  {
    "path": "test/stats/with-missing-parsed-module/bundle.js",
    "chars": 7315,
    "preview": "/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/**"
  },
  {
    "path": "test/stats/with-missing-parsed-module/stats.json",
    "chars": 13748,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.8.2\",\n  \"hash\": \"3c8608aea48b794f4b86\",\n  \"time\": 1782,\n  \"builtAt\":"
  },
  {
    "path": "test/stats/with-module-concatenation-info/bundle.js",
    "chars": 600,
    "preview": "!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.expo"
  },
  {
    "path": "test/stats/with-module-concatenation-info/expected-chart-data.js",
    "chars": 1953,
    "preview": "module.exports = {\n  label: 'index.js + 5 modules (concatenated)',\n  concatenated: true,\n  statSize: 332,\n  parsedSize: "
  },
  {
    "path": "test/stats/with-module-concatenation-info/stats.json",
    "chars": 24574,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.0.0\",\n  \"hash\": \"d8c858adcd390ee63793\",\n  \"time\": 1129,\n  \"builtAt\":"
  },
  {
    "path": "test/stats/with-modules-chunk.json",
    "chars": 9640,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"1.14.0\",\n  \"hash\": \"4e39ab22a848116a4c15\",\n  \"children\": [\n    {\n     "
  },
  {
    "path": "test/stats/with-modules-in-chunks/expected-chart-data.js",
    "chars": 1001,
    "preview": "module.exports = [\n  {\n    'label': 'runtime.6afe30102d8fe7337431.js',\n    'statSize': 1053,\n    'groups': []\n  },\n  {\n "
  },
  {
    "path": "test/stats/with-modules-in-chunks/stats.json",
    "chars": 19061,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.6.0\",\n  \"hash\": \"3e40877c6bf4ead7ce87\",\n  \"publicPath\": \"\",\n  \"outpu"
  },
  {
    "path": "test/stats/with-multiple-entrypoints/expected-chart-data.js",
    "chars": 7201,
    "preview": "module.exports = [\n  {\n    label: \"react-vendors.js\",\n    isAsset: true,\n    statSize: 138490,\n    groups: [\n      {\n   "
  },
  {
    "path": "test/stats/with-multiple-entrypoints/stats.json",
    "chars": 188195,
    "preview": "{\n    \"hash\": \"063fb032f6c5983a9ddf\",\n    \"version\": \"5.74.0\",\n    \"time\": 2660,\n    \"builtAt\": 1660834599531,\n    \"publ"
  },
  {
    "path": "test/stats/with-no-entrypoints/stats.json",
    "chars": 2125,
    "preview": "{\n  \"hash\": \"0d30ee86a3a7e89aaace\",\n  \"version\": \"5.74.0\",\n  \"time\": 42,\n  \"builtAt\": 1660844314317,\n  \"publicPath\": \"au"
  },
  {
    "path": "test/stats/with-non-asset-asset/bundle.js",
    "chars": 237,
    "preview": "(()=>{var r={146:r=>{r.exports=\"module a\"},296:r=>{r.exports=\"module a\"},260:r=>{r.exports=\"module b\"}},e={};function o("
  },
  {
    "path": "test/stats/with-non-asset-asset/stats.json",
    "chars": 14264,
    "preview": "{\n  \"hash\": \"a00ccf8c892bb7cacd85\",\n  \"version\": \"5.1.0\",\n  \"time\": 279,\n  \"builtAt\": 1602608505481,\n  \"publicPath\": \"au"
  },
  {
    "path": "test/stats/with-special-chars/bundle.js",
    "chars": 1972,
    "preview": "!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.expo"
  },
  {
    "path": "test/stats/with-special-chars/expected-chart-data.js",
    "chars": 217,
    "preview": "module.exports = [\n  {\n    \"groups\": [\n      {\n        \"id\": 0,\n        \"label\": \"index.js\",\n        \"path\": \"./index.js"
  },
  {
    "path": "test/stats/with-special-chars/stats.json",
    "chars": 5671,
    "preview": "{\n  \"errors\": [],\n  \"warnings\": [],\n  \"version\": \"4.25.1\",\n  \"hash\": \"6a0006856f4405101aa5\",\n  \"time\": 279,\n  \"builtAt\":"
  },
  {
    "path": "test/stats/with-worker-loader/bundle.js",
    "chars": 1141,
    "preview": "!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.expo"
  },
  {
    "path": "test/stats/with-worker-loader/bundle.worker.js",
    "chars": 72270,
    "preview": "!function(n){var t={};function r(e){if(t[e])return t[e].exports;var u=t[e]={i:e,l:!1,exports:{}};return n[e].call(u.expo"
  },
  {
    "path": "test/stats/with-worker-loader/stats.json",
    "chars": 1163345,
    "preview": "{\n    \"errors\": [\n    ],\n    \"warnings\": [\n    ],\n    \"version\": \"4.0.0\",\n    \"hash\": \"cf9a021389e125c88552\",\n    \"time\""
  },
  {
    "path": "test/stats/with-worker-loader-dynamic-import/1.bundle.js",
    "chars": 162,
    "preview": "(window.webpackJsonp=window.webpackJsonp||[]).push([[1],[,function(n,e,o){\"use strict\";o.r(e),e.default=function(){retur"
  },
  {
    "path": "test/stats/with-worker-loader-dynamic-import/1.bundle.worker.js",
    "chars": 110,
    "preview": "self.webpackChunk([1],[,function(n,t,c){\"use strict\";c.r(t),c.d(t,\"foo\",(function(){return o}));const o=42}]);"
  },
  {
    "path": "test/stats/with-worker-loader-dynamic-import/bundle.js",
    "chars": 951,
    "preview": "!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.expo"
  },
  {
    "path": "test/stats/with-worker-loader-dynamic-import/bundle.worker.js",
    "chars": 1230,
    "preview": "!function(e){self.webpackChunk=function(r,n){for(var o in n)e[o]=n[o];for(;r.length;)t[r.pop()]=1};var r={},t={0:1};func"
  },
  {
    "path": "test/stats/with-worker-loader-dynamic-import/stats.json",
    "chars": 19499,
    "preview": "{\n  \"errors\": [\n  ],\n  \"warnings\": [\n  ],\n  \"version\": \"4.44.1\",\n  \"hash\": \"6a922406d27e1f958522\",\n  \"time\": 108,\n  \"bui"
  },
  {
    "path": "test/statsUtils.js",
    "chars": 2530,
    "preview": "const { readFileSync } = require(\"node:fs\");\nconst path = require(\"node:path\");\nconst { globSync } = require(\"tinyglobby"
  },
  {
    "path": "test/utils.js",
    "chars": 1855,
    "preview": "const { createAssetsFilter } = require(\"../src/utils\");\n\ndescribe(\"createAssetsFilter\", () => {\n  it(\"should create a no"
  },
  {
    "path": "test/viewer.js",
    "chars": 3687,
    "preview": "const crypto = require(\"node:crypto\");\nconst net = require(\"node:net\");\n\nconst Logger = require(\"../src/Logger\");\nconst "
  },
  {
    "path": "tsconfig.json",
    "chars": 273,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"allowJs\": true,\n    \"checkJs\": tru"
  },
  {
    "path": "webpack.config.js",
    "chars": 3369,
    "preview": "\"use strict\";\n\nconst path = require(\"node:path\");\nconst TerserPlugin = require(\"terser-webpack-plugin\");\nconst webpack ="
  }
]

About this extraction

This page contains the full source code of the webpack/webpack-bundle-analyzer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 190 files (1.9 MB), approximately 566.9k tokens, and a symbol index with 574 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!