! 주의 : TIL 게시글입니다. 다듬지 않고 올리거나 기록을 통째로 복붙했을 수 있는 뒷고기 포스팅입니다.
이 글에서는 내 팀에 맞는 FSD 기반 폴더구조에 대한 고민과 경험을 이야기한 글에서 언급한, ESLint 룰로 Layer 의존성 규칙 설정과 경로 별칭 설정에 대한 실제 설정코드와 트러블 슈팅을 기록합니다.
ESLint Rule로 Layer 의존성 규칙 강제하기
eslint-plugin-boundaries 플러그인을 준비합니다
npm install -D eslint-plugin-boundaries//eslint.config.js
import boundaries from 'eslint-plugin-boundaries';
export default tseslint.config([
{
ignores: []
},
{
...
plugins: {
...
boundaries,
},
}
])이제 eslint config에서 settings를 다음과 같이 작성하여 각 Layer들을 elements-type으로 각각 정의합니다.
export default tseslint.config([
...
{
settings: {
// 레이어 정의
'boundaries/elements': [
{ type: 'app', pattern: 'src/app/**' },
{ type: 'processes', pattern: 'src/processes/**' },
{ type: 'features', pattern: 'src/features/**' },
{ type: 'shared', pattern: 'src/shared/**' },
{ type: 'config', pattern: 'src/config/**' },
],
},
}이제 rules를 다음과 같이 작성해주면!!
export default tseslint.config([
...
{
settings: ...
rules: {
'boundaries/element-types': [
'error',
{
default: 'disallow',
rules: [
{
from: 'app',
allow: [
'processes',
'features',
'shared',
'config',
'api',
],
},
{
from: 'processes',
allow: ['features', 'shared', 'config', 'api'],
},
{
from: 'features',
allow: ['shared', 'config', 'api'],
},
{ from: 'shared', allow: ['api', 'config'] },
{ from: 'api', allow: ['config'] },
],
},
],default: 'disallow'로 기본적으로 금지하고, 허용할 의존성 관계만allow합니다.app → processes → features → shared방향으로만 접근이 가능하도록 작성합니다.features → features같은, 동일 Layer의 다른 Slice 끼리 의존 또한 막습니다
- env 등 configuration 관련 코드가 있는
config/Layer는 읽기 전용으로 어디서든 참조할 수 있도록 합니다.
이제 예를 들어, app하위 파일을 features가 import하려고 시도하면, 아래와 같이 Lint에러가 발생합니다.


경로 별칭 설정하기
eslint-plugin-import라는 ESLint 플러그인을 하나 더 설치하고,
eslint.config.js에서 아까 직전에 설치했던 boundaries 밑에 추가해줍시다.
npm install -D eslint-plugin-import//eslint.config.js
import boundaries from 'eslint-plugin-boundaries';
import eslintPluginImport from 'eslint-plugin-import';
export default tseslint.config([
{
ignores: []
},
{
...
plugins: {
...
boundaries,
import: eslintPluginImport,
},
settings: {
// 레이어 정의
'boundaries/elements': ...,
// 경로 별칭
'import/resolver': {
typescript: { project: ['./tsconfig.json'] },
node: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
},
},
}
])tsconfig.json, tsconfig.app.json, tsconfig.node.json에 다음과 같이 설정해줬습니다
{
...,
"compilerOptions": {
"paths": {
"@app/*": ["./src/app/*"],
"@processes/*": ["./src/processes/*"],
"@features/*": ["./src/features/*"],
"@api/*": ["./src/api/*"],
"@api": ["./src/api"],
"@shared/*": ["./src/shared/*"],
"@config/*": ["./src/config/*"],
"@entities/*": ["./src/entities/*"]
}
}
}또한 vite.config.ts에서:
//vite.config.ts
import { fileURLToPath } from 'node:url';
export default defineConfig({
...,
resolve: {
alias: {
'@app': fileURLToPath(new URL('./src/app', import.meta.url)),
'@processes': fileURLToPath(new URL('./src/processes', import.meta.url)),
'@features': fileURLToPath(new URL('./src/features', import.meta.url)),
'@api': fileURLToPath(new URL('./src/api', import.meta.url)),
'@shared': fileURLToPath(new URL('./src/shared', import.meta.url)),
'@config': fileURLToPath(new URL('./src/config', import.meta.url)),
'@entities': fileURLToPath(new URL('./src/entities', import.meta.url)),
},
},
});이렇게 하면 이제 아래와 같이 import할 수 있습니다.
import Foo from "@shared/ui/Foo";버그 : <tsconfigRootDir> 파싱에러
Parsing error: ESLint was configured to run on `<tsconfigRootDir>/src/shared/ui/Button.tsx` using `parserOptions.project`: `<tsconfigRootDir>/tsconfig.json`
이런 에러가 났었는데요 저는
//eslint.config.js
const __dirname = import.meta.dirname;
export default tseslint.config([
{},
{
...
languageOptions: {
...
parserOptions: { project: './tsconfig.json', tsconfigRootDir: __dirname },
}
}
])tsconfigRootDir를 이렇게 넣어줍시다.
위 설정들은 팀 프로젝트 내에서 쓰기 위한 정도의 설정 예시입니다.
당연히 설정은 본인 팀, 본인 프로젝트에 맞게 적절히 쓰시면 되겠습니다
FSD 문서에서도 언급하듯이 정답은 없습니다