# eslint-plugin-no-barrel-files

ESLint plugin for reducing barrel-file usage in two ways:

- `no-barrel-files`: disallow authoring barrel files
- `prefer-source-imports`: prefer importing from source modules instead of through a barrel

The plugin is useful both for strict greenfield setups and for incremental migrations away from existing barrels.

## Why?

Barrel files can:

- slow down builds and tests
- make circular dependencies easier to introduce
- make tree shaking less effective
- blur module boundaries and make import paths less explicit

References:

- https://github.com/jestjs/jest/issues/11234
- https://github.com/vercel/next.js/issues/12557
- https://dev.to/tassiofront/barrel-files-and-why-you-should-stop-using-them-now-bc4
- https://flaming.codes/posts/barrel-files-in-javascript

## Install

```sh
npm install --save-dev eslint-plugin-no-barrel-files
```

`prefer-source-imports` needs `typescript` in the consuming project whenever tsconfig resolution is enabled (the default), including when linting JavaScript files. Set `tsconfig: false` to use only relative imports and the rule's manual `paths` option instead.

```sh
npm install --save-dev typescript
```

## Quick Start

This plugin supports:

- ESLint 9+ and 10+ via flat config
- ESLint 8 via legacy config

### Flat Config

```js
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [...noBarrelFiles.configs['flat/recommended']];
```

### Legacy Config

```js
module.exports = {
  extends: ['plugin:no-barrel-files/recommended'],
};
```

## Included Configs

The plugin exports two recommended configs:

- `configs.recommended` for legacy config
- `configs["flat/recommended"]` for flat config

Both enable `no-barrel-files` and `prefer-source-imports`.

## Rules

### `no-barrel-files`

Disallows common barrel-file patterns such as re-exporting imported bindings or using `export *`.

```js
// fail
export * from "./foo";

import Foo from "./foo";
export default Foo;

import Foo from "./foo";
export { Foo };

export { Moo } from "./Moo";
export { default as Moo } from "./Moo";

// pass
const Foo = "baz";
function Bar() {}
class Baz {}

export default Foo;
export { Bar, Baz };

import { Moo } from "./Moo";
export const Baz = Moo;
```

Use this rule when you want to stop new barrel files from being created.

#### Options

##### `allow`

Allows intentional barrel files whose path matches one of the supplied glob patterns. Patterns match paths relative to the current working directory; absolute patterns are also supported.

```js
{
  'no-barrel-files/no-barrel-files': ['error', {
    allow: ['src/index.ts', 'packages/*/src/index.ts'],
  }],
}
```

### `prefer-source-imports`

Reports imports that go through a barrel when the rule can resolve the original source module.

```ts
// fail
import { Foo } from './barrel';
import type { TypeFoo } from './barrel';

// if barrel.ts contains:
// export { Foo } from "./foo";
// export type { TypeFoo } from "./types";

// pass
import { Foo } from './foo';
import type { TypeFoo } from './types';
```

This rule is useful when:

- a codebase still has barrel files
- you want to migrate consumers away from barrels gradually
- you want autofixable guidance where possible

#### What it can resolve

`prefer-source-imports` currently supports:

- relative imports
- TypeScript `baseUrl` and `paths` from the nearest `tsconfig.json`
- explicit alias mappings through the `paths` rule option
- explicit re-exports such as `export { Foo } from "./foo"`
- aliased re-exports such as `export { Bar as Foo } from "./bar"`
- local re-exports such as `import { Foo } from "./foo"; export { Foo }`
- default re-exports such as `export { default as Foo } from "./foo"`
- `export * from "./foo"` when the exported name can be resolved back to the source file
- type-only re-exports such as `export type { Foo } ...` and `export { type Foo } ...`

Current scope:

- the rule focuses on named imports
- default imports are rewritten when the barrel directly re-exports its default export
- namespace imports are not the target of this rule

#### Safe autofix behavior

The rule only autofixes when the full import declaration can be rewritten safely.

If only part of an import can be resolved safely, the rule still reports the problem but may skip autofix.

## `prefer-source-imports` Options

### `fixStyle`

Controls how the replacement import path is generated.

- `"relative"`
  Always rewrites to a relative import path.
- `"preserve-alias"`
  Preserves an alias only when the reverse alias mapping is unique.
- `"auto"`
  Preserves aliases for alias-based imports when the reverse alias mapping is unique; otherwise falls back to a relative path.

### `ignore`

Skips imports whose source specifier matches one of the supplied glob patterns. Use this for intentional public barrel APIs while continuing to fix internal barrel imports.

```js
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [
  {
    plugins: {
      'no-barrel-files': noBarrelFiles,
    },
    rules: {
      'no-barrel-files/prefer-source-imports': ['error', {
        ignore: ['@acme/ui', '@acme/*/public'],
      }],
    },
  },
];
```

### `tsconfig`

Controls tsconfig-based path resolution.

- omitted or `true`
  Use the nearest `tsconfig.json`.
- `false`
  Disable tsconfig-based resolution entirely.
- `"./path/to/tsconfig.json"`
  Resolve using a specific config file.

### `paths`

Manual alias mappings that supplement or replace tsconfig path resolution.

Values can be:

- a string
- an array of strings

Example:

```js
{
  paths: {
    "@app/*": "src/*",
    "@shared/*": ["packages/shared/src/*", "src/shared/*"],
  },
}
```

## Configuration Examples

### Enable Only `no-barrel-files`

This blocks new barrel files, but does not yet enforce direct source imports.

```js
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [
  {
    plugins: {
      'no-barrel-files': noBarrelFiles,
    },
    rules: {
      'no-barrel-files/no-barrel-files': 'error',
    },
  },
];
```

### Enable Both Rules

This is a good migration setup.

```js
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [...noBarrelFiles.configs['flat/recommended']];
```

### Use Manual Alias Resolution Only

```js
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [
  {
    plugins: {
      'no-barrel-files': noBarrelFiles,
    },
    rules: {
      'no-barrel-files/prefer-source-imports': [
        'error',
        {
          tsconfig: false,
          fixStyle: 'preserve-alias',
          paths: {
            '@app/*': 'src/*',
          },
        },
      ],
    },
  },
];
```

### Use A Specific `tsconfig.json`

```js
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [
  {
    plugins: {
      'no-barrel-files': noBarrelFiles,
    },
    rules: {
      'no-barrel-files/prefer-source-imports': [
        'error',
        {
          tsconfig: './tsconfig.eslint.json',
          fixStyle: 'auto',
        },
      ],
    },
  },
];
```

## Adoption Strategy

Typical rollout options:

- Start with `no-barrel-files` only to block new barrels.
- Add `prefer-source-imports` later to migrate consumers away from old barrels.
- Run `prefer-source-imports` first if the codebase already has many barrels and you want to shrink their usage before deleting them.

## Notes

- `prefer-source-imports` depends on being able to resolve the barrel and the underlying source module.
- `prefer-source-imports` uses the consuming project's `typescript` installation for tsconfig parsing and module resolution.
- The current TypeScript peer-dependency range is `^5.6.3 || ^6.0.0`. TypeScript 7 is not supported yet.
- If `prefer-source-imports` runs on a TypeScript file without `typescript` installed and tsconfig resolution is enabled, the rule reports a configuration error instead of crashing the plugin.
- The same configuration error is reported for JavaScript files when the rule needs tsconfig-based resolution but `typescript` is not installed.
- Alias preservation only happens when reverse alias lookup is unique.
- If multiple aliases point to the same file, the fixer may fall back to a relative path or skip autofix depending on `fixStyle`.

## Contributing

If you find a bug or want an additional feature, open an issue or send a pull request.
