Claude Cursor GitHub Copilot Skill

tree-shaking-dead-code-review

Verifies that a bundler's tree-shaking actually eliminated dead code by inspecting output bytes and sideEffects/module-format configuration, rather than trusting a clean build as proof of elimination.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_tree-shaking-dead-code-review-febe32a.zip · 12 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/tree-shaking-dead-code-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Tree-Shaking & Dead-Code Review

Purpose

"The build succeeded" and "the code was tree-shaken" are unrelated claims. A production build with zero errors can still ship a fully dead, unreferenced module byte-for-byte, because tree-shaking is not a guaranteed pass — it is a conditional static-analysis optimization that silently no-ops the moment it hits a CJS require, a missing or wrong sideEffects field, a barrel-file re-export, or a development-mode build. This skill treats "eliminated" as a claim that must be proven from bundle-analyzer output bytes and module lists, before and after, in a production build — never inferred from build success, and never inferred from a sideEffects: false change applied without reading the target module for real side effects first, since that specific mistake causes silent runtime breakage (dropped polyfills, dropped CSS injection, dropped security-relevant initialization), not just a size regression.

When to use

Use this skill when the user asks to:

  • verify why a supposedly-unused import is still present in bundle-analyzer output,
  • evaluate whether a new dependency is tree-shakeable before it is added,
  • configure or review a sideEffects field in package.json for an app or a library the user is authoring,
  • explain why a sideEffects: false change either had no effect or broke something at runtime.

When NOT to use

  • General bundle-size triage with no specific dead-code suspicion yet — use bundle-budget-code-splitting-review first to establish the numeric budget and rank contributors; hand off here once a specific module is suspected of being fully unused rather than merely heavy.
  • Deciding route- or component-level code-splitting boundaries — that is a splitting-boundary decision, not an elimination-verification decision; bundle-budget-code-splitting-review owns it.
  • Diagnosing which Core Web Vitals sub-phase is regressing with no analyzer report yet — hand off to core-web-vitals-triage first, return here once dead code is the named suspect.

Context7 Documentation Protocol

Tree-shaking configuration surface differs by bundler and has shifted across majors (Rolldown replacing Rollup inside Vite 7+, webpack's sideEffects/usedExports interaction, Rollup's treeshake preset system). Do not prescribe a fix from memorized training data.

  1. Call ToolSearch with query "context7" (or "select:mcp__Context7__resolve-library-id,mcp__Context7__query-docs") to load the Context7 tools if not already loaded this session.
  2. Call mcp__Context7__resolve-library-id for the bundler actually installed in the project (webpack, Rollup, or Vite/Rolldown) before prescribing any sideEffects or treeshake configuration — do not assume the bundler from the framework name alone.
  3. Call mcp__Context7__query-docs for the specific mechanism in question — e.g. "sideEffects array vs boolean", "treeshake.moduleSideEffects options", "production mode requirement for usedExports" — before ruling on it. Do this per review; do not reuse a prior session's memory of bundler internals.
  4. Context7-grounded facts to confirm, not assume:
    • webpack: tree-shaking requires mode: 'production' (or optimization.usedExports explicitly enabled) to take visible effect; sideEffects: false in package.json additionally requires optimization.providedExports (on by default in production mode) to let webpack drop unused-export modules; a module with real side effects (e.g. CSS imports) must be listed explicitly, e.g. "sideEffects": ["**/*.css"], or that side effect is silently dropped.
    • Rollup: treeshake accepts false, a preset ('smallest' | 'safest' | 'recommended'), or a fine-grained object — moduleSideEffects (boolean, 'no-external', a string array, or a predicate function) and propertyReadSideEffects are the two options most often responsible for either under- or over-aggressive elimination.
    • Vite 7+ ships Rolldown as its production bundler; build.rollupOptions is now an alias for build.rolldownOptions and is deprecated in favor of it — verify which option surface the installed Vite major actually documents before prescribing config keys.
  5. If Context7 is unavailable or returns no relevant match for the installed bundler, fall back to official_docs and mark the claim documentation-based (Context7 unavailable) rather than presenting it as freshly verified.
  6. Never invent a bundler config key, CLI flag, or package.json field that no queried source confirms.

Lean operating rules

  • Require confirmation the build ran in production mode before evaluating any tree-shaking claim. A development-mode build routinely skips or partially applies elimination by design; "it's still there in dev" proves nothing.
  • Determine the suspect dependency's actual module format from its package.json (exports/module with an import condition vs. a main/require-only CJS entry) — do not infer format from the package's popularity or age. CJS defeats static tree-shaking analysis regardless of bundler, and is the most common silent cause of "unused code that won't go away."
  • Check sideEffects in both the app's own package.json and the suspect dependency's package.json. A missing field or sideEffects: true tells the bundler to assume every import has a side effect and keep it — this is often correct behavior being mistaken for a bug.
  • Before proposing or accepting sideEffects: false on any module, read that module for top-level side-effecting code — global polyfills, CSS-in-JS injection, prototype patching, analytics auto-init, CSP nonce injection, sanitizer initialization. A false sideEffects: false claim is a correctness bug that ships silently; it will not show up as a build error.
  • Rule out a barrel-file re-export pattern (import * as utils from './utils', or a package index.js that re-exports everything) in the app's own code before blaming the dependency — this is a common tree-shaking blocker that has nothing to do with the dependency's configuration.
  • Never accept "no build errors" or "the build completed" as proof of elimination. Require a bundle-analyzer or output-file diff showing the specific module or byte range is actually absent, taken from a production build, before and after the change.
  • After any sideEffects: false change, require a runtime smoke test of the affected surface, not just a rebuild — dropped initialization code fails at runtime, not at build time.
  • Confirm the installed bundler and its major version via Context7 before prescribing exact config syntax — see Context7 Documentation Protocol. Do not assume a memorized API is still current.

References

Load these only when needed:

  • Module format and sideEffects field — use when determining whether a dependency is ESM or CJS, and when reading or writing the sideEffects field in package.json (webpack's flag semantics and the production-mode requirement).
  • Rollup and Vite treeshake options — use when the project is Rollup- or Vite/Rolldown-based and the review needs treeshake.moduleSideEffects, propertyReadSideEffects, or preset-level configuration.
  • ESM/CJS interop and verification — use when the suspect module's format is ambiguous, when CJS interop is suspected as the blocker, and for the before/after diff and runtime-smoke-test verification workflow that closes out every review.

Response minimum

Return, at minimum:

  • the module format (ESM/CJS) and sideEffects field state for both the app and the suspect dependency, cited from the actual package.json content read,
  • confirmation the evidence came from a production-mode build, not development mode,
  • the exact package.json or bundler-config change proposed, with syntax version-confirmed via Context7 against the installed bundler major,
  • the before/after bundle-analyzer byte and module-count diff proving elimination, not just build success,
  • a correctness caveat and runtime-smoke-test requirement whenever sideEffects: false is newly applied.
Files (vanguard-frontier-agentic)
  • references
    • esm-cjs-interop-and-verification.md 6 KB
      # ESM/CJS Interop and the Verification Workflow
      
      Use this reference when the suspect module's format is ambiguous, when CJS interop is suspected as the actual blocker, and for the before/after diff and runtime-smoke-test steps that close out every review in this skill.
      
      ## What people get wrong
      
      The naive story is:
      
      > I imported it with `import`, so it's an ES module now.
      
      Wrong. Writing `import x from 'pkg'` in your own source does not change what `pkg` itself is. Per Node.js's package.json documentation, a package's actual module system is determined by its own `"type"` field, its `"exports"` map conditions, and file extensions (`.mjs`/`.cjs` override `"type"`) — not by the syntax the *importer* happens to use. Bundlers perform CJS/ESM interop specifically so that `import`-syntax consumers can still consume CJS packages, and that interop is exactly what defeats static tree-shaking: the bundler cannot statically prove which of a CJS module's dynamically-assigned `module.exports` properties are unused, so it must keep the whole thing.
      
      ## Diagnosing interop as the cause
      
      Before concluding a bundler configuration is wrong, rule out CJS interop as the actual and unfixable-by-config cause:
      
      1. Check the dependency's `package.json` `"exports"` map for an `"import"` condition pointing at a real ESM build. If the only entry is `"require"` or a bare `"main"` pointing at a `.js` file with no `"type": "module"`, the package is CJS-only at that entry point — no bundler `sideEffects` or `treeshake` setting will make it statically analyzable.
      2. Check whether the package ships *both* CJS and ESM builds but exposes only a barrel-style default export from the ESM entry (e.g. `lodash-es`'s top-level `index.js` re-exporting every function) versus a per-function subpath (`lodash-es/debounce`). Importing the barrel entry can still pull in far more than needed even when the package is technically ESM, because the bundler must still resolve and analyze the whole re-export graph; importing the specific subpath sidesteps that entirely and is usually the more reliable fix regardless of `sideEffects` configuration.
      3. Check the app's *own* code for a barrel-file pattern before blaming the dependency at all: `import * as utils from './utils'` where `./utils/index.js` re-exports every internal module, or a component library's internal `index.ts` that re-exports every component. This is one of the most common tree-shaking blockers and has nothing to do with any external dependency's configuration — the fix is importing the specific submodule directly, or restructuring the barrel to preserve per-export side-effect metadata the bundler can act on.
      
      ## Example: the "why is lodash still in my bundle" case
      
      A report of "lodash is in the bundle even though we only use `lodash-es`'s `debounce`" is almost always one of:
      
      - The app actually imports from the CJS `lodash` package somewhere (a transitive dependency, or a stray import), not `lodash-es` — confirm via the analyzer's module-resolution path, not the package name alone.
      - The app imports the `lodash-es` barrel default (`import _ from 'lodash-es'` or `import { debounce } from 'lodash-es'`) rather than the subpath (`import debounce from 'lodash-es/debounce'`). Named imports from a barrel *can* still tree-shake correctly under a fully ESM, side-effect-clean barrel, but many barrels re-export in ways that retain more than expected — subpath import is the more reliable fix and should be verified by diff, not assumed to work either way.
      
      Recommend the narrowest import path the package actually exposes, then confirm via analyzer diff that only the intended submodule remains — do not close the finding on the recommendation alone.
      
      ## The verification workflow
      
      Every finding in this skill closes only with evidence, not a config change alone:
      
      1. **Confirm production mode.** Re-run (or ask for) the project's production build command. A development-mode analyzer run is not admissible evidence either for "it's broken" or "it's fixed."
      2. **Capture the before state.** Record the suspect module's presence and byte size (parsed and gzipped/brotli, matching how the project already measures) from the analyzer output.
      3. **Apply the narrowest fix.** Prefer, in order: correcting an app-side barrel import → correcting a `sideEffects` field with side-effecting files explicitly listed → switching to a bundler-level `moduleSideEffects`/`treeshake` override only when the first two do not apply → replacing a CJS-only dependency with an ESM-native alternative or its narrowest subpath.
      4. **Capture the after state.** Re-run the identical production build and analyzer command. Diff module list and byte counts against the before state.
      5. **Runtime smoke test, mandatory whenever `sideEffects: false` changed.** Load the affected page or component in the built (not dev-server) output and confirm styling, polyfilled behavior, and any global registration the module was responsible for still function. A passing build after `sideEffects: false` proves nothing about runtime correctness — that field's entire failure mode is silent at build time.
      
      ## Hard stops
      
      - Do not apply `sideEffects: false` without having read the target module for top-level side effects.
      - Do not claim tree-shaking success, or failure, from a development-mode build.
      - Do not treat "no build errors" as proof of elimination in either direction.
      - Do not close a finding without a before/after analyzer diff attached.
      
      ## Adversarial checklist
      
      Before closing any review under this skill, confirm:
      
      - Was the diff taken from a production build, not development mode?
      - Is the dependency's actual module format verified from its own `package.json`, not assumed from its name or popularity?
      - If `sideEffects: false` was applied or proposed, was the target module's source actually read for side effects first?
      - Is a barrel-file import pattern in the app's *own* code ruled out as the real blocker, separately from the dependency's configuration?
      - Was a runtime smoke test performed after any `sideEffects: false` change, not just a rebuild?
      
      If any answer is no, the review is not done.
      
    • module-format-and-sideeffects.md 5.2 KB
      # Module Format and the sideEffects Field
      
      Use this reference when determining whether a dependency is structurally tree-shakeable, and when reading or writing the `sideEffects` field for webpack-family bundlers.
      
      ## What people get wrong
      
      The naive story is:
      
      > The bundler is smart enough to figure out what's unused. If it doesn't, the bundler is broken.
      
      Wrong. Tree-shaking is a conditional optimization built on two independent prerequisites, and if either is missing the bundler is not broken — it is behaving exactly as documented:
      
      1. The module must be statically analyzable ES modules (`import`/`export`), not `require`/`module.exports`.
      2. The bundler must be told, explicitly or by default assumption, which modules have no side effects, so it is safe to drop unreferenced exports.
      
      Neither prerequisite is automatic. Both must be verified, not assumed.
      
      ## Module format: the first gate
      
      Static analysis of `import`/`export` bindings is what lets a bundler know, at build time, exactly which exports are used and which are not. `require()` calls are dynamic and CommonJS's `module.exports` object can be mutated at runtime in ways a static analyzer cannot fully prove safe — so a CJS module is generally not tree-shakeable, regardless of which bundler processes it.
      
      Check module format from the dependency's own `package.json`, not from assumption:
      
      - An `"exports"` field with an `"import"` condition, or a top-level `"module"` field pointing at an ESM build, signals the package ships (or can ship) ES modules.
      - A `"main"` field with no ESM entry point, or a package that only exposes `require()`-compatible output, is CJS-only. Static tree-shaking of that package's internals is not achievable through configuration; the fix is choosing a narrower import path the package exposes, or an ESM-native alternative.
      
      Do not assume format from the package's popularity, age, or name. Long-lived, widely used packages are exactly the ones most likely to still ship a CJS-only default entry for backward compatibility while also shipping an ESM build the app is failing to resolve.
      
      ## The sideEffects field: the second gate
      
      Per webpack's own guidance, tree-shaking of unused *exports* within an otherwise-imported ESM module is a separate mechanism (`usedExports`) from tree-shaking of unused *whole modules* (`sideEffects`). The `sideEffects` field governs the latter: it tells webpack whether a module can be dropped entirely if none of its exports are referenced.
      
      - `"sideEffects": false` at the package level tells the bundler: assume nothing in this package has an import-time side effect; safe to drop any file whose exports are unused.
      - Omitting the field, or `"sideEffects": true`, tells the bundler to assume every file might have a side effect and to keep every imported file regardless of whether its exports are used.
      - A module that genuinely does have import-time side effects — CSS imports, global polyfills, prototype patching, analytics auto-registration — must be listed explicitly if the rest of the package is marked side-effect-free, e.g.:
      
      ```json
      {
        "name": "awesome-ui",
        "sideEffects": ["**/*.css"]
      }
      ```
      
      Marking the whole package `sideEffects: false` while a CSS import exists uninstrumented inside it will silently drop the CSS from the production bundle. This does not produce a build error. It produces broken styling in production that only shows up after deploy.
      
      ## The production-mode requirement
      
      Per webpack's documented guidance, tree-shaking's user-visible effect (actual removal of dead code from output) requires `mode: 'production'`, or `optimization.usedExports` explicitly enabled in a non-production config. In development mode, webpack intentionally may keep `usedExports` analysis observable in the module graph without physically removing the code from the bundle, specifically so developers can inspect what *would* be removed. A development-mode bundle showing a module present is not evidence tree-shaking is broken — it may be evidence tree-shaking was never active for that build.
      
      `"sideEffects": false` additionally depends on `optimization.providedExports` to identify which exports are actually used before webpack can drop unused ones; this is enabled by default under `mode: 'production'`, but if a project runs a custom production-like config outside the `mode` shorthand, confirm the option is not disabled.
      
      ## What to check, in order
      
      1. Confirm the analyzer output being reviewed came from a production-mode build. If not, stop and get one — nothing below is meaningful otherwise.
      2. Read the suspect dependency's `package.json` `exports`/`module`/`main` fields to determine module format.
      3. Read `sideEffects` on both the app's own `package.json` and the dependency's `package.json`.
      4. If proposing `sideEffects: false` (or adding a package to an existing `sideEffects` array as an exception), read the module's source for top-level side-effecting statements before proposing it — do not propose it from the field being merely absent.
      
      ## Verification target
      
      A `package.json` diff and a bundle-analyzer module-list diff, both taken from a production build, one before and one after the change. "The build still succeeds" is not verification of either format or side-effect claims.
      
    • rollup-vite-treeshake-options.md 5.4 KB
      # Rollup and Vite Tree-Shaking Options
      
      Use this reference when the project bundles with Rollup directly, or with Vite (which bundles production builds through Rollup on Vite 5/6, and through Rolldown — a Rust reimplementation with a Rollup-compatible option surface — on Vite 7+). Confirm the installed major via Context7 before applying any snippet below; the option surface has moved.
      
      ## What people get wrong
      
      The naive story is:
      
      > Rollup's ESM-first design means everything just gets tree-shaken automatically; there's nothing to configure.
      
      Incomplete. Rollup's `treeshake` option is not a single on/off switch — it accepts `false` (fully disabled), a named preset, or a fine-grained options object, and the two settings most responsible for either under-elimination or unsafe over-elimination are `moduleSideEffects` and `propertyReadSideEffects`, both of which default to conservative (safe but less aggressive) behavior unless explicitly overridden.
      
      ## The treeshake option surface
      
      ```javascript
      export default {
        input: 'src/index.js',
        output: { format: 'es', dir: 'dist' },
        treeshake: {
          moduleSideEffects: false,        // assume modules have no side effects
          propertyReadSideEffects: false,  // property reads are side-effect-free
          tryCatchDeoptimization: false,   // don't pessimistically include try-catch bodies
          unknownGlobalSideEffects: false, // accessing unknown globals is safe
          preset: 'smallest',              // or 'safest' / 'recommended'
        },
      };
      ```
      
      `treeshake` also accepts the boolean `true` (default behavior) or one of the preset strings directly (`'smallest' | 'safest' | 'recommended'`) without a full options object.
      
      ### moduleSideEffects
      
      `moduleSideEffects` controls whether an entire imported module is retained even when none of its exports are used, mirroring in spirit what webpack's `sideEffects` package.json field does, but configured at the bundler level rather than (or in addition to) the package level. It accepts:
      
      - `boolean` — blanket true/false for all modules.
      - `'no-external'` — treat only external (`node_modules`) modules as side-effect-free; local project code keeps its analyzed side effects.
      - a string array — explicit module ID patterns to treat as side-effect-free.
      - a predicate function `(id, external) => boolean` for per-module logic.
      
      Setting this to `false` globally without auditing what actually lives in `node_modules` is the Rollup-side equivalent of the webpack `sideEffects: false` correctness trap: any dependency that performs import-time registration (polyfills, global CSS, `window` patches) will be silently dropped.
      
      ### propertyReadSideEffects
      
      This governs whether Rollup assumes a property read (including getter invocation) can safely be removed if its result is unused:
      
      ```javascript
      // Removed if treeshake.propertyReadSideEffects === false
      const foo = {
        get bar() {
          console.log('effect');
          return 'bar';
        },
      };
      const result = foo.bar;
      const illegalAccess = foo.quux.tooDeep; // would also throw at runtime if kept
      ```
      
      Setting this `false` can eliminate code whose only purpose was a getter side effect (logging, lazy initialization, a Proxy trap). This is a narrower and less commonly needed lever than `moduleSideEffects` — do not reach for it as a first attempt to eliminate a stubborn module; confirm the actual blocker is a property read, not module-level retention, before touching this option.
      
      ### External module elimination example
      
      ```javascript
      // input
      import { unused } from 'external-a';
      import 'external-b';
      console.log(42);
      ```
      
      With `treeshake.moduleSideEffects === true` (default-safe), both imports are retained even though `unused` is never referenced, because `moduleSideEffects: true` means Rollup assumes importing `external-a` and `external-b` might do something even without using their exports. With `moduleSideEffects: false`, both are dropped — which is correct only if neither package performs import-time work.
      
      ## Vite / Rolldown specifics
      
      Vite's production build has historically gone through Rollup, exposed via `build.rollupOptions`. Starting with Vite 7.3.1, the production bundler is Rolldown, and `build.rollupOptions` is now documented as a deprecated alias for `build.rolldownOptions` — if both are set, `rollupOptions` is ignored and Vite emits a warning. Verify the installed Vite major via Context7 before choosing which option key to write into a config; a `rollupOptions.output.manualChunks` object-form snippet, or a Rollup-specific `treeshake` shape, is not guaranteed to carry over unchanged into `rolldownOptions`.
      
      `import.meta.env.DEV` and `import.meta.hot` guards are statically replaceable constants in Vite's build pipeline — code inside an `if (import.meta.env.DEV)` block is tree-shaken out of production builds by design. If a user reports dev-only code appearing in a production bundle, check whether the guard condition is actually one of these statically-analyzable forms rather than a runtime environment-variable read that the bundler cannot constant-fold.
      
      ## Verification target
      
      A Rollup/Rolldown build with `--sourcemap` or the project's configured `rollup-plugin-visualizer`/equivalent output, diffed before and after any `treeshake` option change, on a production-mode build. Confirm via Context7 that the exact option key exists on the installed bundler version before writing it into config — `rollupOptions` vs `rolldownOptions` is the most likely place this silently drifts.
      
  • metadata.json 1.7 KB
    {
      "id": "tree-shaking-dead-code-review",
      "name": "Tree-Shaking & Dead-Code Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "codex",
        "claude-code",
        "cursor",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Verifies that a bundler's tree-shaking actually eliminated dead code by inspecting output bytes, module format, and sideEffects/treeshake configuration against a production-mode before/after diff, rather than trusting a clean build as proof of elimination, loaded progressively.",
      "source_type": "original",
      "official_docs": [
        "https://webpack.js.org/guides/tree-shaking/",
        "https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking",
        "https://rollupjs.org/configuration-options/#treeshake",
        "https://nodejs.org/api/packages.html#packagejson-and-file-extensions",
        "https://web.dev/articles/reduce-javascript-payloads-with-tree-shaking"
      ],
      "security_notes": "Do not recommend blanket sideEffects: false on a package the reviewer has not verified is actually side-effect-free (e.g., polyfills, CSS imports, analytics auto-init) -- this silently drops required initialization code, which is a correctness bug, not just a size issue, and can remove security-relevant setup such as CSP nonce injection or sanitizer initialization. Require a runtime smoke test, not just a rebuild, after any sideEffects: false change. Do not accept or echo any credential-shaped string found in a pasted build config, package.json, or analyzer report as if it were safe to keep in the transcript.",
      "last_verified": "2026-07-02",
      "path": "skills/frontend/tree-shaking-dead-code-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 8.4 KB
    ---
    name: tree-shaking-dead-code-review
    description: Verifies that a bundler's tree-shaking actually eliminated dead code by inspecting output bytes and sideEffects/module-format configuration, rather than trusting a clean build as proof of elimination.
    allowed-tools: Read Grep Glob Bash(npm run build:*)
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-02"
      category: operational
    ---
    
    # Tree-Shaking & Dead-Code Review
    
    ## Purpose
    
    "The build succeeded" and "the code was tree-shaken" are unrelated claims. A production build with zero errors can still ship a fully dead, unreferenced module byte-for-byte, because tree-shaking is not a guaranteed pass — it is a conditional static-analysis optimization that silently no-ops the moment it hits a CJS `require`, a missing or wrong `sideEffects` field, a barrel-file re-export, or a development-mode build. This skill treats "eliminated" as a claim that must be proven from bundle-analyzer output bytes and module lists, before and after, in a production build — never inferred from build success, and never inferred from a `sideEffects: false` change applied without reading the target module for real side effects first, since that specific mistake causes silent runtime breakage (dropped polyfills, dropped CSS injection, dropped security-relevant initialization), not just a size regression.
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - verify why a supposedly-unused import is still present in bundle-analyzer output,
    - evaluate whether a new dependency is tree-shakeable before it is added,
    - configure or review a `sideEffects` field in package.json for an app or a library the user is authoring,
    - explain why a `sideEffects: false` change either had no effect or broke something at runtime.
    
    ## When NOT to use
    
    - General bundle-size triage with no specific dead-code suspicion yet — use `bundle-budget-code-splitting-review` first to establish the numeric budget and rank contributors; hand off here once a specific module is suspected of being fully unused rather than merely heavy.
    - Deciding route- or component-level code-splitting boundaries — that is a splitting-boundary decision, not an elimination-verification decision; `bundle-budget-code-splitting-review` owns it.
    - Diagnosing which Core Web Vitals sub-phase is regressing with no analyzer report yet — hand off to `core-web-vitals-triage` first, return here once dead code is the named suspect.
    
    ## Context7 Documentation Protocol
    
    Tree-shaking configuration surface differs by bundler and has shifted across majors (Rolldown replacing Rollup inside Vite 7+, webpack's `sideEffects`/`usedExports` interaction, Rollup's `treeshake` preset system). Do not prescribe a fix from memorized training data.
    
    1. Call `ToolSearch` with query `"context7"` (or `"select:mcp__Context7__resolve-library-id,mcp__Context7__query-docs"`) to load the Context7 tools if not already loaded this session.
    2. Call `mcp__Context7__resolve-library-id` for the bundler actually installed in the project (webpack, Rollup, or Vite/Rolldown) before prescribing any `sideEffects` or `treeshake` configuration — do not assume the bundler from the framework name alone.
    3. Call `mcp__Context7__query-docs` for the specific mechanism in question — e.g. "sideEffects array vs boolean", "treeshake.moduleSideEffects options", "production mode requirement for usedExports" — before ruling on it. Do this per review; do not reuse a prior session's memory of bundler internals.
    4. Context7-grounded facts to confirm, not assume:
       - webpack: tree-shaking requires `mode: 'production'` (or `optimization.usedExports` explicitly enabled) to take visible effect; `sideEffects: false` in package.json additionally requires `optimization.providedExports` (on by default in production mode) to let webpack drop unused-export modules; a module with real side effects (e.g. CSS imports) must be listed explicitly, e.g. `"sideEffects": ["**/*.css"]`, or that side effect is silently dropped.
       - Rollup: `treeshake` accepts `false`, a preset (`'smallest' | 'safest' | 'recommended'`), or a fine-grained object — `moduleSideEffects` (boolean, `'no-external'`, a string array, or a predicate function) and `propertyReadSideEffects` are the two options most often responsible for either under- or over-aggressive elimination.
       - Vite 7+ ships Rolldown as its production bundler; `build.rollupOptions` is now an alias for `build.rolldownOptions` and is deprecated in favor of it — verify which option surface the installed Vite major actually documents before prescribing config keys.
    5. If Context7 is unavailable or returns no relevant match for the installed bundler, fall back to `official_docs` and mark the claim `documentation-based (Context7 unavailable)` rather than presenting it as freshly verified.
    6. Never invent a bundler config key, CLI flag, or `package.json` field that no queried source confirms.
    
    ## Lean operating rules
    
    - Require confirmation the build ran in production mode before evaluating any tree-shaking claim. A development-mode build routinely skips or partially applies elimination by design; "it's still there in dev" proves nothing.
    - Determine the suspect dependency's actual module format from its package.json (`exports`/`module` with an `import` condition vs. a `main`/`require`-only CJS entry) — do not infer format from the package's popularity or age. CJS defeats static tree-shaking analysis regardless of bundler, and is the most common silent cause of "unused code that won't go away."
    - Check `sideEffects` in both the app's own package.json and the suspect dependency's package.json. A missing field or `sideEffects: true` tells the bundler to assume every import has a side effect and keep it — this is often correct behavior being mistaken for a bug.
    - Before proposing or accepting `sideEffects: false` on any module, read that module for top-level side-effecting code — global polyfills, CSS-in-JS injection, prototype patching, analytics auto-init, CSP nonce injection, sanitizer initialization. A false `sideEffects: false` claim is a correctness bug that ships silently; it will not show up as a build error.
    - Rule out a barrel-file re-export pattern (`import * as utils from './utils'`, or a package `index.js` that re-exports everything) in the app's own code before blaming the dependency — this is a common tree-shaking blocker that has nothing to do with the dependency's configuration.
    - Never accept "no build errors" or "the build completed" as proof of elimination. Require a bundle-analyzer or output-file diff showing the specific module or byte range is actually absent, taken from a production build, before and after the change.
    - After any `sideEffects: false` change, require a runtime smoke test of the affected surface, not just a rebuild — dropped initialization code fails at runtime, not at build time.
    - Confirm the installed bundler and its major version via Context7 before prescribing exact config syntax — see Context7 Documentation Protocol. Do not assume a memorized API is still current.
    
    ## References
    
    Load these only when needed:
    
    - [Module format and sideEffects field](references/module-format-and-sideeffects.md) — use when determining whether a dependency is ESM or CJS, and when reading or writing the `sideEffects` field in package.json (webpack's flag semantics and the production-mode requirement).
    - [Rollup and Vite treeshake options](references/rollup-vite-treeshake-options.md) — use when the project is Rollup- or Vite/Rolldown-based and the review needs `treeshake.moduleSideEffects`, `propertyReadSideEffects`, or preset-level configuration.
    - [ESM/CJS interop and verification](references/esm-cjs-interop-and-verification.md) — use when the suspect module's format is ambiguous, when CJS interop is suspected as the blocker, and for the before/after diff and runtime-smoke-test verification workflow that closes out every review.
    
    ## Response minimum
    
    Return, at minimum:
    
    - the module format (ESM/CJS) and `sideEffects` field state for both the app and the suspect dependency, cited from the actual package.json content read,
    - confirmation the evidence came from a production-mode build, not development mode,
    - the exact package.json or bundler-config change proposed, with syntax version-confirmed via Context7 against the installed bundler major,
    - the before/after bundle-analyzer byte and module-count diff proving elimination, not just build success,
    - a correctness caveat and runtime-smoke-test requirement whenever `sideEffects: false` is newly applied.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related