PABCD Initiative Documentation Hub pabcd_initiative codexclaw cli-jaw

Barrel/Re-export Discipline

Source: dev-architecture/references/barrel-discipline.md

Barrel/Re-export Discipline

Last reviewed: 2026-07-02

When barrel files help vs. when they create hidden coupling and break tree-shaking.

---

When Barrels Are OK

ContextWhy Allowed
Public package API (packages/ui/src/index.ts)Consumers need a single stable entry point
Framework plugin entry (my-plugin/index.ts)Plugin contract requires single export
Monorepo package boundary (@org/shared/index.ts)Cross-package contract, versioned
Generated API client entrySingle entry for codegen output

Common trait: The barrel is a PUBLIC contract consumed by EXTERNAL code (other packages, other teams, npm consumers).

---

When Barrels Are BANNED

ContextWhy Banned
Feature module internal (features/auth/index.ts)Hides internal file structure, makes deps opaque
Utility folder (utils/index.ts)Creates coupling magnet — everything imports from one point
Component folder re-exporting siblingsDirect imports are clearer and tree-shake better
Deep re-export chains (barrel imports barrel)Impossible to trace actual dependency
Any barrel that also contains logicMixed concern — barrel should ONLY re-export

Common trait: The barrel serves INTERNAL code in the SAME package. Direct imports are always better here.

---

Tree-Shaking Impact

PatternTree-Shakeable?Why
export * from './heavy'NO (most bundlers)Bundler cannot determine which exports are used
export { A, B } from './module'YESNamed exports allow dead-code elimination
Barrel with side-effect codeNEVERSide effects force inclusion
export * from './a'; export * from './b'NONamespace collision risk + full inclusion
Direct import from './Button'ALWAYSOptimal — bundler sees exact usage

Rule of thumb: If your barrel uses export *, it destroys tree-shaking. Always use named exports.

---

Safe Barrel Template

// packages/ui/src/index.ts — PUBLIC package API barrel
// Rules:
// 1. Named exports ONLY (no export *)
// 2. No logic in this file (only re-exports)
// 3. No renaming (export as-is)
// 4. Alphabetical order for scanability

export { Button } from './components/Button';
export { Dialog } from './components/Dialog';
export { Input } from './components/Input';

export type { ButtonProps } from './components/Button';
export type { DialogProps } from './components/Dialog';
export type { InputProps } from './components/Input';

Anti-pattern (banned):

// BAD: features/auth/index.ts — INTERNAL barrel
export * from './login';       // wildcard = no tree-shaking
export * from './register';    // hides what's actually exported
export * from './hooks';       // consumers don't know what they get
export { default as AuthProvider } from './AuthProvider';  // renaming in barrel

---

Import Rules Summary

RuleCorrectIncorrect
Internal: import from source fileimport { fn } from './auth/login'import { fn } from './auth'
Cross-package: import from barrelimport { Button } from '@ui/components'import { Button } from '@ui/components/Button/Button'
No barrel logicBarrel file has ONLY export statementsBarrel imports, transforms, then re-exports
No re-export renameexport { Button } from './Button'export { Button as Btn } from './Button'
No circular barrelBarrel does not import from files that import from itindex.ts imports A.ts which imports from ./index

---

ESLint Enforcement

// .eslintrc.json — ban internal barrel imports
{
  "rules": {
    "no-restricted-imports": ["error", {
      "patterns": [
        {
          "group": ["./*/index", "../*/index"],
          "message": "Import from source file directly, not internal barrel."
        }
      ]
    }]
  }
}