eslint-plugin-entered-modules
v0.1.1
Published
ESLint plugin to enforce modular architecture with entry point files
Maintainers
Readme
eslint-plugin-entered-modules
An ESLint plugin that enforces a modular architecture with explicit entry-point files.
Entered Modules
Entered Modules is a concept used in some of our internal projects to describe specific conventions for how the codebase should be divided into multiple modules and what kinds of import/export statements are permitted.
For a comprehensive explanation, see the original file, copied from one of the internal projects.
[!TIP] The word “entered” here has a made-up meaning: “having entries” or “having entry points.”
Here’s a real example snippet from an internal file, src/reader/Reader.tsx (the @/reader module). Note that there are no index files used—the last path segments are never directories.
import { Box, Grid } from '@/ui/entry.core'
import { HEIGHT_IN_PIXELS as BOTTOM_BAR_HEIGHT_IN_PIXELS, ReaderBottomBar } from './ReaderBottomBar'
import { ReaderCssVariables } from './ReaderCssVariables'
import { ReaderTopBar, HEIGHT_IN_PIXELS as TOP_BAR_HEIGHT_IN_PIXELS } from './ReaderTopBar'
import { Canvas as ReaderCanvas } from './canvas/entry'
import { Preferences as ReaderPreferences, useViewStore } from './preferences/entry'
import { useWakeLock } from './wake-lock.hook'[!TIP] Internally, this is used together with
@trivago/prettier-plugin-sort-importsfor even better development experience!
Other benefits include:
- Simple reasoning about all of a module's entry points (gathered more or less in one place and sorted alphabetically).
- Leveraging the power of plaintext with Unix-like tooling.
For example, with tree, we can produce output similar to the following:
$ cd src
$ tree -P '*<'"$SOME_PATTERN"'>*'
.
├── profile
│ ├── avatar
│ │ ├── (...)
│ │ ├── entry.ts
│ │ └── (...)
│ ├── badge.hooks.ts
│ ├── (...)
│ ├── entry..children.ts # ⤵️ Internal downstream-only API
│ ├── entry..so.children.ts # ⤵️ Internal downstream-only API
│ ├── entry.cs.ts # ✅ Public API
│ ├── entry.so.ts # ✅ Public API
│ ├── entry.ts # ✅ Public API
│ ├── (...)
│ └── PasswordForm.tsx
├── router
│ ├── entry.so.ts
│ └── entry.ts
├── trpc
│ ├── entry.cs.ts
│ └── entry.so.ts
└── ui
├── entry.controls.ts
├── entry.flags.tsx
├── entry.layout.ts
├── entry.icons.tsx
└── entry.semantics.tsInstallation
npm install --save-dev eslint-plugin-entered-modulesUsage
ESLint Flat Config (recommended)
import enteredModules from 'eslint-plugin-entered-modules';
export default [
{
plugins: {
'entered-modules': enteredModules,
},
rules: {
'entered-modules/no-invalid-entry-imports': 'error',
},
},
];Or use the recommended configuration:
import enteredModules from 'eslint-plugin-entered-modules';
export default [
{
plugins: {
'entered-modules': enteredModules,
},
rules: {
...enteredModules.configs.recommended.rules,
},
},
];Rules
no-invalid-entry-imports
Enforces strict import rules for modular architecture with entry point files.
Rule Details
This rule enforces that all imports follow a modular architecture pattern where modules expose their APIs through explicit entry point files. The rule ensures:
- Top-level imports (
@/module-name/...) can only traverse exactly 1 directory segment and must reference entry files matching the patternentry.<OPTIONAL-SEGMENTS>.ts[x]. - Parent imports (
../...) can only traverse exactly 1 directory segment and must reference children entry files matching the patternentry..<OPTIONAL-SEGMENTS>.children.ts[x]. - Subdirectory imports (
./subdir/...) can only traverse exactly 1 directory segment and must reference entry files matching the patternentry.<OPTIONAL-SEGMENTS>.ts[x]. - Sibling imports (
./file.ts) can reference any file at the same nesting level.
Entry Point File Naming
Entry point files must follow these patterns:
- Normal entry points:
entry.ts,entry.core.ts,entry.cs.ts, etc.- Pattern:
entry.<OPTIONAL-SEGMENTS>.ts[x]where segments match[0-9A-Za-z-]+.
- Pattern:
- Children entry points (internal APIs for child modules):
entry..children.ts,entry..utils.children.ts, etc.- Pattern:
entry..<OPTIONAL-SEGMENTS>.children.ts[x].
- Pattern:
Examples
✅ Valid imports:
// Top-level imports (1 directory segment, entry file)
import { foo } from '@/utils/entry';
import { bar } from '@/ui/entry.core';
// Parent imports (children entry files only)
import { baz } from '../entry..children';
import { qux } from '../entry..canvas.children';
// Subdirectory imports (1 segment, entry file)
import { component } from './components/entry';
// Sibling imports (any file)
import { helper } from './helper';
import { config } from './entry';❌ Invalid imports:
// Top-level imports with too many directory segments
import { foo } from '@/utils/helpers/entry'; // ❌ Error: must have exactly 1 directory segment
// Top-level imports to non-entry files
import { bar } from '@/ui/Button'; // ❌ Error: must reference entry.<SEGMENTS>.ts[x] file
// Top-level imports to children entries
import { baz } from '@/utils/entry..children'; // ❌ Error: children entries are for internal use only
// Parent imports to normal entry files
import { qux } from '../entry'; // ❌ Error: must reference entry..<SEGMENTS>.children.ts[x] file
// Parent imports with too many segments
import { quux } from '../../entry..children'; // ❌ Error: can only traverse 1 directory segment
// Subdirectory imports with too many segments
import { corge } from './utils/helpers/entry'; // ❌ Error: can only traverse 1 directory segment
// Subdirectory imports to children entries
import { grault } from './utils/entry..children'; // ❌ Error: cannot reference children entriesArchitecture
This plugin enforces a modular architecture pattern where:
- Modules expose their APIs through explicit
entryfiles (notindexfiles). - Deep imports across module boundaries are forbidden.
- Children modules can access parent internal APIs through special
entry..childrenfiles. - All imports can only traverse exactly 1 directory segment (except sibling file imports).
License
MIT
