eslint-plugin-layered-imports
v0.1.0
Published
Organize imports into readable groups for FSD and layered frontend projects.
Maintainers
Readme
Why?
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.
Example
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";Installation
pnpm add -D eslint-plugin-layered-importsnpm install -D eslint-plugin-layered-importsUsage
Quick start with the FSD preset
Use 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: ["@/", "~/"],
},
],
},
},
];Manual flat config setup
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"],
}],
},
},
];Rules
import-spacing
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
Valid and invalid examples
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";Options
| 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. |
internalAliases
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.
groups
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.
internalLayerOrder
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.
Autofix behavior
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.
Limitations
- 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.
Roadmap
- [x] Add
import-spacingrule - [x] Support configurable import group order
- [x] Add auto-fix support
- [x] Add FSD-friendly preset
- [x] Support optional internal layer ordering
