https://github.com/effozen/eslint-plugin-fsd-lint
ESLint plugin for enforcing Feature-Sliced Design architecture
https://github.com/effozen/eslint-plugin-fsd-lint
eslint feature-sliced-design fsd lint plugin
Last synced: 10 months ago
JSON representation
ESLint plugin for enforcing Feature-Sliced Design architecture
- Host: GitHub
- URL: https://github.com/effozen/eslint-plugin-fsd-lint
- Owner: effozen
- License: mit
- Created: 2025-01-30T14:15:23.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-26T17:01:30.000Z (about 1 year ago)
- Last Synced: 2025-07-27T00:45:11.236Z (10 months ago)
- Topics: eslint, feature-sliced-design, fsd, lint, plugin
- Language: JavaScript
- Homepage: https://www.npmjs.com/package/eslint-plugin-fsd-lint
- Size: 754 KB
- Stars: 51
- Watchers: 1
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.ko.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README
# π eslint-plugin-fsd-lint π
[](https://www.npmjs.com/package/eslint-plugin-fsd-lint)
[](https://www.npmjs.com/package/eslint-plugin-fsd-lint)
[](https://bundlephobia.com/package/eslint-plugin-fsd-lint)
[](https://github.com/effozen/eslint-plugin-fsd-lint/blob/main/LICENSE)
[English](README.md) | [νκ΅μ΄](README.ko.md)
> ESLint 9+μ μλ‘μ΄ Flat Config μμ€ν
μ μ§μν©λλ€.
## π μκ°
`eslint-plugin-fsd-lint`λ **Feature-Sliced Design(FSD)** μν€ν
μ²λ₯Ό μ€μνλλ‘ κ°μ νλ ESLint νλ¬κ·ΈμΈμ
λλ€.
μ΅μ **ESLint 9+** λ₯Ό μλ²½ν μ§μνκ³ , **Flat Config** νμμ λ°λ₯΄κΈ° λλ¬Έμ μλ°μ€ν¬λ¦½νΈμ νμ
μ€ν¬λ¦½νΈ νλ‘μ νΈμ λ§€λλ½κ² ν΅ν©ν μ μμ΅λλ€.
### β¨ μ μ΄ νλ¬κ·ΈμΈμ μ¬μ©ν΄μΌ νλμ?
- **Flat Config μ§μ**: **ESLint 9+** λ° **μλ‘μ΄ Flat Config μμ€ν
**κ³Ό μλ²½ νΈν
- **μ격ν FSD μ€μ**: κΈ°λ₯ κΈ°λ° νλ‘μ νΈ κ΅¬μ‘°μμ μν€ν
μ² μλ°μ λ°©μ§
- **μ μ§λ³΄μμ± ν₯μ**: λͺ
νν λͺ¨λ λΆλ¦¬μ μμ‘΄μ± κ΄λ¦¬λ₯Ό μ λ
- **μΌκ΄λ μ½λ νμ§ λ³΄μ₯**: import ν¨ν΄κ³Ό λͺ¨λ² μ¬λ‘λ₯Ό νμ€ν
- **ν¬λ‘μ€ νλ«νΌ νΈνμ±**: Windowsμ Unix κΈ°λ° μμ€ν
λͺ¨λμμ μννκ² μλ
- **μ μ°ν ν΄λ μ΄λ¦ μ§μ **: μ¬μ©μ μ μ ν΄λ μ΄λ¦ ν¨ν΄(`1_app`, `2_pages` λ±) μ§μ
- **λ€μν λ³μΉ νμ**: `@shared`μ `@/shared` λͺ¨λ μ§μ
- **ν¬κ΄μ μΈ ν
μ€νΈ 컀λ²λ¦¬μ§**: μ€μ μλ리μ€μ μ£μ§ μΌμ΄μ€λ‘ μ² μ ν ν
μ€νΈλ¨
### π Feature-Sliced Designμ΄λ?
Feature-Sliced Design(FSD)μ νλ‘ νΈμλ μ ν리μΌμ΄μ
μ ꡬ쑰ννλ νλμ μΈ μν€ν
μ² ν¨ν΄μ
λλ€.
λ³Έ νλ¬κ·ΈμΈμ **μ¬λ°λ₯Έ λ μ΄μ΄ λΆλ¦¬, import μ ν, μμ‘΄μ± κ΄λ¦¬** λ±μ μ£Όμ FSD μμΉμ μ μ©νκ³ ,
κ·λͺ¨κ° μ»€μ Έλ μ μ§λ³΄μκ° μ©μ΄ν μ½λλ² μ΄μ€λ₯Ό λ§λ€λλ‘ λμ΅λλ€.
---
## π¦ μ€μΉ
λ€μ λͺ
λ Ήμ΄λ‘ `eslint-plugin-fsd-lint`λ₯Ό **npm** λλ **pnpm**μΌλ‘ μ€μΉν μ μμ΅λλ€:
### npm μ¬μ© μ:
```shell
npm install --save-dev eslint-plugin-fsd-lint
```
### pnpm μ¬μ© μ:
```shell
pnpm add -D eslint-plugin-fsd-lint
```
### Peer Dependencies
μ΄ νλ¬κ·ΈμΈμ ESLint 9+ λ²μ μ νμλ‘ ν©λλ€.
νλ‘μ νΈμ ESLintκ° μ€μΉλμ΄ μλμ§ νμΈνμΈμ:
```shell
npm install --save-dev eslint
```
> π‘ ν: μ¬λ¬ ν¨ν€μ§λ₯Ό κ°μ§ λͺ¨λ
Έλ ν¬ νκ²½μμ μμ
μ€μ΄λΌλ©΄, νλ‘μ νΈ λ£¨νΈ λ 벨μ `eslint-plugin-fsd-lint`λ₯Ό μ€μΉν΄ λͺ¨λ μν¬μ€νμ΄μ€μμ μ€μ μ 곡μ ν μ μμ΅λλ€.
---
## π μ¬μ© λ°©λ² & μ€μ
### π§ Flat Config μ€μ (`eslint.config.mjs`)
`eslint-plugin-fsd-lint`λ **ESLint 9+** λ²μ μ λ§μΆ° **Flat Config μμ€ν
**κ³Ό λ§€λλ½κ² λμνλλ‘ μ€κ³λμμ΅λλ€.
νλ‘μ νΈμμ μ¬μ©νκΈ° μν΄μλ `eslint.config.mjs`μ λ€μκ³Ό κ°μ μ€μ μ μΆκ°νμΈμ:
```js
import fsdPlugin from 'eslint-plugin-fsd-lint';
export default [
// κΆμ₯ ν리μ
μ¬μ©
fsdPlugin.configs.recommended,
// λλ κ°λ³μ μΌλ‘ κ·μΉ ꡬμ±
{
plugins: {
fsd: fsdPlugin,
},
rules: {
// FSD λ μ΄μ΄ import κ·μΉ κ°μ (μ: featuresλ pagesλ₯Ό import λΆκ°)
'fsd/forbidden-imports': 'error',
// μ¬λΌμ΄μ€/λ μ΄μ΄ κ° μλ κ²½λ‘ import κΈμ§, λ³μΉ(@) μ¬μ©
// κΈ°λ³Έμ μΌλ‘ κ°μ μ¬λΌμ΄μ€ λ΄ μλ κ²½λ‘λ νμ© (μ€μ κ°λ₯)
'fsd/no-relative-imports': 'error',
// Public API (index νμΌ)λ₯Ό ν΅ν importλ§ νμ©
'fsd/no-public-api-sidestep': 'error',
// κ°μ λ μ΄μ΄ λ΄ μ¬λΌμ΄μ€ κ° μ§μ import λ°©μ§
'fsd/no-cross-slice-dependency': 'error',
// λΉμ¦λμ€ λ‘μ§ λ μ΄μ΄μμ UI import λ°©μ§
'fsd/no-ui-in-business-logic': 'error',
// μ μ μ€ν μ΄ μ§μ import κΈμ§
'fsd/no-global-store-imports': 'error',
// FSD λ μ΄μ΄ κΈ°λ°μΌλ‘ import μμ κ°μ
'fsd/ordered-imports': 'warn',
},
},
];
```
**κΈ°λ³Έμ μΌλ‘ κ°μ λλ μ£Όμ μμΉ:**
- **λ μ΄μ΄ μν¬νΈ**: μμ λ μ΄μ΄λ νμ λ μ΄μ΄λ₯Ό μν¬νΈν μ μμ΅λλ€ (μ: `features`λ `pages`λ₯Ό μν¬νΈν μ μμ).
- **μ¬λΌμ΄μ€ 격리**: κ°μ λ μ΄μ΄ λ΄μ μ¬λΌμ΄μ€λ μλ‘ μ§μ μν¬νΈν΄μλ μ λ©λλ€ (`no-cross-slice-dependency`).
- **Public API μ¬μ©**: λ€λ₯Έ μ¬λΌμ΄μ€/λ μ΄μ΄μμμ μν¬νΈλ ν΄λΉ μ¬λΌμ΄μ€/λ μ΄μ΄μ Public API(`index.js` λλ `index.ts`)λ₯Ό ν΅ν΄ μ΄λ£¨μ΄μ ΈμΌ ν©λλ€. λ΄λΆ λͺ¨λ μ§μ μν¬νΈλ κΈμ§λ©λλ€ (`no-public-api-sidestep`).
- **μ λ κ²½λ‘ (λ³μΉ)**: μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€λ λ μ΄μ΄ κ°μ μν¬νΈμλ μ λ κ²½λ‘(μ: `@`μ κ°μ λ³μΉμΌλ‘ μ€μ λ¨)λ₯Ό μ¬μ©νμΈμ. μλ κ²½λ‘λ μΌλ°μ μΌλ‘ κΈμ§λ©λλ€ (`no-relative-imports`).
- **μ¬λΌμ΄μ€ λ΄ μλ κ²½λ‘**: _κ°μ μ¬λΌμ΄μ€ λ΄μμμ_ μν¬νΈμλ μλ κ²½λ‘κ° **νμ©λ©λλ€** (μ: feature λ΄μμ `../lib`μ ν¬νΌλ₯Ό μν¬νΈ). μ΄λ `no-relative-imports`μ `allowSameSlice` μ΅μ
μΌλ‘ μ€μ κ°λ₯ν©λλ€ (κΈ°λ³Έκ°: true).
### π μ¬μ© κ°λ₯ν ꡬμ±
μ΄ νλ¬κ·ΈμΈμ μΈ κ°μ§ λ€μν μκ²©μ± μμ€μ μ¬μ μ μλ ꡬμ±μ μ 곡ν©λλ€:
```js
import fsdPlugin from 'eslint-plugin-fsd-lint';
export default [
// νμ€ κΆμ₯ ꡬμ±
fsdPlugin.configs.recommended,
// μ격ν κ΅¬μ± (λͺ¨λ κ·μΉμ΄ error)
// fsdPlugin.configs.strict,
// κΈ°λ³Έ κ΅¬μ± (λ μ격ν¨)
// fsdPlugin.configs.base,
];
```
### π οΈ κ³ κΈ κ΅¬μ±
κ³ κΈ μ΅μ
μ ν΅ν΄ κ·μΉμ λμμ 컀μ€ν°λ§μ΄μ¦ν μ μμ΅λλ€:
```js
import fsdPlugin from 'eslint-plugin-fsd-lint';
export default [
{
plugins: {
fsd: fsdPlugin,
},
rules: {
// λ³μΉ νμ λ° ν΄λ ν¨ν΄ ꡬμ±
'fsd/forbidden-imports': [
'error',
{
// @shared λλ @/shared νμ λͺ¨λ μ§μ
alias: {
value: '@',
withSlash: false, // @/shared νμμ μν΄ true μ¬μ©
},
// λ²νΈκ° λ§€κ²¨μ§ ν΄λ μ λμ¬ μ§μ
folderPattern: {
enabled: true,
regex: '^(\\d+_)?(.*)',
extractionGroup: 2,
},
},
],
// κΈ°ν κ·μΉ...
},
},
];
```
### π μμ νλ‘μ νΈ κ΅¬μ‘°
λ€μμ FSD μμΉμ μ€μνλ νλ‘μ νΈ κ΅¬μ‘° μμμ
λλ€:
```plaintext
src/
βββ app/ (λλ 1_app/)
β βββ providers/
β βββ store.js
β βββ index.js // μ±μ Public API
β
βββ processes/ (λλ 2_processes/)
β βββ auth/
β βββ onboarding/
β
βββ pages/ (λλ 3_pages/)
β βββ HomePage/
β β βββ ui/ // HomePageλ₯Ό μν UI μ»΄ν¬λνΈ
β β βββ index.ts // HomePageμ Public API
β βββ ProfilePage/
β
βββ widgets/ (λλ 4_widgets/)
β βββ Navbar/
β β βββ ui/
β β βββ index.ts // Navbarμ Public API
β βββ Sidebar/
β
βββ features/ (λλ 5_features/)
β βββ login/
β β βββ ui/ // login featureλ₯Ό μν UI μ»΄ν¬λνΈ
β β βββ model/ // loginμ μν λΉμ¦λμ€ λ‘μ§
β β βββ index.ts // login featureμ Public API
β βββ registration/
β
βββ entities/ (λλ 6_entities/)
β βββ user/
β β βββ ui/
β β βββ model/
β β βββ index.ts // user entityμ Public API
β βββ post/
β
βββ shared/ (λλ 7_shared/)
β βββ ui/ // μ¬μ¬μ© κ°λ₯ν UI μ»΄ν¬λνΈ
β β βββ Button/ // μμ: Button μ»΄ν¬λνΈ
β βββ lib/ // μ νΈλ¦¬ν° ν¨μ, ν¬νΌ
β βββ config/ // 곡μ μ€μ
β βββ index.ts // shared λ μ΄μ΄μ Public API (μ ν μ¬ν)
```
**ꡬ쑰 κΈ°λ° Import μμ:**
- **νμ©λ¨ (λ³μΉ Import - μ¬λΌμ΄μ€/λ μ΄μ΄ κ°):**
```javascript
// features/login/ui/LoginForm.tsx
import { Button } from '@shared/ui/Button'; // OK: Featureκ° λ³μΉμ ν΅ν΄ Shared UI μ¬μ©
import { getUser } from '@entities/user'; // OK: Featureκ° Public APIλ₯Ό ν΅ν΄ User μν°ν° μ¬μ©
```
- **νμ©λ¨ (μλ κ²½λ‘ Import - κ°μ μ¬λΌμ΄μ€ λ΄):**
```javascript
// features/login/ui/LoginForm.tsx
import { validateInput } from '../lib/validation'; // OK: 'login' feature μ¬λΌμ΄μ€ λ΄μμ μλ κ²½λ‘ import
```
- **κΈμ§λ¨ (μλ κ²½λ‘ Import - μ¬λΌμ΄μ€/λ μ΄μ΄ κ°):**
```javascript
// features/login/ui/LoginForm.tsx
import { config } from '../../../app/config'; // BAD: λ€λ₯Έ λ μ΄μ΄λ‘μ μλ κ²½λ‘
import { User } from '../../entities/user/model/types'; // BAD: μλ κ²½λ‘ + Public API μ°ν
```
- **κΈμ§λ¨ (Public API μ°ν):**
```javascript
// features/login/ui/LoginForm.tsx
import { userSlice } from '@entities/user/model/slice'; // BAD: λ΄λΆ λͺ¨λ μ§μ import
```
- **κΈμ§λ¨ (μ¬λΌμ΄μ€ κ° μμ‘΄μ±):**
```javascript
// features/login/model/auth.ts
import { startRegistration } from '@features/registration'; // BAD: 'login' featureκ° 'registration' feature μ§μ import
```
> π‘ ν: μ΄λ¬ν import κ·μΉ, νΉν λ³μΉ μ¬μ©κ³Ό Public API μ€μλ μ½λλ² μ΄μ€μ 리ν©ν λ§κ³Ό μ μ§λ³΄μλ₯Ό ν¨μ¬ μ½κ² λ§λλλ€.
---
## π μ§μνλ κ·μΉ(Rules)
μ΄ νλ¬κ·ΈμΈμ **Feature-Sliced Design(FSD)μ λͺ¨λ² μ¬λ‘**λ₯Ό κ°μ νλ μΌλ ¨μ ESLint κ·μΉμ μ 곡ν©λλ€.
κ° κ·μΉμ **λͺ
νν λͺ¨λ ꡬ쑰 μ μ§, import μ μ½, μν€ν
μ² μλ° λ°©μ§**μ λμμ΄ λ©λλ€.
| κ·μΉ(Rule) | μ€λͺ
|
| --------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **fsd/forbidden-imports** | μμ λ μ΄μ΄μμμ importλ μ¬λΌμ΄μ€ κ° κ΅μ°¨ importλ₯Ό λ°©μ§ν©λλ€. |
| **fsd/no-relative-imports** | μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€λ λ μ΄μ΄ κ°μ μλ κ²½λ‘ importλ₯Ό κΈμ§ν©λλ€. κ°μ μ¬λΌμ΄μ€ λ΄μμλ μλ κ²½λ‘ νμ©. |
| **fsd/no-public-api-sidestep** | Public APIλ₯Ό μ¬μ©νμ§ μκ³ λ΄λΆ λͺ¨λμ μ§μ importνλ κ²μ λ°©μ§ν©λλ€. |
| **fsd/no-cross-slice-dependency** | κ°μ λ μ΄μ΄ λ΄ μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€ κ°μ μ§μ μμ‘΄μ±μ κΈμ§ν©λλ€ (featuresλΏλ§ μλλΌ λͺ¨λ λ μ΄μ΄μ μ μ©). |
| **fsd/no-ui-in-business-logic** | λΉμ¦λμ€ λ‘μ§ λ μ΄μ΄(e.g., entities)μμ UIλ₯Ό importνμ§ λͺ»νλλ‘ ν©λλ€. |
| **fsd/no-global-store-imports** | μ μ μν(`store`)λ₯Ό μ§μ importνμ§ λͺ»νλλ‘ ν©λλ€. |
| **fsd/ordered-imports** | λ μ΄μ΄λ³λ‘ importλ₯Ό κ·Έλ£Ήννλλ‘ κ°μ ν©λλ€. |
---
## π κ·μΉ μμΈ & μμ
### **1οΈβ£ fsd/forbidden-imports**
**μμ λ μ΄μ΄μμμ importμ μ¬λΌμ΄μ€ κ° κ΅μ°¨ importλ₯Ό λ°©μ§ν©λλ€.**
β
**νμ©:** `features` β `entities` λλ `shared`
β **κΈμ§:** `features` β `app` μ§μ import
```js
// β μλͺ»λ μ (featureμμ appμ import)
import { config } from '../../app/config';
// β
μ¬λ°λ₯Έ μ (featureμμ entities/sharedλ₯Ό import)
import { getUser } from '../../entities/user';
import { formatCurrency } from '../../shared/utils';
```
### 2οΈβ£ fsd/no-relative-imports
μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€λ λ μ΄μ΄ κ°μ μλ κ²½λ‘ importλ₯Ό κΈμ§ν©λλ€.
β
νμ©: νλ‘μ νΈ λ³μΉμ μ¬μ©νκ±°λ κ°μ μ¬λΌμ΄μ€ λ΄μμ μλ κ²½λ‘ μ¬μ©
β κΈμ§: μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€ κ°μ μλ κ²½λ‘ import
```javascript
// β μλͺ»λ μ (μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€ κ° μλ κ²½λ‘ import)
import { fetchUser } from '../another-slice/model/api';
// β
μ¬λ°λ₯Έ μ (κ°μ μ¬λΌμ΄μ€ λ΄μμ μλ κ²½λ‘ import)
import { fetchData } from '../model/api';
// β
μ¬λ°λ₯Έ μ (μ¬λΌμ΄μ€λ λ μ΄μ΄ κ°μλ λ³μΉ import)
import { Button } from '@shared/ui/Button';
// @/shared νμλ μ§μ
import { Button } from '@/shared/ui/Button';
```
#### μ¬μ© κ°λ₯ν μ΅μ
:
```javascript
// eslint.config.js νμΌμμ:
'fsd/no-relative-imports': ['error', {
// κ°μ μ¬λΌμ΄μ€ λ΄μμ μλ κ²½λ‘ import νμ© (κΈ°λ³Έκ°: true)
allowSameSlice: true,
// νμ
μ μ© importμ μλ κ²½λ‘ μ¬μ© νμ© (κΈ°λ³Έκ°: false)
allowTypeImports: false,
// μ΄ κ·μΉμ 무μν μ μλ ν
μ€νΈ νμΌ ν¨ν΄
testFilesPatterns: ['\\.test\\.', '\\.spec\\.'],
// 무μν import ν¨ν΄
ignoreImportPatterns: []
}]
```
### 3οΈβ£ fsd/no-public-api-sidestep
features, widgets, entitiesμ λ΄λΆ λͺ¨λμ μ§μ importνμ§ λͺ»νλλ‘ ν©λλ€.
β
νμ©: index.ts(κ³΅κ° API)λ₯Ό ν΅ν import
β κΈμ§: feature λ΄λΆ νμΌμ μ§μ μ κ·Ό
```javascript
// β μλͺ»λ μ (feature λ΄λΆ νμΌ μ§μ import)
import { authSlice } from '../../features/auth/slice.ts';
// β
μ¬λ°λ₯Έ μ (κ³΅κ° APIλ₯Ό ν΅ν΄ import)
import { authSlice } from '../../features/auth';
```
### 4οΈβ£ fsd/no-cross-slice-dependency
κ°μ λ μ΄μ΄ λ΄ μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€ κ°μ μ§μ μμ‘΄μ±μ λ°©μ§ν©λλ€ (λͺ¨λ λ μ΄μ΄μ μ μ©, featuresλ§ ν΄λΉνμ§ μμ).
β
νμ©: νμ λ μ΄μ΄λ₯Ό ν΅ν ν΅μ
β κΈμ§: κ°μ λ μ΄μ΄ λ΄ μλ‘ λ€λ₯Έ μ¬λΌμ΄μ€ κ°μ μ§μ import
```javascript
// β μλͺ»λ μ (κ°μ λ μ΄μ΄μμ λ€λ₯Έ μ¬λΌμ΄μ€ import)
import { processPayment } from '../../features/payment';
// β
μ¬λ°λ₯Έ μ (entities/sharedλ₯Ό μ€κ°μ μ¬μ©)
import { PaymentEntity } from '../../entities/payment';
// β λν μλͺ»λ μ (entities μ¬λΌμ΄μ€κ° λ€λ₯Έ entities μ¬λΌμ΄μ€ import)
import { Product } from '../../entities/product';
// μ΄ κ·μΉμ μ΄μ featuresλΏλ§ μλλΌ λͺ¨λ λ μ΄μ΄μ μ μ©λ©λλ€!
```
### 5οΈβ£ fsd/no-ui-in-business-logic
λΉμ¦λμ€ λ‘μ§ λ μ΄μ΄(μ: entities)μμ UIλ₯Ό importνμ§ λͺ»νλλ‘ ν©λλ€.
β
νμ©: UIλ widgetsλ pages λ΄μμλ§ μ¬μ©
β κΈμ§: entitiesμμ UI μ»΄ν¬λνΈ import
```javascript
// β μλͺ»λ μ (entityμμ widget import)
import { ProfileCard } from '../../widgets/ProfileCard';
// β
μ¬λ°λ₯Έ μ (widgetμμ entity λ°μ΄ν°λ₯Ό μ¬μ©)
import { getUser } from '../../entities/user';
```
### 6οΈβ£ fsd/no-global-store-imports
μ μ μν(store)λ₯Ό μ§μ importνμ§ λͺ»νλλ‘ ν©λλ€.
β
νμ©: useStore λλ useSelectorμ κ°μ ν
μ¬μ©
β κΈμ§: store κ°μ²΄λ₯Ό μ§μ import
```javascript
// β μλͺ»λ μ (store μ§μ import)
import { store } from '../../app/store';
// β
μ¬λ°λ₯Έ μ (ν
μ¬μ©)
import { useStore } from 'zustand';
import { useSelector } from 'react-redux';
```
### 7οΈβ£ fsd/ordered-imports
λ μ΄μ΄λ³λ‘ importλ₯Ό κ·Έλ£Ήννλλ‘ κ°μ ν©λλ€.
β
νμ©: λ μ΄μ΄λ³λ‘ import μ λ ¬
β κΈμ§: 무μμ μμμ import
```javascript
// β μλͺ»λ μ (μμμ μμλ‘ import)
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user';
import { formatCurrency } from '../shared/utils';
import { loginUser } from '../features/auth';
import { Header } from '../widgets/Header';
import { useStore } from '../app/store';
// β
μ¬λ°λ₯Έ μ (λ μ΄μ΄λ³ κ·Έλ£Ήν)
import { useStore } from '../app/store'; // App
import { loginUser } from '../features/auth'; // Features
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user'; // Entities
import { formatCurrency } from '../shared/utils'; // Shared
import { Header } from '../widgets/Header'; // Widgets
```
> π‘ ν: `npx eslint --fix` λͺ
λ Ήμ μ¬μ©νλ©΄, FSD λ μ΄μ΄ κ·μΉμ λ°λΌ importλ₯Ό μλ μ λ ¬ν μ μμ΅λλ€.
---
## π μλ μμ (Auto-fix) μ§μ
`eslint-plugin-fsd-lint` λ΄ μΌλΆ κ·μΉμ ESLintμ `--fix` μ΅μ
μ ν΅ν΄ **μλμΌλ‘ μμ **λ μ μμ΅λλ€.
μ΄λ₯Ό ν΅ν΄ κ°λ°μλ€μ **μλμΌλ‘ μ½λλ₯Ό μμ νμ§ μμλ** λΉ λ₯΄κ² κ·μΉ μλ°μ ν΄κ²°ν μ μμ΅λλ€.
### β
μλ μμ μ΄ μ§μλλ κ·μΉ
λ€μ κ·μΉμ μλ μμ μ μ§μν©λλ€:
| κ·μΉ(Rule) | μ€λͺ
|
| ----------------------- | ------------------------------------------------------------------------- |
| **fsd/ordered-imports** | Feature-Sliced Design(FSD) λ μ΄μ΄ κΈ°μ€μΌλ‘ import μμλ₯Ό μλ μ λ ¬ν©λλ€. |
### π§ ESLintμμ `--fix` μ¬μ©νκΈ°
νλ‘μ νΈμ νΉμ νμΌμ λν μλ μμ μ μ μ©νλ €λ©΄ λ€μκ³Ό κ°μ΄ μ€ννμΈμ:
```shell
npx eslint --fix your-file.js
```
νλ‘μ νΈ λ΄ λͺ¨λ νμΌμ λν΄ μλ μμ μ μ μ©νλ €λ©΄:
```shell
npx eslint --fix .
```
### π μλ μμ μ ν μμ
β μμ μ (`fsd/ordered-imports` μλ°)
```javascript
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user';
import { formatCurrency } from '../shared/utils';
import { loginUser } from '../features/auth';
import { Header } from '../widgets/Header';
import { useStore } from '../app/store';
```
β
μμ ν (`npx eslint --fix` μ μ©λ¨)
```javascript
import { useStore } from '../app/store'; // App
import { loginUser } from '../features/auth'; // Features
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user'; // Entities
import { formatCurrency } from '../shared/utils'; // Shared
import { Header } from '../widgets/Header'; // Widgets
```
> π‘ ν: `fsd/ordered-imports` κ·μΉμ FSD λ μ΄μ΄λ₯Ό κΈ°μ€μΌλ‘ κΉλνκ³ κ΅¬μ‘°μ μΈ import μμλ₯Ό μ μ§ν©λλ€.
---
## π μλ‘μ΄ κΈ°λ₯
### 1. ν¬λ‘μ€ νλ«νΌ νΈνμ±
μ΄ νλ¬κ·ΈμΈμ μ΄μ λ΄λΆμ μΌλ‘ νμΌ κ²½λ‘λ₯Ό μ κ·ννμ¬ Windowsμ Unix κΈ°λ° μμ€ν
λͺ¨λμμ μννκ² μλν©λλ€.
### 2. μ μ°ν ν΄λ μ΄λ¦ ν¨ν΄
μ΄μ ν΄λμ λ²νΈ μ λμ¬ λ±μ μ΄λ¦ μ§μ κ·μΉμ μ¬μ©ν μ μμ΅λλ€:
```js
// ν΄λ ν¨ν΄ μ§μ ꡬμ±
"fsd/forbidden-imports": ["error", {
folderPattern: {
enabled: true,
regex: "^(\\d+_)?(.*)",
extractionGroup: 2
}
}]
```
μ΄λ₯Ό ν΅ν΄ λ€μκ³Ό κ°μ ꡬ쑰λ₯Ό μ¬μ©ν μ μμ΅λλ€:
```
src/
1_app/
2_pages/
3_widgets/
4_features/
5_entities/
6_shared/
```
### 3. λ€μν λ³μΉ νμ μ§μ
μ΄μ νλ¬κ·ΈμΈμ `@shared`μ `@/shared` νμ λͺ¨λ μ§μν©λλ€:
```js
// λ³μΉ νμ ꡬμ±
"fsd/forbidden-imports": ["error", {
alias: {
value: "@",
withSlash: false // @/shared νμμ μν΄ true μ¬μ©
}
}]
```
### 4. ν₯μλ cross-slice-dependency κ·μΉ
`no-cross-slice-dependency` κ·μΉμ μ΄μ κΈ°λ³Έμ μΌλ‘ featuresλΏλ§ μλλΌ λͺ¨λ λ μ΄μ΄μ μ μ©λ©λλ€:
```js
// features λ μ΄μ΄λ§ μ ν (λ κ±°μ λμ)
"fsd/no-cross-slice-dependency": ["error", {
featuresOnly: true
}]
```
### 5. μ¬μ μ μλ κ΅¬μ± νλ‘ν
μ΄μ μ¬λ¬ κ΅¬μ± ν리μ
μ μ¬μ©ν μ μμ΅λλ€:
- `recommended` - νμ€ κΆμ₯ μ€μ
- `strict` - μ΅λ κ°μ μμ€
- `base` - μ¬μ΄ λμ
μ μν λ μ격ν μ€μ
### 6. ν¬κ΄μ μΈ ν
μ€νΈ 컀λ²λ¦¬μ§
μ΄ νλ¬κ·ΈμΈμ μ΄μ λͺ¨λ κ·μΉμ λν κ΄λ²μν ν
μ€νΈ μΌμ΄μ€λ₯Ό ν¬ν¨ν©λλ€:
- κΈ°λ³Έ import μλ리μ€
- μ£μ§ μΌμ΄μ€μ 볡μ‘ν ν¨ν΄
- κ²½λ‘ λ³ν (Windows, Unix, νΌν©)
- μ¬μ©μ μ μ ꡬμ±
- μ€μ μ¬μ© μμ
### κ²½λ‘ λ³μΉ μ§μ
νλ¬κ·ΈμΈμ μ΄μ λ€μν κ²½λ‘ λ³μΉ νμμ μ¬λ°λ₯΄κ² μ²λ¦¬ν©λλ€:
```javascript
// λ νμ λͺ¨λ μ§μλ©λλ€
import { UserCard } from '@entities/user';
import { UserCard } from '@/entities/user';
```
### λμ import μ§μ
λͺ¨λ κ·μΉμ΄ μ΄μ λμ importλ₯Ό μ§μν©λλ€:
```javascript
// μ ν¨ν λμ import
const UserCard = await import('@entities/user');
const { UserCard } = await import('@entities/user');
// μ ν¨νμ§ μμ λμ import (κ·μΉμ μν΄ κ°μ§λ¨)
const UserCard = await import('@entities/user/ui');
```
### ν¬κ΄μ μΈ ν
μ€νΈ 컀λ²λ¦¬μ§
λͺ¨λ κ·μΉμ΄ μ΄μ μ² μ νκ² ν
μ€νΈλμμ΅λλ€:
- κΈ°λ³Έ import μλ리μ€
- μ£μ§ μΌμ΄μ€μ 볡μ‘ν ν¨ν΄
- κ²½λ‘ λ³ν (Windows, Unix, νΌν©)
- μ¬μ©μ μ μ ꡬμ±
- μ€μ μ¬μ© μμ
- κ²½λ‘ λ³μΉ νμ
- λμ import ν¨ν΄
## κ·μΉ
### no-public-api-sidestep
λ΄λΆ λͺ¨λμμμ μ§μ importλ₯Ό λ°©μ§νκ³ public API μ¬μ©μ κ°μ ν©λλ€.
```javascript
// β
μ ν¨ν¨: public API μ¬μ©
import { UserCard } from '@entities/user';
import { UserCard } from '@/entities/user'; // μ΄κ²λ μ ν¨ν¨
import { UserCard } from '@entities/user/index';
// β μ ν¨νμ§ μμ: λ΄λΆ μ§μ import
import { UserCard } from '@entities/user/ui/UserCard';
import { UserCard } from '@entities/user/model/types';
// β
μ ν¨ν¨: public APIλ₯Ό μ¬μ©νλ λμ import
const UserCard = await import('@entities/user');
const { UserCard } = await import('@entities/user');
// β μ ν¨νμ§ μμ: public APIλ₯Ό μ°ννλ λμ import
const UserCard = await import('@entities/user/ui/UserCard');
```
---
## π€ 컨νΈλ¦¬λ·°μ
(κΈ°μ¬)
`eslint-plugin-fsd-lint`λ₯Ό κ°μ νκΈ° μν λͺ¨λ κΈ°μ¬λ₯Ό νμν©λλ€!
μλ‘μ΄ κ·μΉ μ μμ΄λ κ°μ μ¬νμ΄ μλ€λ©΄ μμ λ‘κ² Pull Requestλ₯Ό 보λ΄μ£ΌμΈμ.
μμΈν λ΄μ©μ [κΈ°μ¬ κ°μ΄λ](CONTRIBUTING.md)λ₯Ό μ°Έκ³ νμΈμ.
---
## π λΌμ΄μ μ€
μ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€λ‘ λ°°ν¬λ©λλ€.
μμΈν λ΄μ©μ [LICENSE](LICENSE.md) νμΌμ μ°Έκ³ νμΈμ.