skilldrop-cli 0.16.36 → 0.16.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +1 -1
  2. package/catalogue.json +3 -1
  3. package/model-routing.json +8 -0
  4. package/package.json +1 -1
  5. package/packs/ai-engineering/skills/judge-audit/SKILL.md +157 -0
  6. package/packs/ai-engineering/skills/judge-audit/evals/eval_queries.json +11 -0
  7. package/packs/ai-engineering/skills/judge-audit/evals/evals.json +136 -0
  8. package/packs/ai-engineering/skills/judge-audit/evals/files/1/judgements.csv +321 -0
  9. package/packs/ai-engineering/skills/judge-audit/evals/files/1/notes.md +28 -0
  10. package/packs/ai-engineering/skills/judge-audit/evals/files/2/pairwise.csv +101 -0
  11. package/packs/ai-engineering/skills/judge-audit/evals/files/2/run-notes.md +16 -0
  12. package/packs/ai-engineering/skills/judge-audit/evals/files/4/grades.csv +97 -0
  13. package/packs/ai-engineering/skills/judge-audit/evals/files/5/pairwise_judgements.jsonl +120 -0
  14. package/packs/ai-engineering/skills/judge-audit/evals/files/6/rag-judge/README.md +36 -0
  15. package/packs/ai-engineering/skills/judge-audit/evals/files/6/rag-judge/judgments.csv +301 -0
  16. package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/gold_labels.csv +101 -0
  17. package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/run_1.jsonl +100 -0
  18. package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/run_2.jsonl +100 -0
  19. package/packs/ai-engineering/skills/judge-audit/evals/files/8/patches.csv +241 -0
  20. package/packs/ai-engineering/skills/judge-audit/evals/files/9/adequacy_ratings.csv +151 -0
  21. package/packs/ai-engineering/skills/judge-audit/examples/summary-panel.md +70 -0
  22. package/packs/ai-engineering/skills/judge-audit/manifest.json +44 -0
  23. package/packs/ai-engineering/skills/judge-audit/reference.md +176 -0
  24. package/packs/ai-engineering/skills/judge-audit/scripts/judge_stats.py +1735 -0
  25. package/packs/frontend/pack.json +9 -3
  26. package/packs/frontend/skills/bundle-size-review/SKILL.md +114 -0
  27. package/packs/frontend/skills/bundle-size-review/evals/eval_queries.json +12 -0
  28. package/packs/frontend/skills/bundle-size-review/evals/evals.json +108 -0
  29. package/packs/frontend/skills/bundle-size-review/evals/files/1/package.json +28 -0
  30. package/packs/frontend/skills/bundle-size-review/evals/files/1/src/utils/format.js +26 -0
  31. package/packs/frontend/skills/bundle-size-review/evals/files/1/stats.json +1334 -0
  32. package/packs/frontend/skills/bundle-size-review/evals/files/2/PR.md +21 -0
  33. package/packs/frontend/skills/bundle-size-review/evals/files/2/meta-after.json +440 -0
  34. package/packs/frontend/skills/bundle-size-review/evals/files/2/meta-before.json +360 -0
  35. package/packs/frontend/skills/bundle-size-review/evals/files/3/package.json +29 -0
  36. package/packs/frontend/skills/bundle-size-review/evals/files/3/stats.json +1583 -0
  37. package/packs/frontend/skills/bundle-size-review/evals/files/4/src/components/CommentBox.jsx +24 -0
  38. package/packs/frontend/skills/bundle-size-review/evals/files/4/stats/after.json +299 -0
  39. package/packs/frontend/skills/bundle-size-review/evals/files/4/stats/before.json +275 -0
  40. package/packs/frontend/skills/bundle-size-review/evals/files/5/.browserslistrc +3 -0
  41. package/packs/frontend/skills/bundle-size-review/evals/files/5/build/meta.json +371 -0
  42. package/packs/frontend/skills/bundle-size-review/evals/files/5/build.mjs +18 -0
  43. package/packs/frontend/skills/bundle-size-review/evals/files/5/package-lock.json +39 -0
  44. package/packs/frontend/skills/bundle-size-review/evals/files/5/package.json +22 -0
  45. package/packs/frontend/skills/bundle-size-review/evals/files/5/src/components/Icon.jsx +8 -0
  46. package/packs/frontend/skills/bundle-size-review/evals/files/5/src/polyfills.js +7 -0
  47. package/packs/frontend/skills/bundle-size-review/evals/files/5/src/utils/groupRows.js +4 -0
  48. package/packs/frontend/skills/bundle-size-review/evals/files/6/.browserslistrc +3 -0
  49. package/packs/frontend/skills/bundle-size-review/evals/files/6/package.json +25 -0
  50. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/App.tsx +8 -0
  51. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/chartLocales.ts +16 -0
  52. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/index.ts +8 -0
  53. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/messages/de.ts +5 -0
  54. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/messages/en.ts +5 -0
  55. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/useLocale.ts +14 -0
  56. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/main.tsx +10 -0
  57. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/pages/Home.tsx +14 -0
  58. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/pages/Reports.tsx +31 -0
  59. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/routes.tsx +17 -0
  60. package/packs/frontend/skills/bundle-size-review/evals/files/6/stats.json +1869 -0
  61. package/packs/frontend/skills/bundle-size-review/evals/files/6/vite.config.ts +13 -0
  62. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/package.json +26 -0
  63. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/App.tsx +27 -0
  64. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/pages/Analytics.tsx +10 -0
  65. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/pages/Users.tsx +13 -0
  66. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/stats.json +1421 -0
  67. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/webpack.config.js +25 -0
  68. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/package.json +27 -0
  69. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/App.tsx +18 -0
  70. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/index.tsx +4 -0
  71. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/pages/Home.tsx +10 -0
  72. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/pages/Product.tsx +13 -0
  73. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/stats.json +1496 -0
  74. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/webpack.config.js +26 -0
  75. package/packs/frontend/skills/bundle-size-review/evals/files/7/package.json +9 -0
  76. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Button.css +3 -0
  77. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Button.js +14 -0
  78. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Chart.js +21 -0
  79. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/index.js +22 -0
  80. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/package.json +20 -0
  81. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Button.css +3 -0
  82. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Button.tsx +9 -0
  83. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Chart.tsx +14 -0
  84. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/index.ts +6 -0
  85. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/tsconfig.json +14 -0
  86. package/packs/frontend/skills/bundle-size-review/examples/inputs/stats-after.json +1358 -0
  87. package/packs/frontend/skills/bundle-size-review/examples/inputs/stats-before.json +1334 -0
  88. package/packs/frontend/skills/bundle-size-review/examples/larkspur-sparkline.md +52 -0
  89. package/packs/frontend/skills/bundle-size-review/manifest.json +52 -0
  90. package/packs/frontend/skills/bundle-size-review/reference.md +147 -0
  91. package/packs/frontend/skills/bundle-size-review/scripts/bundle_stats.py +1633 -0
  92. package/packs/frontend/skills/web-performance-review/SKILL.md +3 -3
  93. package/packs/frontend/skills/web-performance-review/manifest.json +2 -1
@@ -0,0 +1,52 @@
1
+ # Worked example: a chart library moves into the first load while the total stays flat
2
+
3
+ ## Input
4
+
5
+ > PR #241 on larkspur-dispatch-web adds a small jobs-per-hour sparkline to the dashboard header. Our CI checks that total JS stays under 850 KB and it passed. A reviewer says the dashboard feels slower to open. Before and after webpack stats are attached (`inputs/stats-before.json`, `inputs/stats-after.json`). Did this PR make the first load heavier?
6
+
7
+ ## What the script printed (abridged)
8
+
9
+ ```bash
10
+ python3 scripts/bundle_stats.py --compare examples/inputs/stats-before.json examples/inputs/stats-after.json --budget main=330kB
11
+ ```
12
+
13
+ ```text
14
+ Chunk sizes: emitted asset bytes (after minification, before compression).
15
+ Module and package sizes: webpack module size, before minification.
16
+ Compressed sizes: not measured; no built files were read (pass --dist DIR after a build).
17
+
18
+ All JS chunks: 812,606 B (812.6 kB) -> 810,657 B (810.7 kB) (-1,949 B); initial +492,392 B, lazy -494,341 B
19
+
20
+ Initial JS per entry
21
+ main: 311,282 B (311.3 kB) -> 803,674 B (803.7 kB) (+492,392 B)
22
+
23
+ Chunks that changed
24
+ reports lazy 498,212 B -> 3,871 B (-494,341 B) changed; loaded with import() from src/routes.js
25
+ vendors initial 301,412 B -> 792,840 B (+491,428 B) changed
26
+ main initial 9,870 B -> 10,834 B (+964 B) changed
27
+
28
+ Packages that changed (sorted by change in initial size)
29
+ echarts total 503,088 B -> 503,088 B (0 B); initial 0 B -> 503,088 B (+503,088 B) changed
30
+ chain: src/index.js > src/App.jsx > src/components/JobsSparkline.jsx > node_modules/echarts/index.js
31
+ zrender total 167,660 B -> 167,660 B (0 B); initial 0 B -> 167,660 B (+167,660 B) changed
32
+ moved from lazy to initial: echarts, zrender
33
+
34
+ Project files (root: examples/inputs)
35
+ found: (none)
36
+ absent: package.json, .browserslistrc, bundler config (webpack/vite/rollup/esbuild config, build.mjs), lockfile
37
+ bundled source files not on disk here (9): src/App.jsx, ..., src/components/JobsSparkline.jsx, ...
38
+
39
+ Budgets on each entry's initial JS, before -> after
40
+ main: budget 330,000 B (raw); before 311,282 B (PASS), after 803,674 B (OVER)
41
+ ```
42
+
43
+ ## Reply
44
+
45
+ Yes, the reviewer is right: the first load went from 311,282 to 803,674 bytes of JavaScript (+492,392), while the total fell by 1,949 bytes, which is why the 850 KB total check passed. The new `JobsSparkline.jsx` imports echarts directly, and that pulled echarts and zrender out of the lazy `reports` chunk (498,212 → 3,871 bytes) into `vendors`, which every page loads before the first screen. Sizes are webpack's emitted files, minified and before compression; no built files were attached, so there is no compressed figure here.
46
+
47
+ 1. **Load the sparkline's chart code on demand.** Chain: `src/index.js` → `App.jsx` → `components/JobsSparkline.jsx` → `echarts`. Render the header without the sparkline, then `import("echarts/core")` inside the component and draw once it resolves. Check the reports page still opens its charts, and that the header doesn't jump when the sparkline appears (reserve its height).
48
+ 2. **Register only the line chart.** The bare `echarts` import brings every chart type and component; the moved modules include geo, dataZoom and bar views that a line sparkline never draws. Switch to `echarts/core` with `use([LineChart, GridComponent, CanvasRenderer])` plus what the reports page draws; open every report chart afterwards, since an unregistered type fails at runtime. Confirm the installed echarts is 5.x first (no `package.json` was attached), since `echarts/core` arrived in 5.
49
+
50
+ **Guard:** a per-entry limit on `main`'s initial JavaScript would have failed this PR: webpack `performance: { hints: "error", maxEntrypointSize: 330000 }` passes today's 311,282 bytes and fails 803,674. The total-size limit can't see a lazy-to-initial move; keep it, but add the per-entry one.
51
+
52
+ To confirm: whether the sparkline has to show on first paint. If it does, a plain SVG path drawn from the hourly counts needs no chart library at all.
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "bundle-size-review",
3
+ "version": "0.2.0",
4
+ "description": "Find what makes a JavaScript bundle heavy and what can safely come out, from the bundler's stats file (webpack stats.json, an esbuild metafile, or the rollup-plugin-visualizer raw-data JSON that Vite builds use) read together with the repo around it (package.json, .browserslistrc, the lockfile, the bundler config and the source files that import the heavy modules). Gives each entry's first-load JavaScript in the exact bytes the stats record, kept apart from lazy chunks; finds whole-library imports, CommonJS packages that can't be tree-shaken, packages bundled twice and polyfills the browser targets don't need; names the import chain that pulls each module in before suggesting a removal; and, given before and after stats, says what grew, why, whether it lands in the first load and which per-entry budget would have caught it. Use when the user says \"why is our bundle so big\", \"what can we cut from the bundle\", \"review this stats.json\", \"why is lodash or moment in our bundle\", \"the bundle grew in this PR\", or \"set a bundle size budget\".",
5
+ "entrypoint": "SKILL.md",
6
+ "deps": {
7
+ "npm": [],
8
+ "pip": []
9
+ },
10
+ "env": {
11
+ "required": [],
12
+ "optional": []
13
+ },
14
+ "related": [
15
+ "dependency-upgrade-review",
16
+ "pre-merge-review",
17
+ "rendering-strategy",
18
+ "web-performance-review"
19
+ ],
20
+ "tags": [
21
+ "frontend",
22
+ "bundle-size",
23
+ "webpack",
24
+ "tree-shaking",
25
+ "performance-budget"
26
+ ],
27
+ "permissions": {
28
+ "network": [
29
+ "registry.npmjs.org"
30
+ ],
31
+ "commands": [
32
+ "npx",
33
+ "npm",
34
+ "pnpm",
35
+ "yarn"
36
+ ],
37
+ "files": "named-paths",
38
+ "notes": "The script reads only the stats files it is given (or stdin), the project files next to them (package.json, .browserslistrc, the lockfile, bundler and TypeScript configs, and source files outside node_modules/dist/build, or the --root folder) and, with --dist, the built files in that folder; it writes only the -o path and makes no network calls. The instructions run the project's own bundler through npx --no to produce the stats, npm ls / pnpm why / yarn why to read versions, and npm view / npm dedupe, which reach the npm registry, to check which versions a dependent accepts and to align duplicates."
39
+ },
40
+ "model": {
41
+ "tier": "standard",
42
+ "rationale": "Sizes, duplicates and what pulls each module in are computed from the bundler's stats file by its script; the judgment is which removal is safe."
43
+ },
44
+ "handoff": [
45
+ {
46
+ "to": "web-performance-review",
47
+ "when": "the user's real question is why the page is slow for users, with a Lighthouse or PageSpeed report in hand",
48
+ "purpose": "Rank fixes by how much each moves LCP, INP and CLS, of which JavaScript weight is only one cause.",
49
+ "fallback": "If web-performance-review is absent, say the bundle findings cover download and parse cost only, and name the Lighthouse audits (LCP element, long tasks, render-blocking resources) that would show whether JavaScript is the bottleneck."
50
+ }
51
+ ]
52
+ }
@@ -0,0 +1,147 @@
1
+ # bundle-size-review reference
2
+
3
+ ## Producing the stats file
4
+
5
+ webpack 5 (webpack-cli 4 or later writes the JSON straight to the file, so build logs cannot corrupt it):
6
+
7
+ ```bash
8
+ npx --no webpack --mode production --json=stats.json
9
+ ```
10
+
11
+ esbuild, from the CLI or from `esbuild.build({ metafile: true })` with `result.metafile` written to a file:
12
+
13
+ ```bash
14
+ npx --no esbuild src/main.tsx --bundle --minify --splitting --format=esm --outdir=dist --metafile=meta.json
15
+ ```
16
+
17
+ Vite or Rollup: add rollup-plugin-visualizer with the raw-data template, then run the normal build. `gzipSize` and `brotliSize` make the plugin record compressed sizes per module, which beats an estimate.
18
+
19
+ ```js
20
+ // vite.config.js
21
+ import { visualizer } from "rollup-plugin-visualizer";
22
+ export default {
23
+ plugins: [visualizer({ filename: "stats.json", template: "raw-data", gzipSize: true, brotliSize: true })],
24
+ };
25
+ ```
26
+
27
+ Keep the built folder (`dist/`) next to the stats and pass it with `--dist`: the script then gzips the real emitted files (level 9). Without it, no compressed size is printed.
28
+
29
+ ## What each format carries, and what its sizes mean
30
+
31
+ | Format | Detected by | Raw size of a chunk | Module size | Initial vs lazy | Importers |
32
+ |---|---|---|---|---|---|
33
+ | webpack stats | `assets`, `chunks`, `modules`, `entrypoints` | emitted asset bytes, after minification (`assets[].size`) | `modules[].size`, **before minification** | `chunks[].initial`; an entry's chunks are `entrypoints.<name>.chunks` | `modules[].reasons[].moduleName`, with `type` (`import()` = lazy) and `userRequest` |
34
+ | esbuild metafile | `inputs` and `outputs` objects | `outputs[].bytes`, minified if `--minify` was on | `outputs[].inputs[].bytesInOutput`, what each input adds to that output | entry outputs have `entryPoint`; their `imports` of kind `import-statement` are initial, `dynamic-import` are lazy | `inputs[].imports[]` with `kind` and `original` |
35
+ | rollup-plugin-visualizer raw data | `tree`, `nodeParts`, `nodeMetas` | sum of `renderedLength`, after tree-shaking, before minification unless the plugin runs after the minifier | `nodeParts[].renderedLength`, plus `gzipLength` / `brotliLength` when enabled | modules reached from an `isEntry` module by static imports are initial; `imported[].dynamic` marks lazy | `nodeMetas[].importedBy` |
36
+
37
+ Consequences for the reply:
38
+
39
+ - Quote these bytes exactly, in one unit system: bytes, with kB = 1000 bytes beside them. Name the kind once ("webpack module sizes, before minification"; "esbuild output bytes, minified, uncompressed").
40
+ - A webpack stats file without `assets` has no emitted sizes: each chunk's size is the sum of its module sizes, before minification. Say so; the emitted figure comes from a build.
41
+ - webpack module sizes and visualizer rendered lengths rank modules; they are not the download. The saving from a removal is measured by rebuilding and running `--compare`.
42
+ - Prebuilt production files (`react-dom.production.min.js`) are already minified, so their module size is close to what ships.
43
+ - esbuild lists `.js.map` outputs beside the code; they are not downloaded by users and stay out of every count.
44
+
45
+ ## Compressed sizes
46
+
47
+ | Basis | Where it comes from | How to word it |
48
+ |---|---|---|
49
+ | built-file | the emitted files under `--dist`, gzipped at level 9 | "measured from the built files" |
50
+ | visualizer | sum of the plugin's per-module gzip / brotli lengths | "from the visualizer"; slightly above the whole-file figure, because each module is compressed alone |
51
+ | none | no built files and no visualizer gzip | no figure. Say how to measure it after a build: gzip the entry's `.js` files at level 9 (`gzip -9c`) and count the bytes (`wc -c`) |
52
+
53
+ A ratio applied to raw bytes is a guess, and a guess in the lead line reads as a measurement. Leave it out.
54
+
55
+ ## Project files read around the stats
56
+
57
+ The script picks the project root as the first of the stats folder, its parent and its grandparent that holds `package.json` or `.browserslistrc` (else one with `src/`), or `--root`. It prints what it found and what is absent, so a reply can say "no `.browserslistrc`, no `browserslist` field in `package.json`" only when that is what was looked for.
58
+
59
+ | File | What it gives | Used for |
60
+ |---|---|---|
61
+ | `.browserslistrc`, `package.json` `browserslist` | browser queries (the `[production]` section when present) | polyfill check; plain `chrome >= 110` style queries resolve offline, others (`defaults`, `> 0.5%`) need `npx --no browserslist` |
62
+ | `webpack.config.*`, `vite.config.*`, `rollup.config.*`, `build.mjs` / `build.js`, `esbuild.config.*` (root, `config/`, `scripts/`) | `target` (esbuild style `chrome110`), `performance.maxEntrypointSize` / `hints` | the targets when no browserslist exists (esbuild lowers syntax, it does not add polyfills); existing budgets |
63
+ | `tsconfig.json` | `target` | syntax level only; it never decides polyfills |
64
+ | `package-lock.json` / `npm-shrinkwrap.json` (v1 to v3) | installed version per install path, each package's declared ranges | versions of duplicate copies, who asks for which range, API checks before suggesting a fix |
65
+ | `yarn.lock`, `pnpm-lock.yaml` | listed, not parsed | read versions with `yarn why` / `pnpm why` |
66
+ | source files (outside `node_modules`, `dist`, `build`) | who imports each bundled package, namespace imports read with `X[...]`, which files use a polyfilled feature | the fix lands in a named file |
67
+
68
+ The polyfill table in the script holds the first browser version with each feature native (MDN browser-compat data) for the common core-js modules. core-js sometimes keeps a polyfill longer to patch a spec bug, so a rebuild confirms each removal. A module outside the table is reported as "check by hand".
69
+
70
+ ## Version-correct fixes
71
+
72
+ - Read the installed version before naming an API, an import path or an option. `echarts/core` exists from echarts 5; `lodash-es` named imports need an ESM-aware build; `@aws-sdk/client-*` is a different package from `aws-sdk`.
73
+ - Never fix a duplicate by moving the app's own range down a major: that is a downgrade of the app.
74
+ - `overrides` (npm), `pnpm.overrides` or `resolutions` (Yarn) that force a dependent onto a major it never declared is a change to test by hand, not a free removal. Check whether a newer dependent accepts the newer major, with network access:
75
+
76
+ ```bash
77
+ npm view @harbor/calendar versions
78
+ npm view @harbor/calendar@latest dependencies
79
+ ```
80
+
81
+ ## Reading the stats by hand
82
+
83
+ When no script can run:
84
+
85
+ 1. **webpack:** take `entrypoints.main.assets[]` (or the `files` of each chunk in `entrypoints.main.chunks`), keep `.js`, add their `size` from `assets[]`. That is the initial raw size. Every chunk with `"initial": false` is lazy; its `origins[].moduleName` is the file that loads it.
86
+ 2. **esbuild:** start at the output whose `entryPoint` is the app entry, follow its `imports` of kind `import-statement` through `outputs`, and add the `bytes` of each. Outputs reached only through `dynamic-import` are lazy.
87
+ 3. **visualizer:** each top-level child of `tree` is an output file; add `gzipLength` over the `nodeParts` of the files the entry loads statically.
88
+ 4. **Chains:** from the module, follow its importers (`reasons`, `inputs[].imports` reversed, `importedBy`) until reaching the entry. Prefer the chain with no `import()` step: that is the one that puts it in the first load.
89
+
90
+ ## Replacements for the usual heavy packages
91
+
92
+ | In the bundle | Change | What to check after |
93
+ |---|---|---|
94
+ | `lodash/lodash.js` from `import _ from "lodash"` | per-method imports (`import groupBy from "lodash/groupBy"`) or named imports from `lodash-es`; native methods where the targets have them | every importer the stats list; `_.chain(...)` usage, which needs the whole library; that Babel or TypeScript is not compiling ESM imports to `require` |
95
+ | `moment` (and `moment/locale/*` or `moment-with-locales`) | date-fns, Day.js, Luxon or `Intl.DateTimeFormat`; to keep moment, drop the locales (webpack: `new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ })`, then import the locales in use) | moment objects mutate and the others don't; lenient parsing of non-ISO strings; time zones (moment-timezone); every locale shown to users. If a dependency imports moment, the dependency has to change, not the app |
96
+ | `core-js` from `import "core-js/stable"` or `@babel/polyfill` | `@babel/preset-env` with `useBuiltIns: "usage"`, `corejs: 3` and the project's `browserslist`; for Vite, `@vitejs/plugin-legacy` serves polyfills only to old browsers | run `npx --no browserslist` to see the real targets; the features the polyfills cover against those targets |
97
+ | full `echarts` from `import * as echarts from "echarts"` | `echarts/core` with `use([...])` for the charts, components and renderer in use | open every chart: a type that is not registered fails at runtime |
98
+ | `chart.js/auto` | import the controllers, elements and scales in use and `Chart.register(...)` them | every chart type and plugin |
99
+ | `aws-sdk` (v2) in browser code | the modular `@aws-sdk/client-*` packages | that each call's request and error shapes match the v3 client |
100
+ | an icon pack or component library through its index | per-path imports, or confirm the package is ESM with `"sideEffects": false` so the index is shaken | the bundle after rebuild: compare with `--compare` |
101
+ | a CommonJS package with an ESM build | point the import at the ESM entry (`module` / `exports`) | that the bundler resolves the ESM file (the stats path shows it) |
102
+
103
+ ## Side effects and barrel files
104
+
105
+ A bundler keeps a module, used or not, when it might have side effects. `"sideEffects": false` in a package's `package.json` (or a list such as `["*.css"]`) tells it that unused re-exports can go. App code has the same problem: `src/components/index.ts` re-exporting forty components, one of which registers something at the top level, keeps all forty. Symptoms in the stats: many modules from one folder or package, all reached through one `index` file, most of them never imported by anything else.
106
+
107
+ ## Duplicates and package managers
108
+
109
+ - npm hoists one copy to `node_modules/<pkg>` and nests another under `node_modules/<dependent>/node_modules/<pkg>` only when the hoisted version does not satisfy that dependent's range. Two install paths means two versions.
110
+ - Versions are in the path only for pnpm (`node_modules/.pnpm/<pkg>@<version>/...`) and Yarn PnP caches. Otherwise read them:
111
+
112
+ ```bash
113
+ npm ls date-fns
114
+ pnpm why date-fns
115
+ yarn why date-fns
116
+ ```
117
+
118
+ - To get one copy when the ranges overlap: reinstall so one version satisfies both (`npm dedupe`). When they don't (the dependent wants 2.x and the app 3.x), the options are a newer dependent that accepts 3.x, or an override tested by hand; see "Version-correct fixes".
119
+
120
+ ```bash
121
+ npm dedupe
122
+ ```
123
+
124
+ - Two copies of React, Vue, or a state library are a bug as well as a size cost (hooks fail with "Invalid hook call", stores split). Name that when it applies.
125
+ - The same module in several webpack chunks (not two installs, one module copied) means the split-chunks settings did not extract it to a shared chunk.
126
+
127
+ ## Budgets
128
+
129
+ A budget is a limit on one entry's initial JavaScript, in the unit of the tool that enforces it. Set it between the good build and the bad one: the current figure plus about 5%, or the team's target. The script checks it on the stats' own bytes, or on measured gzip:
130
+
131
+ ```bash
132
+ python3 scripts/bundle_stats.py stats.json --budget main=320kB
133
+ python3 scripts/bundle_stats.py stats.json --dist dist/ --budget main=gzip:100kB
134
+ ```
135
+
136
+ Exit 1 fails the job. Sizes are bytes or carry a `kB` (1000) or `KiB` (1024) suffix; `--budget '*=320kB'` applies to every entry; repeat `--budget` for several. A gzip budget with no built files is reported as not measured rather than passed.
137
+
138
+ webpack's own check counts emitted bytes, minified and uncompressed, per entrypoint:
139
+
140
+ ```js
141
+ // webpack.config.js
142
+ module.exports = { performance: { hints: "error", maxEntrypointSize: 330000, maxAssetSize: 250000 } };
143
+ ```
144
+
145
+ When the stats carry only module sizes (no `assets`), read the entry's emitted size from one build before fixing `maxEntrypointSize`. size-limit measures compressed size from the built files itself, so its limit is a compressed figure.
146
+
147
+ A total-size limit across all chunks is the wrong guard on its own: it passes a change that moves a library from a lazy chunk into the entry (the total stays flat) and fails a change that only grows a route few users open.