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

@tnnquang/eslint

v3.0.1

Published

Opinionated ESLint plugin for modern TypeScript projects. Enforces explicit imports, function declarations, path aliases, and consistent type imports across React, Vue, Angular, NestJS, Next.js, and Nuxt.

Readme

@tnnquang/eslint

Opinionated ESLint plugin for modern TypeScript projects. Enforces explicit imports, function declarations, path aliases, and consistent type imports.

Supports ESLint v7–v9+ (both legacy eslintrc and flat config formats).

Philosophy

This plugin enforces patterns that improve code quality at scale:

  • Explicit over implicit — Named imports make dependencies visible; function declarations are hoisted and produce better stack traces.
  • Consistency over flexibility — Path aliases eliminate fragile relative paths; type imports clarify the value/type boundary.
  • Performance-aware — Named imports enable tree-shaking; avoiding barrel files prevents bundler slowdowns and circular deps.
  • Framework-aware — Rules adapt to your framework's conventions automatically.

Rules

| Rule | Fixable | Description | |------|---------|-------------| | no-arrow-components | ✅ | Enforce function declarations for top-level functions and components | | no-namespace-import | ✅ | Disallow import * as X and default-as-namespace usage | | enforce-path-alias | ✅ | Enforce path aliases instead of deep relative imports | | no-barrel-imports | — | Disallow importing from barrel/index files within the same module | | consistent-type-imports | ✅ | Enforce consistent import type usage in TypeScript |

Installation

npm install --save-dev @tnnquang/eslint
# or
yarn add -D @tnnquang/eslint
# or
pnpm add -D @tnnquang/eslint

Usage

Flat Config (ESLint v9+ — recommended)

// eslint.config.js
import tnnquang from "@tnnquang/eslint";

export default [
  tnnquang.configs["flat/recommended"],
  // or framework-specific:
  // tnnquang.configs["flat/react"],
  // tnnquang.configs["flat/vue"],
  // tnnquang.configs["flat/typescript"],
];

Available flat configs: flat/recommended, flat/strict, flat/react, flat/vue, flat/angular, flat/nestjs, flat/nextjs, flat/nuxt, flat/typescript.

Legacy Config (eslintrc)

{
  "extends": ["plugin:@tnnquang/eslint/recommended"]
}

Available legacy configs: recommended, strict, react, vue, angular, nestjs, nextjs, nuxt, typescript.

Manual Configuration

// eslint.config.js (flat)
import tnnquang from "@tnnquang/eslint";

export default [
  {
    plugins: { "@tnnquang/eslint": tnnquang },
    rules: {
      "@tnnquang/eslint/no-arrow-components": "warn",
      "@tnnquang/eslint/no-namespace-import": ["error", {
        allowedStarImports: ["@sentry/*"],
        allowedLibraries: ["lodash-es"],
      }],
      "@tnnquang/eslint/enforce-path-alias": ["warn", {
        minRelativeDepth: 2,
      }],
      "@tnnquang/eslint/no-barrel-imports": "warn",
      "@tnnquang/eslint/consistent-type-imports": ["warn", {
        prefer: "type-imports",
      }],
    },
  },
];

Rule Details

no-arrow-components

Enforces function declarations for top-level functions and React components. Provides autofix.

Options

{
  // Allow arrow functions for non-component top-level functions
  allowArrowFunctions?: boolean; // default: false

  // Allow arrow functions as arguments to HOCs (memo, forwardRef, styled, etc.)
  allowInCallExpressions?: boolean; // default: true

  // Custom list of HOC/wrapper names that can wrap arrow functions
  allowedWrappers?: string[]; // default: ["memo", "forwardRef", "React.memo", ...]

  // Allow arrow functions with explicit type annotation (TypeScript)
  allowTypedFunctionExpressions?: boolean; // default: true

  // Allow arrow functions in `export default () => {}`
  allowExportDefault?: boolean; // default: false

  // Exclude functions matching these patterns (regex)
  exclude?: string[]; // e.g., ["^use[A-Z]", ".*Handler$"]

  // Only check functions matching these patterns (regex)
  include?: string[];

  // Enable/disable for TypeScript files
  checkTypeScript?: boolean; // default: true
}

Examples

// ❌ Incorrect
const MyComponent = () => <div>Hello</div>;
const fetchData = async (url) => { return fetch(url); };
export default () => <App />;

// ✅ Correct
function MyComponent() { return <div>Hello</div>; }
async function fetchData(url) { return fetch(url); }
export default function() { return <App />; }

// ✅ Allowed (HOC wrappers)
const MemoComponent = memo(() => <div/>);
const StyledButton = styled.button`color: red`;
const Connected = connect(mapState)(MyComponent);

// ✅ Allowed (typed function expression in TypeScript)
const handler: RequestHandler = (req, res) => { res.send("ok"); };

no-namespace-import

Disallows both import * as X and default imports used as namespaces (import X; X.method()). Provides autofix.

Options

{
  // Libraries fully excluded from all checks (supports glob patterns)
  allowedLibraries?: string[]; // e.g., ["@sentry/*", "lodash-es"]

  // Only check these libraries (empty = check all)
  targetLibraries?: string[]; // e.g., ["react", "lodash", "redux"]

  // Allow `import * as X` globally
  allowStarImports?: boolean; // default: false

  // Allow default-as-namespace globally
  allowDefaultAsNamespace?: boolean; // default: false

  // Libraries allowed to use `import * as` specifically
  allowedStarImports?: string[]; // e.g., ["@sentry/*", "protobufjs"]

  // Libraries allowed to use default as namespace
  allowedDefaultAsNamespace?: string[]; // e.g., ["moment", "dayjs"]

  // Check TypeScript type imports
  checkTypeScriptTypes?: boolean; // default: true

  // Allow namespace for type-only imports
  allowTypeNamespaces?: boolean; // default: false

  // Ignore all external (non-relative) imports
  ignoreExternalModules?: boolean; // default: false
}

Examples

// ❌ Incorrect
import * as Sentry from "@sentry/browser"; // unless in allowedStarImports
import React from "react";
React.useState(0); // default used as namespace

// ✅ Correct
import { init, captureException } from "@sentry/browser";
import { useState } from "react";

// ✅ With config: allowedStarImports: ["@sentry/*"]
import * as Sentry from "@sentry/browser"; // allowed by config

Glob Pattern Support

All library lists support glob-style wildcards:

  • "@sentry/*" matches @sentry/browser, @sentry/node, etc.
  • "lodash*" matches lodash, lodash-es, lodash/fp
  • "@company/*" matches all packages in the scope

enforce-path-alias

Enforces path aliases for imports instead of deep relative paths. Auto-detects from tsconfig.json, vite.config.*. Provides autofix.

Options

{
  // 'all': any relative import within baseUrl
  // 'direct-children': only imports to other top-level folders
  mode?: "all" | "direct-children"; // default: "direct-children"

  // Minimum ../ depth to trigger (1 = any parent traversal)
  minRelativeDepth?: number; // default: 1

  // Config file to read aliases from
  configFile?: string; // default: "tsconfig.json"

  // Manual path aliases (overrides auto-detection)
  paths?: Record<string, string[]>;

  // Base URL for resolving
  baseUrl?: string; // auto-detected if omitted

  // Folder patterns to exclude
  exclude?: string[]; // e.g., ["test", "spec", "__mocks__"]

  // File patterns to skip entirely
  excludeFiles?: string[]; // e.g., ["*.config.*", "*.spec.*"]

  // Allow ./ imports (sibling imports)
  allowRelativeInSameFolder?: boolean; // default: true

  // Supported file extensions
  supportedExtensions?: string[];

  // Include .d.ts files
  includeDeclarationFiles?: boolean; // default: false

  // Check dynamic import() expressions
  includeDynamicImports?: boolean; // default: true
}

Examples

// tsconfig.json: { "paths": { "@/*": ["./src/*"] } }

// ❌ Incorrect (in src/components/Button.tsx)
import { validate } from "../../utils/validation";
import { API_URL } from "../../config/constants";
const module = await import("../../services/api");

// ✅ Correct
import { validate } from "@/utils/validation";
import { API_URL } from "@/config/constants";
const module = await import("@/services/api");

// ✅ Allowed (same folder, allowRelativeInSameFolder: true)
import { ButtonProps } from "./Button.types";

no-barrel-imports

Disallows importing from barrel/index files within the same module to prevent circular dependencies. Not auto-fixable (ambiguous target).

Options

{
  // Scope of detection
  scope?: "same-module" | "same-parent" | "all-relative"; // default: "same-parent"

  // File names considered barrels
  barrelFiles?: string[]; // default: ["index"]

  // Import patterns allowed even if they hit a barrel
  allowedPatterns?: string[]; // e.g., ["@/store", "@/api"]

  // Folders to exclude
  excludeFolders?: string[]; // default: ["node_modules"]

  // Also check aliased imports
  checkAliasImports?: boolean; // default: false
}

Examples

// src/components/Button/Button.tsx

// ❌ Incorrect (resolves to Icon/index.ts barrel)
import { Icon } from "../Icon";

// ✅ Correct (direct import)
import { Icon } from "../Icon/Icon";

consistent-type-imports

Enforces consistent import type usage in TypeScript. Only active for .ts/.tsx files. Provides autofix.

Options

{
  // Which style to enforce
  prefer?: "type-imports" | "inline-type-imports" | "no-type-imports"; // default: "type-imports"

  // Disallow type annotations in value imports
  disallowTypeAnnotations?: boolean; // default: true

  // Fix style
  fixStyle?: "separate-type-imports" | "inline-type-imports";

  // Libraries to exclude (e.g., with side effects)
  allowedLibraries?: string[]; // supports glob
}

Examples

// prefer: "type-imports" (default)

// ❌ Incorrect
import { type User, type Post } from "./types";

// ✅ Correct
import type { User, Post } from "./types";

// prefer: "inline-type-imports"

// ❌ Incorrect
import type { User } from "./types";

// ✅ Correct
import { type User } from "./types";

Configuration Recipes

React + TypeScript (strict)

// eslint.config.js
import tnnquang from "@tnnquang/eslint";

export default [
  tnnquang.configs["flat/strict"],
  {
    rules: {
      "@tnnquang/eslint/no-namespace-import": ["error", {
        allowedStarImports: ["@testing-library/*"],
        allowedDefaultAsNamespace: ["classnames"],
      }],
      "@tnnquang/eslint/no-arrow-components": ["error", {
        exclude: ["^use[A-Z]"], // allow hooks as arrow functions
      }],
    },
  },
];

Vue/Nuxt

import tnnquang from "@tnnquang/eslint";

export default [
  tnnquang.configs["flat/vue"],
  {
    rules: {
      "@tnnquang/eslint/enforce-path-alias": ["warn", {
        configFile: "tsconfig.json",
        exclude: ["composables", "stores"],
      }],
    },
  },
];

NestJS Backend

import tnnquang from "@tnnquang/eslint";

export default [
  tnnquang.configs["flat/nestjs"],
  {
    rules: {
      "@tnnquang/eslint/no-namespace-import": ["error", {
        allowedStarImports: ["@nestjs/common", "@nestjs/core"],
      }],
      "@tnnquang/eslint/consistent-type-imports": ["warn", {
        prefer: "inline-type-imports",
      }],
    },
  },
];

Monorepo with custom aliases

import tnnquang from "@tnnquang/eslint";

export default [
  tnnquang.configs["flat/typescript"],
  {
    rules: {
      "@tnnquang/eslint/enforce-path-alias": ["error", {
        mode: "all",
        minRelativeDepth: 2,
        paths: {
          "@app/*": ["./src/*"],
          "@shared/*": ["./packages/shared/src/*"],
          "@ui/*": ["./packages/ui/src/*"],
        },
        exclude: ["__tests__", "__mocks__"],
      }],
    },
  },
];

Migration from v2

Breaking Changes in v3

  1. New rules added: no-barrel-imports and consistent-type-imports
  2. no-namespace-import now catches import * as — add exceptions with allowedStarImports
  3. no-arrow-components now has autofix — run eslint --fix to auto-convert
  4. Node.js 18+ required
  5. Flat config support — use flat/* config names for ESLint v9+

Migration Steps

// If you used v2 and have libraries that use import * as:
"@tnnquang/eslint/no-namespace-import": ["error", {
+  allowedStarImports: ["@sentry/*", "protobufjs"],
+  allowedDefaultAsNamespace: ["moment", "dayjs"],
}]

License

MIT

Author

Tran Ngoc Nhat Quang

Contributing

Issues and pull requests are welcome at github.com/tnnquang/eslint-plugin.