Description
π¦ Bundle Budget Plugin
Bundle size tracking for your build artifacts
Track, compare, and prevent bundle size regressions to maintain web performance (e.g. LCP) across product areas.
π§ͺ Reference PR
π #1024 β BundleBudget Plugin PoC Implementation
User story
As a developer, I want to track bundle size regressions per product area and route,
so that we can avoid performance regressions and optimize LCP over time.
The plugin should:
- Analyze
stats.json
output from different bundler. - Identify and compare main, initial, and lazy chunks over glob matching
- Use chunk input fingerprinting to map renamed chunk files.
- Group chunk sizes by route/product (e.g.,
/route-1
,/route-2
). - give penalties for site and blacklisted imports
- visualise inputs as well as static imports as they directly contribute to the real bundle size.
- Store and compare bundle stats across versions/releases.
Metric
Bundle size in bytes.
Parsed from --stats-json
output and grouped by file.
Property | Value | Description |
---|---|---|
value | 132341 |
Total size of all chunks. |
displayValue | 13.4 MB / 13 Files |
Display value inc. number of files. |
Integration Requirements
The plugin can be implemented in 2 ways:
- Using stats files
- Crawling the filesystem
As stats file serve significantly more details and are state of the art when debugging bundle size this issue favours this approach.
Using Stats Files
Bundle stats are detailed metadata about your build outputsβlisting each generated file, its original inputs, and any static importsβexported via a metafile (e.g. from ESBuild or other bundlers).
Generate a stats file with ESBuild:
esbuild src/index.js --bundle --outfile=dist/bundle.js --metafile=stats.json
The resulting file maintains the following data structure:
EsbuildBundleStats # Root object containing all bundle stats
βββ inputs (Record<string, MetafileInput>) # Map of each source input file to its metadata
β βββ <inputPath> # File path of a specific input module
β βββ bytes: number # Size of this input module in bytes
β βββ imports (MetafileImport[]) # List of static imports declared by this input
β βββ [ ] # Array of import entries
β βββ path: string # Resolved filesystem path of the imported module
β βββ kind: ImportKind # Import type (e.g. "import", "require", "dynamic")
β βββ external?: boolean # True if marked external (excluded from bundle)
β βββ original?: string # Original import specifier in source code
βββ outputs (Record<string, MetafileOutput>)# Map of each generated output file to its metadata
βββ <outputPath> # File path of a specific output chunk
βββ bytes: number # Total size of this output file in bytes
βββ inputs (Record<string,MetafileOutputInput>) # Map of input modules contributing to this output
β βββ <inputPath> # Path of an input that fed into this output
β βββ bytesInOutput: number # Number of bytes this input contributed to the output
βββ imports (MetafileImport[]) # List of static imports found in this output
β βββ [ ] # Array of import entries
β βββ path: string # Resolved filesystem path of the imported module
β βββ kind: ImportKind # Import type (e.g. "import", "require", "dynamic")
β βββ external?: boolean # True if marked external (excluded from bundle)
β βββ original?: string # Original import specifier in source code
βββ exports: string[] # List of named exports provided by this bundle
βββ entryPoint?: string # Entry-point module path for this output chunk, if any
File Type Definitions
Type | Structure | Description |
---|---|---|
inputs |
Record<string, MetafileInput> |
Map of each source file path to its metadata (bytes and imports). |
imports |
MetafileImport[] |
Array of import entries, each with path , kind , and optional flags. |
π outputs |
Record<string, MetafileOutput> |
Map of each output file path to its metadata (bytes, inputs, imports, exports, entryPoint). |
π entryPoint |
string (optional) |
The entry-point module path for this output chunk, if present. |
The plugin will use this information to gather the configured artefact groups.
Crawling the filesystem
Note
No research done as not scaleable
Setup and Requirements
π¦ Package Dependencies
- Dev Dependencies:
- None required, optional CLI runner for local debugging.
- Optional Dependencies:
π Configuration Files
angular.json
/vite.config.ts
or equivalent β for custom build config.- No required config file for the plugin itself.
Bundle Stats
The following is a minimal stats representation used to explain different features of the plugin. It will be referred to as Example Stats.
stats.json
βββ outputs
βββ dist/index.js // entryPoint: src/index.ts
β βββ inputs
β β βββ src/index.ts
β β βββ src/lib/feature-1.ts // import-statement
β β β βββ src/lib/utils/format.ts // import-statement
β β βββ src/lib/utils/math.ts // import-statement
β β βββ src/lib/feature-2.ts // dynamic-import
β βββ imports
β βββ dist/chunks/chunk-U6O5K65G.js // import-statement
β βββ dist/chunks/feature-2-X2YVDBQK.js // dynamic-import
βββ dist/bin.js // entryPoint: src/bin.ts
β βββ inputs
β β βββ src/bin.ts
β β βββ src/lib/feature-1.ts // import-statement
β β β βββ src/lib/utils/format.ts // import-statement
β β βββ src/lib/utils/math.ts // import-statement
β βββ imports
β βββ dist/chunks/chunk-U6O5K65G.js // import-statement
βββ dist/chunks/chunk-U6O5K65G.js
β βββ inputs
β βββ src/lib/utils/format.ts
β βββ src/lib/feature-1.ts
β βββ src/lib/utils/math.ts
βββ dist/chunks/feature-2-X2YVDBQK.js // entryPoint: src/lib/feature-2.ts
βββ inputs
βββ src/lib/feature-2.ts
Features
General
The audit name is provided over the title
property. Internally a audit slug
is derived from the
title
: A unique identifier for this group.description
: One two sentences explaining the purpose of the audit
Types
type Audit = {
slug?: string;
title: string;
description?: string;
};
Example Configuration
const audit1: Audit = {
title: 'Initial Bundles',
};
const audit2: Audit = {
slug: 'app-core',
title: 'π― App Core',
description: 'This audit checks the core functionality of the app.',
};
Every audit gets the merged configuration of the global and audit specific configuration listed in the description.
Configuration Example
const config: BundleStatsConfig = {
title: 'Initial Bundles',
description: 'This audit checks the initial bundles of the app.',
};
Report Output
This audit checks the initial bundles of the app.
<details>
<summary>βοΈ Config Summary</summary>
**Selection**
β’ `includeOutputs`: `**/*`
**Scoring**
β’ `totalSize`: `0 B β 97.66 kB`
**Insights**
β’ π§ `**/math.ts`
β’ π§ `**/format.ts`
β’ π§© `**/*feature-2*`
β’ π `src/index.ts, src/bin.ts`
β’ π€ `dist/chunks/chunk-*.js`
β’ π¦ `**/node_modules/**`
β’ π¦ `dist/index.js, dist/bin.js`
</details>
Selection
To select files for an audit, glob patterns are used to include and exclude parts of the output files.
All options are provided as glob patterns matching either path
, path
in inputs
or entryPoint
.
Types
export interface SelectionOptions {
// targeting output path of a `OutputNode`
includeOutputs: string[];
excludeOutputs: string[];
// targeting input paths of a `OutputNode`
includeInputs: string[];
excludeInputs: string[];
// targeting entryPoint path of a `OutputNode`
includeEntryPoints: string[];
excludeEntryPoints: string[];
}
Example Configuration
const selection: SelectionOptions = {
includeOutputs: ['**/features/*'],
excludeOutputs: ['**/features/legacy/**'],
excludeInputs: ['**/ui/**'],
includeEntryPoints: ['**/features/auth/*.ts'],
};
Selection Behaviour
- Include patterns: If any include pattern is specified, only matching items are selected
- Exclude patterns: Applied after include patterns to filter out unwanted items
- Pattern matching: Uses standard glob patterns (
*
,**
,?
,[...]
) - Multiple patterns: All patterns in an array are combined with OR logic
- Precedence: Exclude patterns take precedence over include patterns
π Dependency Preservation
Excluded files that are static imports (import-statement
) cannot be fully excluded because they:
- Contribute to bundle size: They are bundled with their importing file
- Are always loaded: They load immediately when their importing file loads
- Are required dependencies: Removing them would break the importing file
Example: When excluding chunk files:
{
includeOutputs: ["**/*"],
excludeOutputs: ["**/chunks/**"] // Exclude chunk files
}
Static imports will still appear in the outputs
section:
outputs
βββ dist/index.js
β βββ imports
β βββ dist/chunks/chunk-U6O5K65G.js // import-statement
β βββ dist/chunks/feature-2-X2YVDBQK.js // dynamic-import
βββ π dist/chunks/chunk-U6O5K65G.js // preserved as import-statement of dist/index.js
Import Type Behaviour:
import-statement
: Always included (static imports are bundled dependencies)dynamic-import
: Excluded from selection as they do not contribute to the bundle size (runtime dependencies are required for functionality, but can load separately from the rest)- Chunks in outputs: Can be excluded if not imported by included files
This ensures the selection maintains functional integrity while respecting your filtering preferences.
All examples target the stats data under the Example Stats section above.
Include Specific Output
Selection Options
Select only dist/index.js
and its dependencies:
{
includeOutputs: ['**/dist/index.js'];
}
Selection Result:
stats.json
βββ outputs
βββ π― dist/index.js // entryPoint: src/index.ts
β βββ inputs
β βββ imports
β βββ dist/chunks/chunk-U6O5K65G.js // import-statement
βββ π dist/chunks/chunk-U6O5K65G.js // imported by `dist/index.js`
βββ inputs
Include by Input Files
Selection Options
Select outputs that contain specific input files:
{
includeInputs: ['**/feature-2.ts'];
}
Selection Result:
stats.json
βββ outputs
βββ dist/index.js // entryPoint: src/index.ts
β βββ inputs
β βββ src/index.ts
β βββ π― src/lib/feature-2.ts // dynamic-import
βββ π dist/chunks/chunk-U6O5K65G.js // imported by `dist/index.js`
Include by Entry Point
Selection Options
Select outputs that have src/bin.ts
as their entry point:
{
includeEntryPoints: ['**/bin.ts'];
}
Selection Result:
stats.json
βββ outputs
βββ dist/bin.js // entryPoint: src/bin.ts π―
βββ π dist/chunks/chunk-U6O5K65G.js // imported by `dist/bin.js`
Exclude Outputs Patterns
Selection Options
Select all outputs except bin files:
{
includeOutputs: ["**/*"],
excludeOutputs: ["**/bin.js"]
}
Selection Result:
stats.json
βββ outputs
βββ π― dist/index.js // entryPoint: src/index.ts
βββ π dist/chunks/chunk-U6O5K65G.js // imported by `dist/index.js`
βββ π dist/chunks/feature-2-X2YVDBQK.js // imported by `dist/index.js`
βββ // excluded: dist/bin.js
Exclude Inputs Patterns
Selection Options
Select all outputs but exclude those containing feature-2 files:
{
includeOutputs: ["**/*"],
excludeInputs: ["**/feature-2.ts"]
}
Selection Result:
stats.json
βββ outputs
βββ π― dist/bin.js // entryPoint: src/bin.ts
βββ π dist/chunks/chunk-U6O5K65G.js // imported by `dist/bin.js`
βββ // excluded: dist/index.js (contains feature-2.ts)
βββ // excluded: dist/chunks/feature-2-X2YVDBQK.js (contains feature-2.ts)
Exclude Entry Points Patterns
Selection Options
Select all outputs except those with bin entry points:
{
includeOutputs: ["**/*"],
excludeEntryPoints: ["**/bin.ts"]
}
Selection Result:
stats.json
βββ outputs
βββ π― dist/index.js // entryPoint: src/index.ts
βββ π dist/chunks/chunk-U6O5K65G.js // imported by `dist/index.js`
βββ π dist/chunks/feature-2-X2YVDBQK.js // imported by `dist/index.js`
βββ // excluded: dist/bin.js (entryPoint: src/bin.ts)
Scoring
The plugin assigns a score in the range [0 β¦ 1]
to each artefact (or artefact selection) based on:
- Size vs. a configurable maximum threshold
- Diagnostics penalties (errors & warnings, including blacklist violations as warnings)
A perfect score (1
) means βwithin budgetβ; lower values indicate regressions.
The selection process of a scored set of files is explained in detail in File Selection
Types
export interface ScoringOptions {
// targeting output path of a `OutputNode`
totalSize: number | MinMax;
penalty: {
artefactSize?: number | MinMax;
blacklist?: string[];
}
Example Configuration
const selection: SelectionOptions = {
includeOutputs: ['**/features/*'],
excludeOutputs: ['**/features/legacy/**'],
excludeInputs: ['**/ui/**'],
includeEntryPoints: ['**/features/auth/*.ts'],
};
Total Size
Every artefact selection has budgets assigned under budget
.
totalSize
: Total bytes of all files in a selection.
Examples
const scoring1: Scoring = {
totalSize: 300_000,
};
const scoring2: Scoring = {
totalSize: [10_000, 300_000],
};
Panelties
To give actionable feedback to users of the plugin you can add penalties a set of penalties:
artefactSize
: Byte size of a files in a selection.blacklist
: List of globs to flag inputs as well as imports as forbidden
Types
type Penalty = {
artefactSize?: number | MinMax;
blacklist?: string[];
};
Example Configuration
const penalty1: Penalty = {
artefactSize: 50_000,
};
const penalty2: Penalty = {
artefactSize: [1_000, 50_000],
blacklist: ['node_modules/old-charting-lib'],
};
Scoring Parameters
Parameter | Description |
---|---|
S |
Actual bytes |
M |
Size threshold bytes |
E |
Count of issues of severity errors (π¨) |
W |
Count of issues of severity warnings ( |
we |
Weight per error (default 1 ) |
ww |
Weight per warning (default 0.5 ) |
Size score
Issues penalty
Final blended score
xychart-beta
title "Score vs Artifact Size (with penalty shift)"
x-axis [0, 1, 1.25, 1.5, 1.75, 2]
y-axis "Score" 0 --> 1
line Original [1, 1, 0.75, 0.5, 0.25, 0]
line Penalized [0.5, 0.5, 0.25, 0, 0, 0]
Issues
To give users actionable feedback we need to be able to tell WHAT happened, WHEN to fix it and HOW to fix it.
Issues are configured per audit under the scoring.penalty
property.
The plugin creates diagnostics for each penalty. The table below shows all diagnostic types:
Diagnostic | Description | Config Key | Default | Severity | Recommended Action |
---|---|---|---|---|---|
Blacklisted | Artifact contains an import matching a forbidden glob pattern. | blacklist |
β | π¨ Error | Remove or replace the forbidden dependency. |
Too Large | Artifact exceeds the maximum allowed size. May indicate an unoptimized bundle or accidental check-in. | maxArtifactSize |
5 MB |
π¨ Error | Review and optimize (e.g. code splitting, compression). |
Too Small | Artifact is below the minimum expected size. Could signal missing dependencies or incomplete build. | minArtifactSize |
1 KB |
Verify that the build output is complete and dependencies are included. |
Too Large Issues
Artifacts that exceed the maximum allowed size threshold. This typically indicates unoptimized bundles, accidental inclusion of large files, or missing code splitting strategies.
Configuration: scoring.artifactSize
Example Issues:
Severity | Message | Source file | Location |
---|---|---|---|
π¨ error | main.js is 6.12 MB (exceeds 5 MB); consider splitting or compressing this bundle. |
dist/lib/main.js |
|
π¨ error | vendor.js is 2.05 MB (exceeds 1.5 MB); apply tree-shaking or extract shared dependencies. |
dist/lib/vendor.js |
Use Cases:
- Code Splitting: Break large bundles into smaller chunks
- Tree Shaking: Remove unused code from dependencies
- Compression: Enable gzip/brotli compression
- Asset Optimization: Optimize images and other assets
- Lazy Loading: Load code only when needed
Too Small Issues
Artifacts that fall below the minimum expected size threshold. This could signal missing dependencies, incomplete builds, or configuration issues.
Configuration: scoring.artifactSize
Example Issues:
Severity | Message | Source file | Location |
---|---|---|---|
utils.js is 50 kB (below 100 kB); confirm that expected dependencies are included. |
dist/lib/utils.js |
||
styles.css is 10 B (below 1 kB); confirm that expected dependencies are included. |
dist/lib/styles.css |
Use Cases:
- Dependency Check: Verify all required dependencies are included
- Build Verification: Ensure the build process completed successfully
- Configuration Review: Check bundler configuration for missing entries
- Source Validation: Confirm source files contain expected content
Blacklisted Issues
Artifacts containing imports that match forbidden glob patterns. This helps enforce dependency policies and prevent usage of deprecated or unsafe libraries.
Configuration: scoring.blacklist
Example Issues:
Severity | Message | Source file | Location |
---|---|---|---|
π¨ error | node_modules/old-charting-lib/index.js matches a blacklist pattern; remove or replace this dependency. |
src/main-ASDFGAH.js |
Use Cases:
- Dependency Replacement: Replace blacklisted libraries with approved alternatives
- Code Refactoring: Remove usage of forbidden dependencies
- Policy Review: Update dependency policies if needed
- Security Audit: Investigate security implications of blacklisted dependencies
Artefact Tree
The artefact tree is here to give users a quick understanding of the dependencies graph of the selected artefacts. It should be a replaccement for opening the bundle stats in the browser and search for respective files.
Complete Example:
ποΈ example-group 537.17 kB 101 files
βββ π entry-1.js 138 kB 2 files
β βββ π src/input-1.ts 101 kB
β βββ π src/input-2.ts 37 kB
βββ π entry-2.js 330 kB 2 files
β βββ π node_modules/@angular/router/provider.js 15 kB
β βββ π node_modules/@angular/router/service.js 15 kB
β βββ ... 12 more files 300 kB
βββ π¨ styles.css 14 kB
βββ π static imports from π entry-1.js 104 kB
βββ π file-1.js
Artefact Types
Each group is also displayed as tree of artefacts, inputs and static imports.
The following types are detected.
- π - script file - JS/TS artefact
- π¨ - style files - CSS/SCSS artefacts
- π - entry file - JS/TS artefact
- π - static imports - List of S/TS artefacts statically imported by another file
ποΈ example-group
βββ π entry-1.js
βββ π file-1.js
βββ π¨ styles.css
βββ π static imports from π entry-1.js
βββ π file-2.js
Artefact Inputs
Each artefact is maintains out of inputs ind imports. The inputs are listed under each chunk.
ποΈ example-group
βββ π file-1.js
βββ π src/lib/cli.ts
β βββ π node_modules/yargs/yargs.ts
βββ π node_modules/zod/helpers
βββ π node_modules/zod/helpers/description.ts
Artefact Imports
Each artefact imports that are statically imported and directly contribute to the artefact group size are listed under dedicated import groups per file. This is helpful to understand the true size of you artefact group.
Static imports are loaded together with it's importing parent and therefore will end up in the loaded bytes. Displaying them helps to inderstand why they are part and where they aer imported from.
ποΈ example-group
βββ π entry-1.js
βββ π file-1.js
βββ π entry-1.js // imports file-2.ts and file-3.ts
βββ π file-2.ts // imported from π entry-1.js
β βββ π file-2.ts
βββ π file-3.ts // imported from π entry-1.js
βββ π file-3.ts
Artefact Grouping
Artefact inputs can be grouped over the configuration.
Types
type GroupRule = {
title: string;
patterns: string[];
icon: string;
maxDepth: number;
}
Ungrouped:
ποΈ example-group
βββ π entry-2.js
βββ π node_modules/@angular/router/provider.ts
βββ π node_modules/@angular/router/service.ts
βββ π node_modules/@angular/router/utils.ts
Example Configuration
const groupRules = [
{
title: "@angular/router",
patterns: ["node_modules/@angular/router"],
icon: "π
°οΈ"
}
];
Report Output:
ποΈ example-group
βββ π entry-2.js
ββ π
°οΈ @angular/router
Tree Pruning
The artefact tree can get quite dense in information. To avoid overload we reduce the amount of visible nodes.
Types
type PruningOptions = {
maxChildren: number;
maxDepth: number;
}
Unpruned:
ποΈ example-group
βββ π index.js
β βββ π src/app.ts
β β βββ π src/components/Header.ts
β β β βββ π src/components/Logo.ts
β β β βββ π src/components/Nav.ts
β β βββ π src/utils/math.ts
β βββ π src/route-1.ts
β βββ π src/route-2.ts
β βββ π src/main.css
βββ π vendor.js
β βββ π node_modules/react.ts
β βββ π node_modules/react-dom.ts
β βββ π node_modules/redux.ts
β βββ π node_modules/react-router.ts
β βββ π node_modules/lodash.js
βββ π logo.svg
Example Configuration
const pruningOptions: PruningOptions = {
maxChildren: 3,
maxDepth: 1,
};
Report Output:
ποΈ example-group
βββ π index.js
β βββ π src/app.ts
β βββ β¦ 3 more inputs
βββ π vendor.js
β βββ π node_modules/react.ts
β βββ β¦ 4 more inputs
βββ π logo.svg
Formatting
The Trees text content is formatted to get better readability by default.
Size Formatting
File sizes are calculated to the closest readable unit for better readability.
Unformatted:
ποΈ example-group 300000
βββ π main.js 300000
Formatted:
ποΈ example-group 300 kB
βββ π main.js 300 kB
Source Pluralization
Source counts are properly pluralized based on the number of files.
Unpluralized:
ποΈ example-group 3
βββ π main.js 1
βββ π utils.js 2
Pluralized:
ποΈ example-group 3 sources
βββ π main.js 1 source
βββ π utils.js 2 sources
Path Shortening
Long file paths are shortened while respecting important path segments.
Unshortened:
ποΈ example-group
βββ π src/lib/utils/helper/left-pad/left-pad.js
Shortened:
ποΈ example-group
βββ π src/.../left-pad.js
Redundant Information Handling
If a node has only 1 child or file, redundant size and file count information is not printed to reduce clutter.
With redundancy:
ποΈ example-group 300 kB 3 sources
βββ π index.js 100 kB 1 source
βββ π src/app.js 100 kB 1 source
βββ β¦ 4 more inputs 200 kB 4 sources
Without redundancy:
ποΈ example-group 300 kB 3 files
βββ π index.js 100 kB
βββ π src/app.js
βββ β¦ 4 more inputs 200 kB
Hide very small chunks from output
If a node has a very little byte size we will move them into the pruned item at the end.
With small inputs:
ποΈ example-group 840 kB 13 files
βββ π index.js 840 kB 12 files
βββ π src/app.js 400 kB
βββ π src/large-1.js. 200 kB
βββ π src/meduim-1.js. 100 kB
βββ π src/small-1.js. 10 kB
βββ π src/small-2.js. 10 kB
βββ π src/small-3.js. 10 kB
βββ π src/small-4.js. 5 kB
βββ π src/small-5.js. 5 kB
βββ β¦ 100 kB 5 files
Example Configuration
const pruningOptions: PruningOptions = {
minSize: 90_000
};
Without small chunks:
ποΈ example-group 840 kB 13 files
βββ π index.js 840 kB 12 files
βββ π src/app.js 400 kB
βββ π src/large-1.js. 200 kB
βββ π src/meduim-1.js. 100 kB
βββ β¦ 140 kB 8 files
Insights Table
The insights table gives an overview of the different areas of the file selection. The information is aggregated from output files, inputs and output overhead and grouped by pattern.
To have correct data, a process needs to be followed:
- For each Group
- For each Pattern
1. Iterate over all inputs of all outputs and aggregate its contributing bytes
2. Iterate over all imports of all outputs and aggregate its contributing bytes
3. Iterate over all outputs and aggregate its contributing bytes - Aggregate the unmatched bytes of all outputs under a group for the remaining bytes
Types
type InsightsOptions = {
title?: string;
patterns: string[];
icon?: string;
}[];
Example Configuration
const insightsOptions: InsightsOptions = [
{
patterns: ["**/features/host-app/**"],
title: "App Host",
icon: "π₯οΈ"
},
...
]
Report Output:
| Group | Size | Sources |
| ------------------- | -------- | ------- |
| π¦ Node Modules | 1.2 MB | 100 |
| π₯οΈ App Host | 48.15 kB | 109 |
| π Loaders | 30.53 kB | 21 |
| π Legacy | 30.53 kB | 21 |
| π― Sports Feature | 6.4 kB | 61 |
| π
°οΈ Angular | 836 B | 2 |
| π¬οΈ Oxygen | 287 B | 3 |
| *Rest* | 1.2 MB | 100 |
Implementation details
Limitations
From feedback sessions on this issue we collected a couple of things that popped up regularly but are not directly possible with the plugin.
- Any action triggered by a comparison of 2 measures.
- increased size by X%
- increased size by XkB
- increased files by X
For example to implement a failing CI on 20% increase for a specific audit you would have to read the comparison json created by the GH action or over the @code-pushup/ci
package directly and process it with your custom logic.
Data Processing Pipeline
flowchart LR
subgraph Stats Generation
A[Esbuild β stats.json]
B[Webpack β stats.json]
C[Vite β stats.json]
end
D[Unify Stats]
E[Merge Options]
F[Group Stats by Audits]
G[Compute Size Scores]
H[Compute Issue Penalties]
I[Score Audit]
subgraph Generate Audits
J[Add Issues]
K[Add Table]
L[Add Tree]
end
A --> D
B --> D
C --> D
D --> E
E --> F
F --> G
F --> H
G --> I
H --> I
I --> J
J --> K
K --> L
Plugin Configuration
The plugin integrates with supported bundlers to analyze bundle statistics and generate audit reports. Configure the plugin with bundler type, artifact paths, and audit settings.
Types
export type BundleStatsPluginOptions = {
bundler: SUPPORTED_BUNDLERS;
artefactsPaths: string;
generateArtefacts?: string;
audits: BundleStatsAuditOption[];
artefactTree?: TreeOptions;
insightsTable?: InsightsOptions;
scoring?: Omit<ScoringOptions, 'totalSize'>;
}
Minimal Example Configuration
const pluginOptions: PluginOptions = {
bundler: 'webpack',
artefactsPaths: './dist/stats.json',
generateArtefacts: 'esbuild src/index.js --bundle --outfile=dist/bundle.js --metafile=stats.json',
audits: [
{
title: 'Initial Bundles',
selection: {
includeOutputs: ['**/*.js']
},
scoring: {
totalSize: 500_000
}
}
]
};
Artefacts Gathering
The plugin can generate artefacts from the stats.json
file. The plugin can either use an existing stats.json
file or generate a new one if the generateArtefacts
option is provided.
Types
type ArtefactsOptions = {
bundler: SUPPORTED_BUNDLERS;
artefactsPaths: string;
generateArtefacts?: string;
}
Example Configuration:
const options: PluginOptions = {
bundler: 'esbuild',
artefactsPaths: './dist/stats.json'
// ...
};
Full Example Configuration:
const options: PluginOptions = {
bundler: 'esbuild',
artefactsPaths: './dist/stats.json',
generateArtefacts: 'esbuild src/index.js --bundle --outfile=dist/bundle.js --metafile=stats.json',
// ...
};
Options Merging
The Plugin and audit share a set of options:
insightsTable
- The insights to use (grouping)scoring
- The scoring penalty (not the totalSize) to use (penalty)artefactTree
- The artefact tree to use (pruning, grouping, formatting)
const pluginOptions: PluginOptions = {
artefactTree: {}, // π₯ merged into every audit[].artefactTree
insightsTable: [], // π₯ merged into every audit[].insightsTable
scoring: {}, // π₯ merged into every audit[].scoring (not the totalSize)
audits: [
{
scoring: {
totalSize: 500_000
pelanty: {} // π overrides plugin.scoring if set
},
insightsTable: [], // π overrides plugin.insightsTable if set
artefactTree: {}, // π overrides plugin.artefactTree if set
}
]
};
Audit Configuration
Each audit defines a specific bundle analysis with its own selection criteria, scoring thresholds, and reporting options. Audits can override plugin-level settings or inherit them for consistency across multiple audits.
Types
export type BundleStatsAuditOption = {
slug?: string;
title: string;
description?: string;
selection: SelectionOptions;
scoring: ScoringOptions;
artefactTree?: TreeOptions;
insightsTable?: InsightsOptions;
}
Minimal Example Configuration
const auditConfig: BundleStatsAuditOption = {
title: 'All Bundles',
selection: { includeOutputs: ['**/*.js'] },
scoring: { totalSize: 500_000 }
};
Full Example Configuration
const auditConfig: BundleStatsAuditOption = {
slug: 'initial-bundles',
title: 'Initial Bundle Size',
description: 'Monitors the size of initial JavaScript bundles loaded on page load.',
selection: {
includeOutputs: ['**/main.js', '**/vendor.js'],
excludeOutputs: ['**/chunks/**']
},
scoring: {
totalSize: 500_000,
penalty: {
artefactSize: [50_000, 200_000],
blacklist: ['**/legacy/**', '**/deprecated/**']
}
},
artefactTree: {
groups: [
{
title: 'App Code',
patterns: ['**/src/**'],
icon: 'π―'
},
{
title: 'Node Modules',
patterns: ['**/node_modules/**'],
icon: 'π¦'
}
],
pruning: {
maxChildren: 3,
maxDepth: 1
}
},
insightsTable: [
{
title: 'App Code',
patterns: ['**/src/**'],
icon: 'π―'
},
{
title: 'Node Modules',
patterns: ['**/node_modules/**'],
icon: 'π¦'
}
]
};
Market Research - Viewer
sonda.dev
Repo: https://sonda.dev/
Market Research - CI
SizeLimit
Repo: https://github.com/ai/size-limit
Setup
import type { SizeLimitConfig } from '../../packages/size-limit'
module.exports = [
{
path: "index.js",
import: "{ createStore }",
limit: "500 ms"
}
] satisfies SizeLimitConfig
Relevant Options:
- path: relative paths to files. The only mandatory option.
It could be a path"index.js"
, a [pattern]"dist/app-*.js"
or an array["index.js", "dist/app-*.js", "!dist/app-exclude.js"]
. - import: partial import to test tree-shaking. It could be
"{ lib }"
to testimport { lib } from 'lib'
,*
to test all exports,
or{ "a.js": "{ a }", "b.js": "{ b }" }
to test multiple files. - limit: size or time limit for files from the
path
option. It should be
a string with a number and unit, separated by a space.
Format:100 B
,10 kB
,500 ms
,1 s
. - name: the name of the current section. It will only be useful
if you have multiple sections. - message: an optional custom message to display additional information,
such as guidance for resolving errors, relevant links, or instructions
for next steps when a limit is exceeded.
- gzip: with
true
it will use Gzip compression and disable
Brotli compression. - brotli: with
false
it will disable any compression. - ignore: an array of files and dependencies to exclude from
the project size calculation.
Bundle Stats
repo: https://github.com/relative-ci/bundle-stats?tab=readme-ov-file
Setup
const { BundleStatsWebpackPlugin } = require('bundle-stats-webpack-plugin');
module.exports = {
plugins: [
new BundleStatsWebpackPlugin({
compare: true,
baseline: true,
html: true
})
]
};
Relevant Options
compare
| Enable comparison to baseline bundlebaseline
| Save stats as baseline for future runshtml
| Output visual HTML reportjson
| Output JSON snapshotstats
| (advanced) Customize Webpack stats passed into pluginsilent
| Disable logging
BundleMon
Repo: https://github.com/LironEr/bundlemon
Setup
"bundlemon": {
"baseDir": "./build",
"files": [
{
"path": "index.html",
"maxSize": "2kb",
"maxPercentIncrease": 5
},
{
"path": "bundle.<hash>.js",
"maxSize": "10kb"
},
{
"path": "assets/**/*.{png,svg}"
}
]
}
Relevant Options
path
(string, required) β Glob pattern relative to baseDir (e.g."**/*.js"
)friendlyName
(string, optional) β Human-readable name (e.g."Main Bundle"
)compression
("none" | "gzip", optional) β Override default compression (e.g."gzip"
)maxSize
(string, optional) β Max size:"2000b"
,"20kb"
,"1mb"
maxPercentIncrease
(number, optional) β Max % increase:0.5
= 0.5%,4
= 4%,200
= 200%