๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Path Mapping์„ ์‚ฌ์šฉํ•˜๋ฉด ์ƒ๋Œ€ ๊ฒฝ๋กœ ๋Œ€์‹  ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ ๋ชจ๋“ˆ์„ importํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด ํ–ฅ์ƒ๋˜๊ณ  ํŒŒ์ผ ์ด๋™ ์‹œ import ๊ฒฝ๋กœ๋ฅผ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Web (React)์˜ Path Mapping

Sonamu Web ํ”„๋กœ์ ํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ @/* ๊ฒฝ๋กœ ๋งคํ•‘์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

tsconfig.json ์„ค์ •

web/tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Vite ์„ค์ •

TypeScript ์„ค์ •๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•˜๊ณ , Vite์—๋„ ๋™์ผํ•œ alias๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
web/vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
TypeScript์™€ Vite ๋ชจ๋‘์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜๋งŒ ์„ค์ •ํ•˜๋ฉด ํƒ€์ž… ์ฒดํฌ๋‚˜ ๋นŒ๋“œ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ์˜ˆ์‹œ

src/pages/users/UserDetail.tsx
// โŒ ๋ณต์žกํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€
import { Button } from "../../components/Button";
import { Input } from "../../components/Input";
import { formatDate } from "../../utils/format";
import { useAuth } from "../../hooks/useAuth";
๋ฌธ์ œ์ :
  • ํŒŒ์ผ ์œ„์น˜๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋ชจ๋“  import ์ˆ˜์ • ํ•„์š”
  • ../../../ ๊ฐ™์€ ๋ณต์žกํ•œ ๊ฒฝ๋กœ
  • ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง

๋””๋ ‰ํ† ๋ฆฌ๋ณ„ ๋งคํ•‘

๋” ๋งŽ์€ ๊ฒฝ๋กœ๋ฅผ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
web/tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@pages/*": ["./src/pages/*"],
      "@utils/*": ["./src/utils/*"],
      "@hooks/*": ["./src/hooks/*"],
      "@types/*": ["./src/types/*"]
    }
  }
}
web/vite.config.ts
export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
      "@components": path.resolve(__dirname, "./src/components"),
      "@pages": path.resolve(__dirname, "./src/pages"),
      "@utils": path.resolve(__dirname, "./src/utils"),
      "@hooks": path.resolve(__dirname, "./src/hooks"),
      "@types": path.resolve(__dirname, "./src/types"),
    },
  },
});
์‚ฌ์šฉ ์˜ˆ์‹œ:
// ๊ธฐ๋ณธ ๋ฐฉ์‹
import { Button } from "@/components/Button";

// ์„ธ๋ถ€ ๋งคํ•‘
import { Button } from "@components/Button";
import { UserPage } from "@pages/UserPage";
import { formatDate } from "@utils/format";
import { useAuth } from "@hooks/useAuth";
import type { User } from "@types/user";
@/* ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ๋งŽ์€ ๋งคํ•‘์€ ์˜คํžˆ๋ ค ํ˜ผ๋ž€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

API ์„œ๋ฒ„์˜ ๋ชจ๋“ˆ ํ•ด์„

API ์„œ๋ฒ„๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ path mapping์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด์œ 

api/tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "bundler",  // Vite๊ฐ€ ๋ชจ๋“ˆ ํ•ด์„
    "target": "esnext",
    "module": "esnext"
  }
}
Vite๊ฐ€ ๋ชจ๋“  ๋ชจ๋“ˆ์„ ํ•ด์„
  • API๋Š” Vite๋กœ ๋นŒ๋“œ๋˜๋ฏ€๋กœ ๋ณ„๋„์˜ path mapping ๋ถˆํ•„์š”
  • sonamu ๊ฐ™์€ ํŒจํ‚ค์ง€๋Š” Vite๊ฐ€ ์ž๋™์œผ๋กœ ํ•ด์„
  • ์ƒ๋Œ€ ๊ฒฝ๋กœ๊ฐ€ ๋” ๋ช…ํ™•ํ•˜๊ณ  ๋‹จ์ˆœํ•จ

API ๊ตฌ์กฐ

API๋Š” ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ„๋‹จํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
api/src/application/controllers/UserController.ts
// ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ์ถฉ๋ถ„ํžˆ ๋ช…ํ™•ํ•จ
import { UserService } from "../services/UserService";
import { User } from "../../domain/entities/User";
API์—์„œ path mapping์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ผ๋ฐ˜์ ์œผ๋กœ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Sonamu ํŒจํ‚ค์ง€ Import

Sonamu ์ž์ฒด๋Š” path mapping ์—†์ด importํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// โœ… ์ง์ ‘ import
import { BaseModelClass, api, transactional } from "sonamu";
import { DB } from "sonamu";
import { drivers } from "sonamu/storage";
import { store } from "sonamu/cache";

// โœ… ํƒ€์ž… import
import type { Context, SonamuConfig } from "sonamu";
Sonamu ํŒจํ‚ค์ง€ ๊ตฌ์กฐ:
  • sonamu - ๋ฉ”์ธ ํŒจํ‚ค์ง€
  • sonamu/storage - ์Šคํ† ๋ฆฌ์ง€ ๋“œ๋ผ์ด๋ฒ„
  • sonamu/cache - ์บ์‹œ ๋“œ๋ผ์ด๋ฒ„
Sonamu๋Š” package.json์˜ exports ํ•„๋“œ๋กœ ์„œ๋ธŒํŒจํ‚ค์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํƒ€์ž… Import

ํƒ€์ž…๋งŒ importํ•  ๋•Œ๋Š” type ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
// โœ… ํƒ€์ž…๋งŒ import
import type { User } from "@/types/user";
import type { Context } from "sonamu";

// โœ… ๊ฐ’๊ณผ ํƒ€์ž… ํ˜ผํ•ฉ
import { DB, type Context } from "sonamu";

// โŒ ๋Ÿฐํƒ€์ž„์— ๋ถˆํ•„์š”ํ•œ import
import { User } from "@/types/user";
type import๋Š” ์ปดํŒŒ์ผ ํ›„ ์ œ๊ฑฐ๋˜์–ด ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค.

IDE ์ž๋™ ์™„์„ฑ

Path mapping์„ ์„ค์ •ํ•˜๋ฉด IDE๊ฐ€ ์ž๋™์œผ๋กœ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค.

VS Code

.vscode/settings.json
{
  "typescript.preferences.importModuleSpecifier": "non-relative"
}
์ด ์„ค์ •์œผ๋กœ ์ž๋™ import ์‹œ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

IntelliJ / WebStorm

์ž๋™์œผ๋กœ tsconfig.json์„ ์ฝ์–ด path mapping์„ ์ธ์‹ํ•ฉ๋‹ˆ๋‹ค.
์ฆ์ƒ:
  • @/... ๊ฒฝ๋กœ๊ฐ€ ๋นจ๊ฐ„ ๋ฐ‘์ค„
  • ์ž๋™ ์™„์„ฑ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Œ
ํ•ด๊ฒฐ:
  1. TypeScript ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
    VS Code: Cmd/Ctrl + Shift + P โ†’ "TypeScript: Restart TS Server"
    
  2. tsconfig.json ํ™•์ธ
    {
      "compilerOptions": {
        "baseUrl": ".",  // ํ•„์ˆ˜!
        "paths": {
          "@/*": ["./src/*"]
        }
      }
    }
    
  3. Vite ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
    # Vite ์žฌ์‹œ์ž‘
    pnpm dev
    
  4. node_modules ์žฌ์„ค์น˜
    rm -rf node_modules
    pnpm install
    

Jest ํ…Œ์ŠคํŠธ ์„ค์ •

ํ…Œ์ŠคํŠธ์—์„œ๋„ path mapping์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Jest ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
web/jest.config.ts
export default {
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
    "^@components/(.*)$": "<rootDir>/src/components/$1",
  },
};
ํ…Œ์ŠคํŠธ ํŒŒ์ผ์—์„œ ์‚ฌ์šฉ:
src/components/Button.test.tsx
import { render } from "@testing-library/react";
import { Button } from "@/components/Button";
import { formatDate } from "@/utils/format";

describe("Button", () => {
  it("renders correctly", () => {
    const { getByText } = render(<Button>Click me</Button>);
    expect(getByText("Click me")).toBeInTheDocument();
  });
});

์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ ํ•ด๊ฒฐ

์ฆ์ƒ:
Cannot find module '@/components/Button' or its corresponding type declarations.
์›์ธ:
  1. baseUrl์ด ์„ค์ •๋˜์ง€ ์•Š์Œ
  2. Vite alias๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์Œ
  3. ๊ฒฝ๋กœ ์˜คํƒ€
ํ•ด๊ฒฐ:1. tsconfig.json ํ™•์ธ
{
  "compilerOptions": {
    "baseUrl": ".",  // ๋ฐ˜๋“œ์‹œ ํ•„์š”!
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
2. vite.config.ts ํ™•์ธ
import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
3. ํŒŒ์ผ ์กด์žฌ ํ™•์ธ
ls -la src/components/Button.tsx
์ฆ์ƒ:
pnpm build  # โœ… ์„ฑ๊ณต
pnpm tsc    # โŒ ํƒ€์ž… ์—๋Ÿฌ
์›์ธ:
  • Vite๋Š” alias๋ฅผ ์ธ์‹ํ•˜์ง€๋งŒ TypeScript๋Š” ์ธ์‹ํ•˜์ง€ ๋ชปํ•จ
ํ•ด๊ฒฐ:tsconfig.json์— baseUrl๊ณผ paths ์ถ”๊ฐ€:
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
์ฆ์ƒ:
pnpm tsc    # โœ… ์„ฑ๊ณต
pnpm build  # โŒ ๋นŒ๋“œ ์—๋Ÿฌ
์›์ธ:
  • TypeScript๋Š” alias๋ฅผ ์ธ์‹ํ•˜์ง€๋งŒ Vite๋Š” ์ธ์‹ํ•˜์ง€ ๋ชปํ•จ
ํ•ด๊ฒฐ:vite.config.ts์— alias ์ถ”๊ฐ€:
import path from "path";

export default defineConfig({
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

๊ถŒ์žฅ ์‚ฌํ•ญ

๊ถŒ์žฅ ์„ค์ •:
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
์‚ฌ์šฉ ๋ฐฉ๋ฒ•:
  • @/components/* - ์ปดํฌ๋„ŒํŠธ
  • @/pages/* - ํŽ˜์ด์ง€
  • @/utils/* - ์œ ํ‹ธ๋ฆฌํ‹ฐ
  • @/hooks/* - ์ปค์Šคํ…€ ํ›…
  • @/types/* - ํƒ€์ž… ์ •์˜
ํ”ผํ•ด์•ผ ํ•  ๊ฒƒ:
  • ๋„ˆ๋ฌด ๋งŽ์€ alias (ํ˜ผ๋ž€ ๊ฐ€์ค‘)
  • ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ๋งˆ๋‹ค alias (๋ถˆํ•„์š”)

๋‹ค์Œ ๋‹จ๊ณ„