npm.io
2.3.0 • Published 3d ago

eslint-plugin-no-barrel-files

Licence
ISC
Version
2.3.0
Deps
3
Size
201 kB
Vulns
0
Weekly
0
Stars
54

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:

Install

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.

npm install --save-dev typescript

Quick Start

This plugin supports:

  • ESLint 9+ and 10+ via flat config
  • ESLint 8 via legacy config
Flat Config
import noBarrelFiles from 'eslint-plugin-no-barrel-files';

export default [...noBarrelFiles.configs['flat/recommended']];
Legacy Config
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 *.

// 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.

{
  '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.

// 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 } ...
  • types imported without the type modifier, including through export *
  • default re-exports of an imported binding such as import Foo from "./foo"; export default Foo
  • namespace re-exports such as export * as ns from "./foo" and import * as ns from "./foo"; export { ns }

Current scope:

  • the rule focuses on named imports
  • default imports are rewritten when the barrel directly re-exports its default export
  • namespace imports (import * as ns from './barrel') are not the target of this rule
  • namespace re-exports are only autofixed when the import declaration contains that single specifier
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.

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:

{
  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.

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.

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

export default [...noBarrelFiles.configs['flat/recommended']];
Use Manual Alias Resolution Only
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
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.

Keywords