ESLint plugin for organizing imports into readable groups, useful for FSD and other layered frontend architectures.
๐ English | ๐ฐ๐ท ํ๊ตญ์ด | ๐ฏ๐ต ๆฅๆฌ่ช | ๐จ๐ณ ็ฎไฝไธญๆ
Layered frontend architectures help teams manage dependencies, but import sections can quickly become noisy and inconsistent.
This plugin aims to make imports easier to scan by grouping them by source type and enforcing readable spacing between groups.
import path from "node:path";
import React from "react";
import { clsx } from "clsx";
import { userApi } from "@/entities/user";
import { Button } from "@/shared/ui";
import styles from "./style.module.css";
import { helper } from "../lib/helper";pnpm add -D eslint-plugin-layered-importsnpm install -D eslint-plugin-layered-importsUse the FSD-friendly preset to enable the plugin with sensible defaults for layered frontend projects.
The preset treats @/ imports as internal imports, orders top-level groups as builtin, external, internal, relative, and orders internal imports by the common FSD layer order: app, pages, widgets, features, entities, shared.
import layeredImports from "eslint-plugin-layered-imports";
export default [layeredImports.configs.fsd];The preset is equivalent to enabling the rule with these defaults:
{
internalAliases: ["@/"],
groups: ["builtin", "external", "internal", "relative"],
internalLayerOrder: [
"app",
"pages",
"widgets",
"features",
"entities",
"shared",
],
}You can override preset defaults by adding another config object after it.
import layeredImports from "eslint-plugin-layered-imports";
export default [
layeredImports.configs.fsd,
{
rules: {
"layered-imports/import-spacing": [
"error",
{
internalAliases: ["@/", "~/"],
},
],
},
},
];Use manual setup when you do not want the FSD preset or want to configure every option yourself.
import layeredImports from "eslint-plugin-layered-imports";
export default [
{
plugins: {
"layered-imports": layeredImports,
},
rules: {
"layered-imports/import-spacing": "error",
},
},
];With options:
import layeredImports from "eslint-plugin-layered-imports";
export default [
{
plugins: {
"layered-imports": layeredImports,
},
rules: {
"layered-imports/import-spacing": [
"error",
{
internalAliases: ["@/", "~/"],
groups: ["builtin", "external", "internal", "relative"],
internalLayerOrder: ["features", "entities", "shared"],
},
],
},
},
];Requires a blank line between different import groups.
The initial groups are:
builtin: Node.js built-in modules such asnode:pathorfsexternal: npm package imports such asreactorclsxinternal: project-local alias imports such as@/shared/uirelative: relative imports such as./helperor../lib/helper
Invalid:
import helper from "./helper";
import React from "react";Valid:
import React from "react";
import helper from "./helper";With internalLayerOrder, internal imports can also be ordered by layer.
Invalid:
import { Button } from "@/shared/ui/button";
import { UserCard } from "@/entities/user";
import { LoginForm } from "@/features/auth";Valid:
import { LoginForm } from "@/features/auth";
import { UserCard } from "@/entities/user";
import { Button } from "@/shared/ui/button";| Option | Type | Default | Description |
|---|---|---|---|
internalAliases |
string[] |
["@/"] |
Source prefixes treated as internal imports. |
groups |
Array<"builtin" | "external" | "internal" | "relative"> |
["builtin", "external", "internal", "relative"] |
Top-level import group order. |
internalLayerOrder |
string[] |
undefined |
Optional order inside the internal group. |
Type: string[]
Default: ["@/"]
Configures which import source prefixes should be treated as the internal group.
export default [
{
plugins: {
"layered-imports": layeredImports,
},
rules: {
"layered-imports/import-spacing": [
"error",
{
internalAliases: ["@/", "~/", "@app/"],
},
],
},
},
];Configured aliases are matched as prefixes. Use specific prefixes such as @/ or @app/ instead of a broad @ prefix so scoped packages such as @tanstack/react-query remain external.
Type: Array<"builtin" | "external" | "internal" | "relative">
Default: ["builtin", "external", "internal", "relative"]
Configures the expected order of import groups. The array must include each group exactly once.
export default [
{
plugins: {
"layered-imports": layeredImports,
},
rules: {
"layered-imports/import-spacing": [
"error",
{
groups: ["builtin", "external", "internal", "relative"],
},
],
},
},
];Imports are reported when a later group appears before an earlier configured group.
Type: string[]
Default: undefined
Configures an optional order inside the internal group. The rule removes the matching internalAliases prefix, reads the first path segment as the internal layer, and orders known layers by this array.
export default [
{
plugins: {
"layered-imports": layeredImports,
},
rules: {
"layered-imports/import-spacing": [
"error",
{
internalAliases: ["@/"],
internalLayerOrder: [
"app",
"pages",
"widgets",
"features",
"entities",
"shared",
],
},
],
},
},
];For example, @/shared/ui/button is treated as the shared layer. Known layers are ordered before unknown internal paths. Unknown internal paths keep their existing relative order. Imports in the same known layer are sorted by source path.
The rule can autofix safe import group order violations. When imports are in the same contiguous import block, --fix reorders them by the configured groups order and normalizes blank lines between groups.
npx eslint . --fiximport helper from "./helper";
import React from "react";is fixed to:
import React from "react";
import helper from "./helper";Autofix is intentionally conservative:
- It does not move imports across non-import statements.
- It does not reorder an import block that contains side-effect imports such as
import "./setup";. - Leading comments attached to an import move together with that import.
- Imports inside the same group keep their existing relative order unless
internalLayerOrderis configured for internal imports.
- The rule only manages static
importdeclarations. - It does not sort named specifiers inside a single import declaration.
internalLayerOrderreads the first path segment after a matching internal alias. For example,@/shared/ui/buttonis treated as thesharedlayer.- Unknown internal layers are placed after known layers and keep their existing relative order.
