UNPKG

eslint-plugin-fsd-lint

Version:

ESLint plugin for enforcing Feature-Sliced Design (FSD) architecture

889 lines (684 loc) β€’ 30.5 kB
# πŸš€ eslint-plugin-fsd-lint πŸš€ [![npm version](https://img.shields.io/npm/v/eslint-plugin-fsd-lint)](https://www.npmjs.com/package/eslint-plugin-fsd-lint) [![npm downloads](https://img.shields.io/npm/dt/eslint-plugin-fsd-lint)](https://www.npmjs.com/package/eslint-plugin-fsd-lint) [![npm bundle size](https://img.shields.io/bundlephobia/min/eslint-plugin-fsd-lint)](https://bundlephobia.com/package/eslint-plugin-fsd-lint) [![License](https://img.shields.io/npm/l/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` λͺ¨λ‘ 지원 - **tsconfig 경둜 인식**: `tsconfig.json` / `jsconfig.json`의 `paths`와 `baseUrl`을 μ‚¬μš©ν•΄ aliasκ°€ κ°€λ¦¬ν‚€λŠ” μ‹€μ œ νŒŒμΌμ„ κΈ°μ€€μœΌλ‘œ λ ˆμ΄μ–΄μ™€ 슬라이슀λ₯Ό νŒλ³„ - **포괄적인 ν…ŒμŠ€νŠΈ 컀버리지**: μ‹€μ œ μ‹œλ‚˜λ¦¬μ˜€μ™€ μ—£μ§€ μΌ€μ΄μŠ€λ‘œ μ² μ €νžˆ ν…ŒμŠ€νŠΈλ¨ ### πŸ” 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, slice-level Public API와 shared Public API κ°•μ œ) // fsdPlugin.configs.strict, // κΈ°λ³Έ ꡬ성 (덜 엄격함) // fsdPlugin.configs.base, ]; ``` <details> <summary>recommended 프리셋 μ„€μ •</summary> ```js { plugins: { fsd: fsdPlugin, }, rules: { "fsd/forbidden-imports": "error", "fsd/no-cross-slice-dependency": "error", "fsd/no-global-store-imports": "error", "fsd/no-public-api-sidestep": "error", "fsd/no-relative-imports": "error", "fsd/no-ui-in-business-logic": "error", "fsd/ordered-imports": "warn", }, } ``` </details> <details> <summary>strict 프리셋 μ„€μ •</summary> ```js { plugins: { fsd: fsdPlugin, }, rules: { "fsd/forbidden-imports": "error", "fsd/no-cross-slice-dependency": "error", "fsd/no-global-store-imports": "error", "fsd/no-public-api-sidestep": [ "error", { publicApi: { allowSegmentImports: false, enforceShared: true, }, }, ], "fsd/no-relative-imports": "error", "fsd/no-ui-in-business-logic": "error", "fsd/ordered-imports": "error", }, } ``` </details> <details> <summary>base 프리셋 μ„€μ •</summary> ```js { plugins: { fsd: fsdPlugin, }, rules: { "fsd/forbidden-imports": "warn", "fsd/no-cross-slice-dependency": "warn", "fsd/no-global-store-imports": "error", "fsd/no-public-api-sidestep": "warn", "fsd/no-relative-imports": "off", "fsd/no-ui-in-business-logic": "error", "fsd/ordered-imports": "warn", }, } ``` </details> ### Next.js App Router와 μ»€μŠ€ν…€ λ ˆμ΄μ–΄ 폴더λͺ… Next.js App Router ν”„λ‘œμ νŠΈμ—μ„œλŠ” Next의 λΌμš°νŒ… νŒŒμΌμ„ `src/app`에 두고, λ‚˜λ¨Έμ§€ μ½”λ“œλ₯Ό FSD λ ˆμ΄μ–΄λ‘œ ꡬ성할 수 μžˆμŠ΅λ‹ˆλ‹€. ꢌμž₯ 섀정은 FSD ν‘œμ€€ λ ˆμ΄μ–΄ 폴더λͺ…(`app`, `pages`, `widgets`, `features`, `entities`, `shared`)을 κ·ΈλŒ€λ‘œ μ“°λŠ” κ²ƒμž…λ‹ˆλ‹€. `layers`λ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜μ§€ μ•ŠμœΌλ©΄ ν”ŒλŸ¬κ·ΈμΈμ€ λ‚΄μž₯ FSD 폴더λͺ…(`app`, `processes`, `pages`, `widgets`, `features`, `entities`, `shared`)을 κΈ°λ³Έκ°’μœΌλ‘œ μ‚¬μš©ν•˜λ―€λ‘œ, `processes`λ₯Ό μ“°λŠ” κΈ°μ‘΄ ν”„λ‘œμ νŠΈλ„ 계속 μ§€μ›λ©λ‹ˆλ‹€. μ»€μŠ€ν…€ λ ˆμ΄μ–΄ 폴더λͺ…은 κΈ°μ‘΄ ν”„λ‘œμ νŠΈ, λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, ν”„λ ˆμž„μ›Œν¬ μ œμ•½μ΄ μžˆλŠ” 경우λ₯Ό μœ„ν•΄ μ§€μ›ν•˜μ§€λ§Œ, FSD μ»¨λ²€μ…˜μ΄ 덜 λͺ…ν™•ν•΄μ§ˆ 수 μžˆμœΌλ―€λ‘œ κΈ°λ³Έ μ„ νƒμ§€λ‘œλŠ” ꢌμž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. FSD의 `pages` λ ˆμ΄μ–΄ 이름 λŒ€μ‹  `screens` 폴더λͺ…을 써야 ν•œλ‹€λ©΄ `eslint.config.js`μ—μ„œ `layers.pages.pattern`으둜 λ§€ν•‘ν•˜λ©΄ λ©λ‹ˆλ‹€. 같은 λ°©μ‹μœΌλ‘œ λ‹€λ₯Έ λ ˆμ΄μ–΄ 폴더λͺ…도 ν”„λ‘œμ νŠΈ 상황에 맞게 λ°”κΏ€ 수 μžˆμŠ΅λ‹ˆλ‹€. μ„€μ •μ—μ„œλŠ” FSD의 ν‘œμ€€ λ ˆμ΄μ–΄ ν‚€λ₯Ό μœ μ§€ν•˜κ³ , 각 λ ˆμ΄μ–΄μ˜ `pattern`에 μ‹€μ œ 폴더λͺ…을 λ„£μœΌλ©΄ λ©λ‹ˆλ‹€. ```js import fsdPlugin from "eslint-plugin-fsd-lint"; const fsdOptions = { rootPath: "/src/", alias: { value: "@", withSlash: true, }, layers: { app: { pattern: "app", }, pages: { pattern: "screens", }, widgets: { pattern: "blocks", }, features: { pattern: "actions", }, entities: { pattern: "domain", }, shared: { pattern: "common", }, }, ignoreImportPatterns: ["\\.css$"], }; export default [ { files: ["src/**/*.{js,jsx,ts,tsx}"], plugins: { fsd: fsdPlugin, }, rules: { "fsd/forbidden-imports": ["error", fsdOptions], "fsd/no-cross-slice-dependency": ["error", fsdOptions], "fsd/no-public-api-sidestep": ["error", fsdOptions], "fsd/no-relative-imports": [ "error", { ...fsdOptions, allowSameSlice: true, }, ], "fsd/no-global-store-imports": "error", "fsd/ordered-imports": ["warn", fsdOptions], }, }, ]; ``` ```text src/ β”œβ”€β”€ app/ β”‚ β”œβ”€β”€ layout.tsx β”‚ β”œβ”€β”€ page.tsx β”‚ └── api/health/route.ts β”œβ”€β”€ screens/ β”‚ └── dashboard/ β”œβ”€β”€ blocks/ β”œβ”€β”€ actions/ β”œβ”€β”€ domain/ └── common/ ``` `allowedToImport`, `excludeLayers`, `publicApi.enforceForLayers`, `ordered-imports.customOrder` 같은 μ˜΅μ…˜μ€ μ—¬μ „νžˆ ν‘œμ€€ λ ˆμ΄μ–΄ ν‚€(`app`, `pages`, `widgets`, `features`, `entities`, `shared`)λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. `pattern` κ°’λ§Œ μ‹€μ œ 폴더λͺ…μ΄λ‚˜ import segment둜 μ“°μž…λ‹ˆλ‹€. μƒˆ ν”„λ‘œμ νŠΈλΌλ©΄ λͺ…ν™•ν•œ μ΄μœ κ°€ μ—†λŠ” ν•œ ν‘œμ€€ λ ˆμ΄μ–΄ 폴더λͺ…을 μš°μ„  μ‚¬μš©ν•˜λŠ” 편이 μ’‹μŠ΅λ‹ˆλ‹€. ### νŒŒμΌμ‹œμŠ€ν…œ 기반 import 해석 λ ˆμ΄μ–΄μ™€ μŠ¬λΌμ΄μŠ€λŠ” 더 이상 import λ¬Έμžμ—΄μ΄ μ•„λ‹ˆλΌ μ‹€μ œ ν•΄μ„λœ 파일 경둜λ₯Ό κΈ°μ€€μœΌλ‘œ νŒλ‹¨ν•©λ‹ˆλ‹€. 각 κ·œμΉ™μ€ λ‹€μŒ μˆœμ„œλ‘œ importλ₯Ό ν•΄μ„ν•©λ‹ˆλ‹€. 1. `tsconfig.json` / `jsconfig.json`의 `paths` (κ°€μž₯ κΈ΄ νŒ¨ν„΄μ΄ μš°μ„ , 닀쀑 νƒ€κ²Ÿμ€ λ””μŠ€ν¬μ— μ‘΄μž¬ν•˜λŠ” 첫 번째 νŒŒμΌμ„ 선택) 2. `tsconfig.json`의 `baseUrl` 3. μ„€μ •λœ FSD `alias`와 `rootPath` μ‘°ν•© 4. ν˜„μž¬ 파일 κΈ°μ€€ μƒλŒ€ 경둜 5. μ‹€μ œ νŒŒμΌμ„ μ°Ύμ§€ λͺ»ν–ˆμ„ λ•Œμ˜ legacy λ¬Έμžμ—΄ νŒŒμ‹± (예: λ²ˆλ“€λŸ¬μ—λ§Œ aliasκ°€ μ •μ˜λœ 경우) 이 λ³€κ²½ 덕뢄에 alias둜 μž‘μ„±ν•œ 같은 슬라이슀 importκ°€ 더 이상 cross-slice둜 잘λͺ» λ³΄κ³ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ `tsconfig.json`이 `@articles/*`λ₯Ό `src/pages/articles/*`둜 λ§€ν•‘ν•˜κ³  μžˆλ‹€λ©΄, μ•„λž˜ μ½”λ“œλŠ” 이제 μ •μƒμœΌλ‘œ ν†΅κ³Όν•©λ‹ˆλ‹€. ```ts // src/pages/articles/ui/articles-pending-page.tsx import { articleSections } from "@articles/api/queries"; import { ArticlesLayout } from "@articles/ui/articles-page"; ``` κΈ°λ³Έμ μœΌλ‘œλŠ” 린트 λŒ€μƒ νŒŒμΌμ—μ„œ μœ„λ‘œ μ˜¬λΌκ°€λ©° κ°€μž₯ κ°€κΉŒμš΄ `tsconfig.json` / `jsconfig.json`을 μžλ™μœΌλ‘œ μ°ΎμŠ΅λ‹ˆλ‹€. μ›ν•˜λŠ” μ„€μ • 파일이 λ‹€λ₯Έ κ²½λ‘œμ— μžˆλ‹€λ©΄ 각 κ·œμΉ™μ— `tsconfigPath`둜 μ§€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ```js import fsdPlugin from "eslint-plugin-fsd-lint"; export default [ { plugins: { fsd: fsdPlugin, }, rules: { "fsd/forbidden-imports": [ "error", { rootPath: "/apps/web/src/", tsconfigPath: "./apps/web/tsconfig.json", }, ], "fsd/no-cross-slice-dependency": [ "error", { rootPath: "/apps/web/src/", tsconfigPath: "./apps/web/tsconfig.json", }, ], "fsd/no-public-api-sidestep": [ "error", { rootPath: "/apps/web/src/", tsconfigPath: "./apps/web/tsconfig.json", }, ], }, }, ]; ``` νƒ€κ²Ÿ νŒŒμΌμ„ μ°Ύμ§€ λͺ»ν•˜λ©΄ μžλ™μœΌλ‘œ legacy λ¬Έμžμ—΄ νŒŒμ‹±μœΌλ‘œ ν΄λ°±λ˜λ―€λ‘œ, λ²ˆλ“€λŸ¬μ—λ§Œ aliasκ°€ μ •μ˜λœ ν”„λ‘œμ νŠΈλŠ” κΈ°μ‘΄κ³Ό λ™μΌν•˜κ²Œ λ™μž‘ν•©λ‹ˆλ‹€. ### πŸ› οΈ κ³ κΈ‰ ꡬ성 κ³ κΈ‰ μ˜΅μ…˜μ„ 톡해 κ·œμΉ™μ˜ λ™μž‘μ„ μ»€μŠ€ν„°λ§ˆμ΄μ¦ˆν•  수 μžˆμŠ΅λ‹ˆλ‹€: ```js import fsdPlugin from "eslint-plugin-fsd-lint"; export default [ { plugins: { fsd: fsdPlugin, }, rules: { // 별칭 ν˜•μ‹ 및 폴더 νŒ¨ν„΄ ꡬ성 "fsd/forbidden-imports": [ "error", { rootPath: "/src/root/", // @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"; ``` <br/> ### 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: [] }] ``` <br/> ### 3️⃣ fsd/no-public-api-sidestep features, widgets, entities의 λ‚΄λΆ€ λͺ¨λ“ˆμ„ 직접 importν•˜μ§€ λͺ»ν•˜λ„둝 ν•©λ‹ˆλ‹€. βœ… κΈ°λ³Έ ν—ˆμš©: index.ts(곡개 API) λ˜λŠ” μ„Έκ·Έλ¨ΌνŠΈ λ ˆλ²¨μ„ ν†΅ν•œ import ❌ κΈˆμ§€: μ„Έκ·Έλ¨ΌνŠΈ λ‚΄λΆ€ νŒŒμΌμ— 직접 μ ‘κ·Ό ```javascript // βœ… μ˜¬λ°”λ₯Έ 예 (곡개 APIλ₯Ό 톡해 import) import { authSlice } from "../../features/auth"; import { userModel } from "@entities/user/model"; // μ„Έκ·Έλ¨ΌνŠΈ λ ˆλ²¨μ€ ν—ˆμš© // ❌ 잘λͺ»λœ 예 (λ‚΄λΆ€ 파일 직접 import) import { authSlice } from "../../features/auth/slice.ts"; import { userSlice } from "@entities/user/model/slice"; // λ‚΄λΆ€ νŒŒμΌμ€ ν—ˆμš©λ˜μ§€ μ•ŠμŒ ``` **μ°Έκ³ :** λͺ¨λ“  μ„Έκ·Έλ¨ΌνŠΈ 이름이 ν—ˆμš©λ©λ‹ˆλ‹€ (model/ui/api/lib에 μ œν•œλ˜μ§€ μ•ŠμŒ). μ΄λŠ” νŒ€μ΄ 자체적인 μ„Έκ·Έλ¨ΌνŠΈ ꡬ쑰λ₯Ό μ •μ˜ν•  수 μžˆλŠ” μœ μ—°μ„±μ„ μ œκ³΅ν•©λ‹ˆλ‹€. <br/> ### 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뿐만 μ•„λ‹ˆλΌ λͺ¨λ“  λ ˆμ΄μ–΄μ— μ μš©λ©λ‹ˆλ‹€! ``` <br/> ### 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"; ``` <br/> ### 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"; ``` <br/> ### 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"; ``` <br/> βœ… μˆ˜μ • ν›„ (`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` - μ΅œλŒ€ κ°•μ œ μˆ˜μ€€, slice-level Public API와 shared Public API κ°•μ œ - `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"); ``` 더 μ—„κ²©ν•œ ν”„λ‘œμ νŠΈμ—μ„œλŠ” μ„Έκ·Έλ¨ΌνŠΈ 레벨 Public API importλ₯Ό 막고 `shared` λ ˆμ΄μ–΄μ—λ„ Public API μ‚¬μš©μ„ κ°•μ œν•  수 μžˆμŠ΅λ‹ˆλ‹€. ```javascript export default [ { rules: { "fsd/no-public-api-sidestep": [ "error", { publicApi: { allowSegmentImports: false, enforceShared: true, }, }, ], }, }, ]; ``` --- ## 🀝 μ»¨νŠΈλ¦¬λ·°μ…˜(κΈ°μ—¬) `eslint-plugin-fsd-lint`λ₯Ό κ°œμ„ ν•˜κΈ° μœ„ν•œ λͺ¨λ“  κΈ°μ—¬λ₯Ό ν™˜μ˜ν•©λ‹ˆλ‹€! μƒˆλ‘œμš΄ κ·œμΉ™ μ œμ•ˆμ΄λ‚˜ κ°œμ„  사항이 μžˆλ‹€λ©΄ 자유둭게 Pull Requestλ₯Ό λ³΄λ‚΄μ£Όμ„Έμš”. μžμ„Έν•œ λ‚΄μš©μ€ [κΈ°μ—¬ κ°€μ΄λ“œ](CONTRIBUTING.md)λ₯Ό μ°Έκ³ ν•˜μ„Έμš”. --- ## πŸ“ λΌμ΄μ„ μŠ€ 이 ν”„λ‘œμ νŠΈλŠ” MIT λΌμ΄μ„ μŠ€λ‘œ λ°°ν¬λ©λ‹ˆλ‹€. μžμ„Έν•œ λ‚΄μš©μ€ [LICENSE](LICENSE.md) νŒŒμΌμ„ μ°Έκ³ ν•˜μ„Έμš”. <br/>