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
π #??? β BundleBudget Plugin PoC Implementation
Metric
Bundle size over time across key dimensions.
Parsed from --stats-json
output and grouped by logical artifact buckets.
Property | Value | Description |
---|---|---|
value | 132341 |
Total size of all chunks. |
displayValue | 13.4 MB |
Delta compared to base (cached or previous release). |
score | 1 |
1 if within budget or unchanged, 0 if regression is detected. |
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 Angular/Esbuild builds. - Identify and compare main, initial, and lazy chunks.
- Use chunk input fingerprinting to map renamed chunk files.
- Group chunk sizes by route/product (e.g.,
/route-1
,/route-2
). - Store and compare bundle stats across versions/releases.
Integration Requirements
TODO:
- Scoring (compare against budget/trashhold)
- identify hashed files
- unified data
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
stats.json
The stats.json follows the following types
/**
* Describes the kind of an import. This is a string literal union
* for better type safety and autocompletion.
*/
export type ImportKind =
| 'entry-point'
// An import statement, e.g., `import "foo"`
| 'import-statement'
// A require call, e.g., `require("foo")`
| 'require-call'
// A dynamic import, e.g., `import("foo")`
| 'dynamic-import'
// A require.resolve call, e.g., `require.resolve("foo")`
| 'require-resolve'
// An @import rule in CSS
| 'import-rule'
// A url() token in CSS
| 'url-token';
/**
* Represents a single import record within a file.
*/
export interface MetafileImport {
/**
* The path of the imported file, relative to the working directory.
*/
path: string;
/**
* The kind of the import.
*/
kind: ImportKind;
/**
* If true, this dependency is external and was not included in the bundle.
*/
external?: boolean;
/**
* The original path string as it appeared in the source code.
* Useful for context, especially with aliases or tsconfig paths.
*/
original?: string;
}
/**
* Details about a single input file that contributed to the bundle.
*/
export interface MetafileInput {
/**
* The size of the file in bytes.
*/
bytes: number;
/**
* A list of all imports within this file.
*/
imports: MetafileImport[];
}
/**
* Details about the contribution of a single input file to a specific output file.
*/
export interface MetafileOutputInput {
/**
* The number of bytes from this input file that are part of this specific output file.
*/
bytesInOutput: number;
}
/**
* Represents a single output file (a "chunk") from the build process.
*/
export interface MetafileOutput {
/**
* The total size of this output file in bytes.
*/
bytes: number;
/**
* A map of all input files that were bundled into this output file.
* The key is the input file path, and the value provides details about its contribution.
*/
inputs: {
[path: string]: MetafileOutputInput;
};
/**
* A list of imports that were not bundled and remain as import/require statements in the output.
* This is common for external packages.
*/
imports: MetafileImport[];
/**
* A list of all exported symbols from this output file.
*/
exports: string[];
/**
* The entry point that corresponds to this output file. This is only present if the
* output file is an entry point.
*/
entryPoint?: string;
}
/**
* The root structure of the metafile JSON generated by esbuild.
*/
export interface EsbuildMetafile {
/**
* A map of all input files involved in the build.
* The key is the file path relative to the working directory.
*/
inputs: {
[path: string]: MetafileInput;
};
/**
* A map of all output files generated by the build.
* The key is the file path relative to the `outdir`.
*/
outputs: {
[path: string]: MetafileOutput;
};
}
Example stats.json
{
"inputs": {
"node_modules/@angular/core/fesm2022/untracked-BKcld_ew.mjs": {
"bytes": 20949,
"imports": [
{
"path": "<runtime>",
"kind": "import-statement",
"external": true
}
],
"format": "esm"
},
"node_modules/@angular/core/fesm2022/primitives/di.mjs": {
"bytes": 1158,
"imports": [],
"format": "esm"
},
"node_modules/@angular/core/fesm2022/primitives/signals.mjs": {
"bytes": 3167,
"imports": [
{
"path": "node_modules/@angular/core/fesm2022/untracked-BKcld_ew.mjs",
"kind": "import-statement",
"original": "../untracked-BKcld_ew.mjs"
},
{
"path": "node_modules/@angular/core/fesm2022/untracked-BKcld_ew.mjs",
"kind": "import-statement",
"original": "../untracked-BKcld_ew.mjs"
},
{
"path": "<runtime>",
"kind": "import-statement",
"external": true
}
],
"format": "esm"
}
},
"outputs": {
"chunk-MXYP3LXH.js.map": {
"imports": [],
"exports": [],
"inputs": {},
"bytes": 15799
},
"chunk-MXYP3LXH.js": {
"imports": [
{
"path": "chunk-PH2Q42UX.js",
"kind": "import-statement"
},
{
"path": "chunk-A3IGLJVE.js",
"kind": "import-statement"
},
{
"path": "chunk-MMWVBYNW.js",
"kind": "import-statement"
},
{
"path": "chunk-KTVYRR64.js",
"kind": "import-statement"
}
],
"exports": [
"a",
"b"
],
"inputs": {
"lib/re-captcha.ts": {
"bytesInOutput": 165
},
"lib/re-captcha.service.ts": {
"bytesInOutput": 2408
},
"lib/re-captcha-noop.service.ts": {
"bytesInOutput": 342
},
"lib/recaptcha.module.ts": {
"bytesInOutput": 120
},
"index.ts": {
"bytesInOutput": 0
}
},
"bytes": 3345
},
"chunk-5HVHEFQE.js.map": {
"imports": [],
"exports": [],
"inputs": {},
"bytes": 3942
},
"chunk-5HVHEFQE.js": {
"imports": [
{
"path": "chunk-J5C35BAI.js",
"kind": "import-statement"
},
{
"path": "chunk-PUHQEZG3.js",
"kind": "import-statement"
},
{
"path": "chunk-ZQTK7NVT.js",
"kind": "import-statement"
},
{
"path": "chunk-KTVYRR64.js",
"kind": "import-statement"
}
],
"exports": [
"a"
],
"inputs": {
"auth-state.service.ts": {
"bytesInOutput": 732
}
},
"bytes": 946
}
}
}
Crawling the filesystem
TBD
Setup and Requirements
π¦ Package Dependencies
- Dev Dependencies:
- None required, optional CLI runner for local debugging.
- Optional Dependencies:
- esbuild β or any tool that emits
--metafile
/stats.json
.
- esbuild β or any tool that emits
π Configuration Files
angular.json
/vite.config.ts
or equivalent β for custom build config.- No required config file for the plugin itself.
Implementation details
const pluginConfig = {
generateStats: "nx run app-1:build --stats";
statsPath: "./stats.json"
customAudits: [
{
name: 'Main App Core',
include: ['src/main.ts', 'src/app/**'],
thresholds: {
percent: 0.05
}
},
{
name: 'Auth Module',
include: ['src/app/auth/**'],
thresholds: {
bytes: 102400
}
},
{
name: 'Lazy Products',
include: ['src/app/products/**', '!src/app/products/shared/**'],
thresholds: {
percent: 0.02
}
}
]
}
MD Report
Code PushUp Report
π· Category | β Score | π‘ Audits |
---|---|---|
Bundle Size | π‘ 54 | 13 |
π· Categories
Performance
Bundle size metrics π Docs
π’ Score: 92
- π₯ Bundle size changes InitialChunk (BundleSize) - 1 warning
- π‘ Bundle size changes InitialChunk (BundleSize)
π‘οΈ Audits
Bundle size changes InitialChunk (CodePushUp)
π₯ 3 info (score: 0)
Issues
Severity | Message | Source file | Line(s) |
---|---|---|---|
βΉοΈ info | 200 KB | src/components/CreateTodo.jsx |
|
βΉοΈ info | 200 KB | src/components/TodoFilter.jsx |
|
βΉοΈ info | 200 KB | src/components/TodoFilter.jsx |
Alternative tree
βββ index.ts 0.0 KB
βββ lib/recaptcha.module.ts 0.1 KB
βββ lib/re-captcha.service.ts 2.4 KB
β βββ lib/re-captcha.ts 0.2 KB
βββ lib/re-captcha-noop.service.ts 0.3 KB
CI Comment
π Plugin | π‘οΈ Audit | π Previous value | π Current value | π Value change |
---|---|---|---|---|
Bundle size | Total | π¨ 12.0 MB | π₯ 13.1 MB | β +9.2β% |
Bundle size | Initial Chunks | π© 6.0 MB | π© 6.2 MB | β +3.3β% |
Bundle size | Lazy Chunks | π₯ 6.0 MB | π₯ 6.9 MB | β +15.0β% |
Bundle size | Main Bundle | π¨ 5.0 MB | π₯ 5.2 MB | β +4.0β% |
Bundle size | Auth Module | π¨ 1.1 MB | π₯ 1.3 MB | β +18.2β% |
Bundle size | Lazy Products | π© 3.0 MB | π© 3.1 MB | β +3.3β% |