npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-logical-imports

v0.2.2

Published

ESLint plugin that enforces one specifier per import statement and alphabetical sort by local symbol name, with configurable block grouping.

Readme

eslint-plugin-logical-imports

You're sorting imports wrong!

No I'm not

I think you are! Let me explain why. If you disagree with any point, that's okay and you can skip the rest of the readme. This tool is not meant for you. 👍

  1. What is the point of sorting anything?

    Firstly, to make things easier to find, especially when using human eyeballs.

    Secondly, to make things easier to insert, by providing a single objectively correct insertion point (which is also easy to find).

  2. What are you most often trying to find when eyeballing a block of imports?

    A local name, which could be an alias. Something that is referenced elsewhere in the current file.

  3. If you're looking for a local name, what order should the imports be sorted by?

    The local names should be sorted alphabetically. Import blocks should work like a dictionary, or the index of a book.

  4. What happens if you sort by module path instead?

    It breaks the sort order of local names. They might as well be sorted randomly.

  5. What happens if you sort by export name instead?

    It breaks the sort order of aliased local names. They might as well be sorted randomly.

  6. What happens if you group imports from the same module into a single declaration?

    It breaks the sort order of local names relative to other modules. They might as well be sorted randomly.

  7. What is the only consistent and logical method for sorting imports?

    By breaking down declarations to a single import each, then sorting them alphabetically by the local name.

What is this?

An ESLint plugin that enforces an unconventional, highly opinionated but also very logical import style:

  1. One import per declaration. If you need multiple imports from a module, there must be equally many declarations.

  2. Declarations are sorted alphabetically by the local name. The thing that is actually referenced in the rest of the codebase is the thing that should drive the ordering of imports.

Applied in combination, these principles ensure imports are ordered like a dictionary, or the index of a book.

Options

Option|Type|Default|Description ------|----|-------|----------- allowMultipleSpecifiers|boolean|false|Set to true to all multi-imports like { a, b } blocks|string[]|['builtin', 'external', 'internal']|Change the handling of import blocks, or use [] for a single block.

Block glossary

Value|Matches -----|------- builtin|Node/Deno/Bun builtins, e.g. node:fs, module, bun:test builtin:types|Type-only imports of builtins external|External dependencies (npm packages) external:types|Type-only imports of external dependencies internal|Local dependencies, e.g. ./foo/bar, ../baz, /qux internal:types|Type-only imports of internal dependencies

If a :types variant is omitted from blocks, type imports of that category fall back to the runtime-block peer, so the default config merges types into their runtime blocks.

Behaviour

  • Sort key is the local name:

    Import|Sort key ------|-------- import Foo from 'x'|Foo import * as ns from 'x'|ns import { foo } from 'x'|foo import { foo as bar } from 'x'|bar import type { Foo } from 'x'|Foo import { type Foo } from 'x'|Foo

    Comparison is then case-insensitive localeCompare, with ASCII tie-break for stability.

  • Side-effect imports (import 'x') act as fences. They're never reordered and imports either side are sorted independently.

  • Comments stay attached to their import. Leading line and block comments move with the import below, trailing same-line comments stay on the right.

  • Floating comments (a comment with blank lines on either side) attach to the next import, per ESLint's getCommentsBefore semantics.

  • Line endings (LF or CRLF) are detected per file.

How do I install it?

npm install --save-dev eslint-plugin-logical-imports

Requires Node >= 24 and ESLint >= 10.

How do I configure it?

Add the recommended config to your eslint.config.js:

// ...
import logicalImports from 'eslint-plugin-logical-imports';

export default [
  // ...
  logicalImports.configs.recommended,
];

Or configure it manually:

// ...
import logicalImports from 'eslint-plugin-logical-imports';

export default [
  // ...
  {
    plugins: { 'logical-imports': logicalImports },
    rules: {
      'logical-imports/order': ['error', {
        allowMultipleSpecifiers: false,
        blocks: ['builtin', 'external', 'internal'],
      }],
    },
  },
];

How do I use it?

Just run eslint as you normally would. The plugin implements a single, atomic fix so eslint --fix converges in one pass (i.e. is guaranteed to work).

What about performance?

There's no impact from splitting imports across multiple declarations. The spec mandates that all imports of a module must resolve to the same cached hit. This works in all runtimes and bundlers.

Can I control how it handles import blocks?

Yes, you can re-order the blocks if you wish:

// ...
import logicalImports from 'eslint-plugin-logical-imports';

export default [
  // ...
  {
    plugins: { 'logical-imports': logicalImports },
    rules: {
      'logical-imports/order': ['error', {
        blocks: ['internal', 'external', 'builtin'],
      }],
    },
  },
];

Or separate types from runtime imports:

// ...
import logicalImports from 'eslint-plugin-logical-imports';

export default [
  // ...
  {
    plugins: { 'logical-imports': logicalImports },
    rules: {
      'logical-imports/order': ['error', {
        blocks: ['builtin', 'external', 'internal', 'builtin:types', 'external:types', 'internal:types'],
      }],
    },
  },
];

Or group all imports as a single block:

// ...
import logicalImports from 'eslint-plugin-logical-imports';

export default [
  // ...
  {
    plugins: { 'logical-imports': logicalImports },
    rules: {
      'logical-imports/order': ['error', {
        blocks: [],
      }],
    },
  },
];

Single imports is crazy, can I opt out of that part?

Deep sigh, sad face.

Yes, you can. Set allowMultipleSpecifiers: true in config:

// ...
import logicalImports from 'eslint-plugin-logical-imports';

export default [
  // ...
  {
    plugins: { 'logical-imports': logicalImports },
    rules: {
      'logical-imports/order': ['error', {
        allowMultipleSpecifiers: true,
      }],
    },
  },
];

Import declarations will be ordered by their first specifier and specifiers within a declaration will be ordered by their local name. If there is a comment in the specifier list, the specifiers will not be sorted.

What alternatives exist?

Examples

Before:

import { z } from 'zod';
import { format } from 'date-fns';
import { readFile } from 'node:fs/promises';
import { useState, useEffect } from 'react';
import { Component as Widget } from './app/widget';
import express from 'express';
import { format as utilFormat } from 'node:util';
import * as utils from './utils';

After, with recommended config:

import { readFile } from 'node:fs/promises';
import { format as utilFormat } from 'node:util';

import express from 'express';
import { format } from 'date-fns';
import { useEffect } from 'react';
import { useState } from 'react';
import { z } from 'zod';

import * as utils from './utils';
import { Component as Widget } from './app/widget';

Change log

CHANGELOG.md

License

MIT