๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
HMR ์‚ฌ์šฉ ์ค‘ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์™€ ์‹ค์ „ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š์Œ

์ƒํ™ฉ

// user.model.ts ์ˆ˜์ •
export class UserModel extends BaseModel {
  isActive(): boolean {
    return this.status === "active";  // ์ด ๋กœ์ง์„ ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ...
  }
}
์ €์žฅํ–ˆ์ง€๋งŒ API ํ˜ธ์ถœ ์‹œ ์—ฌ์ „ํžˆ isActive is not a function ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์›์ธ 1: ์ •์  Import ์‚ฌ์šฉ

์–ด๋”˜๊ฐ€์—์„œ UserModel์„ ์ •์ ์œผ๋กœ importํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค. ํ™•์ธ ๋ฐฉ๋ฒ•:
# ํ”„๋กœ์ ํŠธ์—์„œ ์ •์  import ์ฐพ๊ธฐ
grep -r "import.*UserModel.*from" src/

# ์ถœ๋ ฅ ์˜ˆ์‹œ:
# src/some-file.ts:import { UserModel } from "./user.model";  โ† ๐Ÿšจ ์ด๊ฒŒ ๋ฌธ์ œ!
ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: ๋™์  import๋กœ ๋ณ€๊ฒฝ
// Before - โŒ
import { UserModel } from "./application/user/user.model";

app.get("/users", async (req, res) => {
  const users = await UserModel.findMany();
  res.json(users);
});

// After - โœ…
app.get("/users", async (req, res) => {
  const { UserModel } = await import("./application/user/user.model");
  const users = await UserModel.findMany();
  res.json(users);
});
Sonamu์˜ Syncer๋Š” Entity ๊ธฐ๋ฐ˜ ํŒŒ์ผ๋“ค์„ ์ž๋™์œผ๋กœ ๋™์  importํ•˜๋ฏ€๋กœ, ์ผ๋ฐ˜์ ์ธ Model/API ํŒŒ์ผ์—์„œ๋Š” ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฃผ๋กœ ์ปค์Šคํ…€ ์Šคํฌ๋ฆฝํŠธ๋‚˜ ์ดˆ๊ธฐํ™” ์ฝ”๋“œ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์›์ธ 2: ์ฝ˜์†”์— ์—๋Ÿฌ๊ฐ€ ์ˆจ์–ด์žˆ์Œ

HMR ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์ง€๋งŒ ๋†“์ณค์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ™•์ธ ๋ฐฉ๋ฒ•: ํ„ฐ๋ฏธ๋„์„ ์Šคํฌ๋กคํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ:
๐Ÿ”„ Invalidated:
- src/user/user.model.ts

โŒ Error loading module: /path/to/user.model.ts
SyntaxError: Unexpected token '}'
    at Module._compile (internal/modules/cjs/loader.js:1137:14)
ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์˜ ํŒŒ์ผ๊ณผ ๋ผ์ธ ๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜๊ณ  ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

์›์ธ 3: ์บ์‹œ๊ฐ€ ๊ผฌ์ž„

๋“œ๋ฌผ์ง€๋งŒ ESM ์บ์‹œ๊ฐ€ ์™„์ „ํžˆ ์ œ๊ฑฐ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•: ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
# Ctrl+C๋กœ ์ข…๋ฃŒ ํ›„
pnpm dev

โ€File not imported dynamicallyโ€ ์—๋Ÿฌ

์ƒํ™ฉ

FileNotImportedDynamicallyException: File /path/to/user.model.ts must be imported dynamically

์›์ธ

Boundary ํŒŒ์ผ(user.model.ts)์ด ์–ด๋”˜๊ฐ€์—์„œ ์ •์  import๋กœ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋น ๋ฅธ ํ•ด๊ฒฐ

์˜ต์…˜ 1: ์ •์  import๋ฅผ ๋™์  import๋กœ ๋ณ€๊ฒฝ (๊ถŒ์žฅ) ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์— ๋‚˜์˜จ ํŒŒ์ผ์„ ์ฐพ์•„์„œ:
// Before
import { UserModel } from "./user.model";

// After
const { UserModel } = await import("./user.model");
์˜ต์…˜ 2: ํ•ด๋‹น ํŒŒ์ผ์„ Boundary์—์„œ ์ œ์™ธ ์ •๋ง ๋™์  import๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ:
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    `./src/**/*.ts`,
    `!./src/config/**/*`,  // config ํด๋” ์ œ์™ธ
  ],
});
์˜ต์…˜ 3: ์—๋Ÿฌ๋ฅผ ๋ฌด์‹œ (๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ)
await hot.init({
  boundaries: [`./src/**/*.ts`],
  throwWhenBoundariesAreNotDynamicallyImported: false,  // ์—๋Ÿฌ ๋ฌด์‹œ
});
์˜ต์…˜ 3์„ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ํŒŒ์ผ์˜ HMR์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ์ด ํฌ๊ฒŒ ๋–จ์–ด์ง€๋ฏ€๋กœ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์žฌ๋กœ๋“œ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ฆ๊ฐ€

์ƒํ™ฉ

# ์ฒ˜์Œ
RSS: 150MB, Heap: 80MB

# ํŒŒ์ผ์„ 10๋ฒˆ ์ˆ˜์ • ํ›„
RSS: 350MB, Heap: 200MB  # ๐Ÿšจ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๊ณ„์† ์ฆ๊ฐ€!

์›์ธ

์ด์ „ ๋ชจ๋“ˆ์˜ ๋ฆฌ์†Œ์Šค(ํƒ€์ด๋จธ, ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ, ์—ฐ๊ฒฐ ๋“ฑ)๊ฐ€ ์ •๋ฆฌ๋˜์ง€ ์•Š์•„ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ™•์ธ ๋ฐฉ๋ฒ•:
// ์žฌ๋กœ๋“œํ•  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰
console.log("Active handles:", process._getActiveHandles().length);
console.log("Active requests:", process._getActiveRequests().length);

// ์ˆซ์ž๊ฐ€ ๊ณ„์† ์ฆ๊ฐ€ํ•˜๋ฉด ๋ฆฌ์†Œ์Šค ๋ˆ„์ˆ˜ ๋ฐœ์ƒ ์ค‘!

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

import.meta.hot.dispose()๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค: ์˜ˆ์‹œ 1: ํƒ€์ด๋จธ
// notification-polling.ts
const timer = setInterval(async () => {
  await checkNewNotifications();
}, 5000);

// โœ… ์žฌ๋กœ๋“œ ์ „ ํƒ€์ด๋จธ ์ •๋ฆฌ
import.meta.hot?.dispose(() => {
  clearInterval(timer);
  console.log("โœจ Timer cleaned up");
});
์˜ˆ์‹œ 2: WebSocket
// websocket-client.ts
const ws = new WebSocket("ws://notification-server.com");

ws.on("message", (data) => {
  console.log("Notification:", data);
});

// โœ… ์žฌ๋กœ๋“œ ์ „ ์—ฐ๊ฒฐ ์ข…๋ฃŒ
import.meta.hot?.dispose(() => {
  ws.close();
  console.log("โœจ WebSocket closed");
});
์˜ˆ์‹œ 3: ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
// event-handler.ts
import { EventEmitter } from "events";

const emitter = new EventEmitter();

function handleData(data: any) {
  console.log("Data received:", data);
}

emitter.on("data", handleData);

// โœ… ์žฌ๋กœ๋“œ ์ „ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ
import.meta.hot?.dispose(() => {
  emitter.removeListener("data", handleData);
  console.log("โœจ Event listener removed");
});
์˜ˆ์‹œ 4: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜ ํ’€
// custom-db-pool.ts
import { Pool } from "pg";

const pool = new Pool({
  host: "localhost",
  port: 5432,
  database: "mydb",
});

// โœ… ์žฌ๋กœ๋“œ ์ „ ํ’€ ์ข…๋ฃŒ
import.meta.hot?.dispose(async () => {
  await pool.end();
  console.log("โœจ DB pool closed");
});

API๊ฐ€ ์ค‘๋ณต ๋“ฑ๋ก๋จ

์ƒํ™ฉ

โš ๏ธ Warning: Route POST /api/user/create is already registered
โš ๏ธ Warning: Route GET /api/user/list is already registered
์„œ๋ฒ„ ๋กœ๊ทธ์— ๊ฐ™์€ API๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ๋“ฑ๋ก๋˜์—ˆ๋‹ค๋Š” ๊ฒฝ๊ณ ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

์›์ธ

Model ํŒŒ์ผ ์žฌ๋กœ๋“œ ์‹œ ๊ธฐ์กด API๊ฐ€ ์ œ๊ฑฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ™•์ธ

Syncer ๋กœ๊ทธ์—์„œ API ์ œ๊ฑฐ ์—ฌ๋ถ€ ํ™•์ธ:
๐Ÿ”„ Invalidated:
- src/user/user.model.ts (with 8 APIs)  # โ† API ๊ฐœ์ˆ˜๊ฐ€ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•จ

# ๋งŒ์•ฝ "with X APIs"๊ฐ€ ์—†๋‹ค๋ฉด ์ œ๊ฑฐ๋˜์ง€ ์•Š์€ ๊ฒƒ!

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ผ๋ฐ˜์ ์œผ๋กœ Syncer๊ฐ€ ์ž๋™ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ, ๋ฌธ์ œ๊ฐ€ ๊ณ„์†๋˜๋ฉด: 1. ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
# ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•
Ctrl+C
pnpm dev
2. Syncer ๋กœ์ง ํ™•์ธ (๋“œ๋ฌธ ๊ฒฝ์šฐ)
// syncer.ts ํ™•์ธ
removeInvalidatedRegisteredApis(invalidatedPath: AbsolutePath) {
  if (!invalidatedPath.endsWith(".model.ts")) {
    return [];
  }

  const entityId = EntityManager.getEntityIdFromPath(invalidatedPath);
  const toRemove = registeredApis.filter(
    api => api.modelName === `${entityId}Model`
  );
  
  for (const api of toRemove) {
    registeredApis.splice(registeredApis.indexOf(api), 1);
  }
  
  return toRemove;
}
์ด ๋กœ์ง์ด ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด Sonamu ๋ฒ„๊ทธ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. GitHub Issues์— ๋ณด๊ณ ํ•ด์ฃผ์„ธ์š”.

์ˆœํ™˜ ์˜์กด์„ฑ ๋ฌธ์ œ

์ƒํ™ฉ

# ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜๋ฉด
๐Ÿ”„ Invalidated:
- src/user/user.model.ts
- src/post/post.model.ts
- src/user/user.model.ts  # โ† ๐Ÿšจ ๋‹ค์‹œ ๋“ฑ์žฅ!
- src/post/post.model.ts  # โ† ๐Ÿšจ ๋ฌดํ•œ ๋ฐ˜๋ณต!

# ๋˜๋Š”
RangeError: Maximum call stack size exceeded
HMR์ด ๋ฌดํ•œ ๋ฃจํ”„์— ๋น ์ง€๊ฑฐ๋‚˜ ์ผ๋ถ€ ๋ชจ๋“ˆ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์›์ธ

Model ๊ฐ„ ์ˆœํ™˜ ์ฐธ์กฐ:
// user.model.ts
import { PostModel } from "../post/post.model";

export class UserModel extends BaseModel {
  async getPosts() {
    return PostModel.findMany({ where: { userId: this.id } });
  }
}

// post.model.ts  
import { UserModel } from "../user/user.model";  // โ† ๐Ÿšจ ์ˆœํ™˜ ์ฐธ์กฐ!

export class PostModel extends BaseModel {
  async getAuthor() {
    return UserModel.findById(this.userId);
  }
}

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์˜ต์…˜ 1: ํƒ€์ž…๋งŒ import (๊ถŒ์žฅ)
// user.model.ts
import type { PostSaved } from "../post/post.types";  // โœ… ํƒ€์ž…๋งŒ

export class UserModel extends BaseModel {
  async getPosts(): Promise<PostSaved[]> {
    // ๋™์  import๋กœ ์‹ค์ œ ํด๋ž˜์Šค ๋กœ๋“œ
    const { PostModel } = await import("../post/post.model");
    return PostModel.findMany({ where: { userId: this.id } });
  }
}

// post.model.ts
import type { UserSaved } from "../user/user.types";  // โœ… ํƒ€์ž…๋งŒ

export class PostModel extends BaseModel {
  async getAuthor(): Promise<UserSaved> {
    const { UserModel } = await import("../user/user.model");
    return UserModel.findById(this.userId);
  }
}
์˜ต์…˜ 2: ๊ณตํ†ต ํƒ€์ž… ํŒŒ์ผ ๋ถ„๋ฆฌ
// shared-types.ts
export type UserSaved = { /* ... */ };
export type PostSaved = { /* ... */ };

// user.model.ts
import type { PostSaved } from "./shared-types";

// post.model.ts
import type { UserSaved } from "./shared-types";
์˜ต์…˜ 3: ์˜์กด์„ฑ ๋ฐฉํ–ฅ ์žฌ์„ค๊ณ„
// post.model.ts์—์„œ๋งŒ user.model.ts๋ฅผ ์ฐธ์กฐ
import { UserModel } from "../user/user.model";  // โœ… ๋‹จ๋ฐฉํ–ฅ

export class PostModel extends BaseModel {
  async getAuthor() {
    return UserModel.findById(this.userId);
  }
}

// user.model.ts์—์„œ๋Š” post.model.ts๋ฅผ ์ฐธ์กฐํ•˜์ง€ ์•Š์Œ
// (ํ•„์š”ํ•˜๋ฉด PostApi์—์„œ ์ฒ˜๋ฆฌ)

ํŠน์ • ํŒŒ์ผ๋งŒ HMR์ด ์•ˆ ๋จ

์ƒํ™ฉ

// user.model.ts ์ˆ˜์ • โ†’ โœ… HMR ์ž‘๋™
// post.model.ts ์ˆ˜์ • โ†’ โœ… HMR ์ž‘๋™
// admin-helper.ts ์ˆ˜์ • โ†’ โŒ ๋ฐ˜์˜ ์•ˆ ๋จ

์›์ธ 1: Boundary ํŒจํ„ด์— ๋งค์นญ๋˜์ง€ ์•Š์Œ

ํ™•์ธ:
const dump = await hot.dump();
const helper = dump.find(d => d.nodePath.includes("admin-helper.ts"));

if (!helper) {
  console.log("โŒ ์ด ํŒŒ์ผ์€ Boundary๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค!");
}
ํ•ด๊ฒฐ:
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    `./src/**/*.ts`,  // ์ด๋ฏธ ํฌํ•จ๋˜์–ด ์žˆ์–ด์•ผ ํ•จ
  ],
});

์›์ธ 2: ํŒŒ์ผ์ด import๋˜์ง€ ์•Š์Œ

HMR์€ ์‹ค์ œ๋กœ import๋œ ํŒŒ์ผ๋งŒ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. ํ™•์ธ:
const dump = await hot.dump();
console.log(`Total tracked files: ${dump.length}`);

// admin-helper.ts๊ฐ€ ๋ชฉ๋ก์— ์—†๋‹ค๋ฉด ์•„๋ฌด๋„ importํ•˜์ง€ ์•Š๋Š” ๊ฒƒ!
ํ•ด๊ฒฐ: ํ•ด๋‹น ํŒŒ์ผ์„ ์–ด๋”˜๊ฐ€์—์„œ importํ•˜๊ฑฐ๋‚˜, ํ•„์š” ์—†๋‹ค๋ฉด ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

SSR ํŒŒ์ผ ๋ณ€๊ฒฝ ์‹œ ์žฌ๋กœ๋“œ ์•ˆ ๋จ

์ƒํ™ฉ

// src/ssr/routes.ts ์ˆ˜์ •
// ์ €์žฅํ•ด๋„ ๋ฐ˜์˜ ์•ˆ ๋จ

์›์ธ

SSR ํŒŒ์ผ์€ ํŠน๋ณ„ํžˆ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ

Syncer๊ฐ€ SSR ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ, ์ˆ˜๋™ ์žฌ๋กœ๋“œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ:
# ์„œ๋ฒ„ ์žฌ์‹œ์ž‘
Ctrl+C
pnpm dev
๋˜๋Š” ์ฝ”๋“œ์—์„œ:
// syncer.ts์—์„œ SSR ํŒŒ์ผ ์ฒ˜๋ฆฌ ํ™•์ธ
if (diffFilePath.includes("/src/ssr/")) {
  console.log("SSR config changed - reloading...");
  await hot.invalidateFile(diffFilePath, event);
  await this.autoloadSSRRoutes();
}

HMR์ด ๋„ˆ๋ฌด ๋А๋ฆผ

์ƒํ™ฉ

# ํŒŒ์ผ ์ €์žฅ
# 5์ดˆ ํ›„...
๐Ÿ”„ Invalidated:  # โ† ๐Ÿšจ ๋„ˆ๋ฌด ๋А๋ฆผ!

์›์ธ

์˜์กด์„ฑ ํŠธ๋ฆฌ๊ฐ€ ๋„ˆ๋ฌด ๊นŠ๊ฑฐ๋‚˜ ๋„“์–ด์„œ ๋งŽ์€ ํŒŒ์ผ์ด ๋ฌดํšจํ™”๋ฉ๋‹ˆ๋‹ค. ํ™•์ธ:
const dump = await hot.dump();
const userModel = dump.find(d => d.nodePath.includes("user.model.ts"));

console.log(`Dependencies: ${userModel?.children?.length}`);
// 100๊ฐœ ์ด์ƒ์ด๋ฉด ๋ฌธ์ œ!

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

1. import ์ตœ์†Œํ™”
// โŒ ๋‚˜์œ ์˜ˆ - ๋ชจ๋“  Model import
import { UserModel } from "./user.model";
import { PostModel } from "./post.model";
import { CommentModel } from "./comment.model";
import { CategoryModel } from "./category.model";
import { TagModel } from "./tag.model";
// ... 20๊ฐœ ๋”

// โœ… ์ข‹์€ ์˜ˆ - ํ•„์š”ํ•œ ๊ฒƒ๋งŒ
import { UserModel } from "./user.model";
import { PostModel } from "./post.model";
2. lodash ๋“ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ import ์ตœ์ ํ™”
// โŒ ๋‚˜์œ ์˜ˆ
import * as lodash from "lodash";

// โœ… ์ข‹์€ ์˜ˆ
import { pick, omit } from "lodash";
3. Boundary ๋ฒ”์œ„ ์ถ•์†Œ
// hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    // ๋ชจ๋“  ํŒŒ์ผ ๋Œ€์‹  ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ๋งŒ
    `./src/**/*.model.ts`,
    `./src/**/*.api.ts`,
  ],
});

๋””๋ฒ„๊น… ํŒ

HMR ๋กœ๊ทธ ํ™œ์„ฑํ™”

์ƒ์„ธํ•œ HMR ๋™์ž‘์„ ํ™•์ธํ•˜๋ ค๋ฉด:
DEBUG=hmr-hook:* pnpm dev
์ถœ๋ ฅ ์˜ˆ์‹œ:
hmr-hook:loader File change src/user/user.model.ts
hmr-hook:dependency_tree Invalidating /path/to/user.model.ts
hmr-hook:dependency_tree Finding dependents...
hmr-hook:dependency_tree Found 3 dependent files
hmr-hook:loader Invalidated 3 files

์˜์กด์„ฑ ํŠธ๋ฆฌ ํ™•์ธ

const dump = await hot.dump();

// ํŠน์ • ํŒŒ์ผ์˜ ์˜์กด์„ฑ ํ™•์ธ
const userModel = dump.find(d => d.nodePath.includes("user.model.ts"));
console.log("Children:", userModel?.children);

// ์ „์ฒด ํŠธ๋ฆฌ๋ฅผ JSON์œผ๋กœ ์ €์žฅ
import { writeFileSync } from "fs";
writeFileSync("dependency-tree.json", JSON.stringify(dump, null, 2));

๋ฌดํšจํ™”๋œ ํŒŒ์ผ ์ถ”์ 

Syncer๊ฐ€ ์ฝ˜์†”์— ์ถœ๋ ฅํ•˜๋Š” ๋กœ๊ทธ๋ฅผ ์ฃผ์˜๊นŠ๊ฒŒ ํ™•์ธ:
๐Ÿ”„ Invalidated:
- src/user/user.model.ts (with 8 APIs)
- src/user/user.api.ts
- src/post/post.api.ts  # โ† ์™œ post.api.ts๊นŒ์ง€?

# user.model.ts๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”๋ฐ post.api.ts๋„ ์žฌ๋กœ๋“œ๋จ
# โ†’ post.api.ts์—์„œ UserModel์„ ์‚ฌ์šฉ ์ค‘

๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ชจ๋‹ˆํ„ฐ๋ง

// memory-monitor.ts
setInterval(() => {
  const used = process.memoryUsage();
  const timestamp = new Date().toISOString();
  
  console.log(`[${timestamp}] Memory:`, {
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    external: `${Math.round(used.external / 1024 / 1024)}MB`,
  });
}, 10000);  // 10์ดˆ๋งˆ๋‹ค

๋งˆ์ง€๋ง‰ ์ˆ˜๋‹จ: ์™„์ „ ์žฌ์‹œ์ž‘

๋ชจ๋“  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ด ์‹คํŒจํ•œ ๊ฒฝ์šฐ:
# 1. ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ
Ctrl+C

# 2. ๋นŒ๋“œ ๋””๋ ‰ํ† ๋ฆฌ ์‚ญ์ œ
rm -rf dist/

# 3. ์ฒดํฌ์„ฌ ์‚ญ์ œ
rm -rf .sonamu/checksums/

# 4. node_modules ์‚ญ์ œ (์ •๋ง ์‹ฌ๊ฐํ•œ ๊ฒฝ์šฐ)
rm -rf node_modules/
pnpm install

# 5. ์žฌ์‹œ์ž‘
pnpm dev

๋„์›€ ์š”์ฒญํ•˜๊ธฐ

์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€ ์•Š์œผ๋ฉด:

1. GitHub Issues์— ๋ณด๊ณ ํ•˜์„ธ์š”

์•„๋ž˜ ๋งํฌ์—์„œ ์ƒˆ๋กœ์šด ์ด์Šˆ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”: https://github.com/cartanova-ai/sonamu/issues

2. ๋‹ค์Œ ์ •๋ณด๋ฅผ ํฌํ•จํ•ด์ฃผ์„ธ์š”

๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•ด ๋‹ค์Œ ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”:
  • Sonamu ๋ฒ„์ „ (package.json)
  • Node.js ๋ฒ„์ „ (node -v)
  • ๋ฐœ์ƒํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
  • ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ ์˜ˆ์ œ
  • HMR ๋กœ๊ทธ (DEBUG=hmr-hook:* pnpm dev)

3. ํŒŒ์ผ ๊ตฌ์กฐ ์˜ˆ์‹œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”

๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ํŒŒ์ผ ๊ตฌ์กฐ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œ์‹œํ•ด์ฃผ์„ธ์š”:

์˜ˆ์‹œ ์„ค๋ช…์„ ํ•จ๊ป˜ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”:
์ฆ์ƒ:
- user.model.ts๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์ €์žฅ
- ์ฝ˜์†”์— "Invalidated: user.model.ts" ํ‘œ์‹œ๋จ
- ํ•˜์ง€๋งŒ user.api.ts์—์„œ ์—ฌ์ „ํžˆ ์ด์ „ ์ฝ”๋“œ ์‹คํ–‰
- ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ํ•˜๋ฉด ์ •์ƒ ์ž‘๋™

์‹œ๋„ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:
1. ๋™์  import ํ™•์ธ โ†’ ์ด๋ฏธ ๋™์ ์œผ๋กœ ๋กœ๋“œ๋จ
2. ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ โ†’ ์ž„์‹œ๋กœ ํ•ด๊ฒฐ๋˜์ง€๋งŒ ๋‹ค์‹œ ๋ฐœ์ƒ
3. ์บ์‹œ ์‚ญ์ œ (rm -rf dist/) โ†’ ํšจ๊ณผ ์—†์Œ

HMR ๋กœ๊ทธ:
[๋กœ๊ทธ ๋‚ด์šฉ ์ฒจ๋ถ€]
์ด๋ ‡๊ฒŒ ์ƒ์„ธํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด ๋น ๋ฅธ ๋‹ต๋ณ€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!