๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Sonamu์˜ Hot Module Replacement (HMR) ์‹œ์Šคํ…œ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

HMR์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Œ

์ฆ์ƒ

ํŒŒ์ผ์„ ์ˆ˜์ •ํ•ด๋„ ์„œ๋ฒ„๊ฐ€ ์ž๋™์œผ๋กœ ์žฌ์‹œ์ž‘๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
# ํŒŒ์ผ ์ˆ˜์ • ํ›„ ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š์Œ

์›์ธ

  1. HMR ๊ฒฝ๊ณ„(boundary)๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์ง€ ์•Š์Œ
  2. ํŒŒ์ผ ๊ฐ์‹œ์ž๊ฐ€ ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•จ
  3. ์ˆœํ™˜ ์˜์กด์„ฑ์œผ๋กœ ์ธํ•œ HMR ์‹คํŒจ

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

1. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์žฌ์‹œ์ž‘

# Ctrl+C๋กœ ์ค‘๋‹จ ํ›„ ์žฌ์‹œ์ž‘
pnpm dev

2. HMR ๊ฒฝ๊ณ„ ํ™•์ธ

HMR ๊ฒฝ๊ณ„๋Š” hmr-hook-register.ts์—์„œ ์„ค์ •๋ฉ๋‹ˆ๋‹ค:
// src/bin/hmr-hook-register.ts
if (process.env.HOT === "yes" && process.env.API_ROOT_PATH) {
  const { hot } = await import("@sonamu-kit/hmr-hook");

  await hot.init({
    rootDirectory: process.env.API_ROOT_PATH,
    boundaries: [`./src/**/*.ts`],  // ๋ชจ๋“  .ts ํŒŒ์ผ
  });

  console.log("๐Ÿ”ฅ HMR-hook initialized");
}
HMR ๊ฒฝ๊ณ„๋ฅผ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด ์ด ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์„ธ์š”.

3. ํŒŒ์ผ ๊ฐ์‹œ์ž ํ•œ๋„ ์ฆ๊ฐ€ (Linux/macOS)

# Linux
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# macOS - ์ผ๋ฐ˜์ ์œผ๋กœ ํ•„์š” ์—†์Œ

4. .gitignore ํŒจํ„ด ํ™•์ธ

HMR์ด node_modules๋‚˜ dist ๊ฐ™์€ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ฐ์‹œํ•˜๋ฉด ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŒŒ์ผ ๋ณ€๊ฒฝ ํ›„ ์—๋Ÿฌ ๋ฐœ์ƒ

์ฆ์ƒ

ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜๋ฉด HMR์ด ๋™์ž‘ํ•˜์ง€๋งŒ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค:
Error: Cannot import module /src/application/user/user.model.ts

์›์ธ

  1. ์ˆœํ™˜ ์˜์กด์„ฑ (circular dependency)
  2. ๋™์  import๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์Œ
  3. ESM/CommonJS ํ˜ผ์šฉ

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

1. ์ˆœํ™˜ ์˜์กด์„ฑ ํ™•์ธ

# madge ์„ค์น˜
pnpm add -D madge

# ์ˆœํ™˜ ์˜์กด์„ฑ ๊ฒ€์‚ฌ
pnpm madge --circular --extensions ts src/
๋ฐœ๊ฒฌ๋œ ์ˆœํ™˜ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜์„ธ์š”:
// โŒ ์ˆœํ™˜ ์˜์กด์„ฑ
// user.model.ts
import { PostModel } from "../post/post.model";

// post.model.ts
import { UserModel } from "../user/user.model";

// โœ… ํ•ด๊ฒฐ: ํƒ€์ž…๋งŒ import
// user.model.ts
import type { PostModel } from "../post/post.model";

// ๋˜๋Š” ๊ณตํ†ต ํƒ€์ž… ํŒŒ์ผ ์ƒ์„ฑ
// types.ts
export type { UserModel } from "./user/user.model";
export type { PostModel } from "./post/post.model";

2. ๋™์  import ํ™•์ธ

HMR ๊ฒฝ๊ณ„ ๋‚ด์—์„œ๋Š” ๋™์  import๋ฅผ ํ”ผํ•˜์„ธ์š”:
// โŒ HMR ๊ฒฝ๊ณ„ ๋‚ด์—์„œ ๋™์  import
class UserModelClass extends BaseModel {
  async getRelated() {
    const { PostModel } = await import("../post/post.model");
    return PostModel.findMany();
  }
}

// โœ… ์ •์  import ์‚ฌ์šฉ
import { PostModel } from "../post/post.model";

class UserModelClass extends BaseModel {
  async getRelated() {
    return PostModel.findMany();
  }
}

HMR ํ›„ ํƒ€์ž… ์—๋Ÿฌ

์ฆ์ƒ

HMR์ด ๋™์ž‘ํ•œ ํ›„ TypeScript ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค:
Property 'newMethod' does not exist on type 'UserModel'

์›์ธ

TypeScript ์„œ๋ฒ„๊ฐ€ HMR ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

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

1. TypeScript ์„œ๋ฒ„ ์žฌ์‹œ์ž‘

VSCode:
Command Palette (Cmd+Shift+P ๋˜๋Š” Ctrl+Shift+P)
> TypeScript: Restart TS Server

2. ์—๋””ํ„ฐ ์žฌ์‹œ์ž‘

Command Palette
> Developer: Reload Window

3. tsconfig.json ํ™•์ธ

{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo"
  }
}

HMR ์„ฑ๋Šฅ ์ €ํ•˜

์ฆ์ƒ

ํŒŒ์ผ์„ ์ €์žฅํ•  ๋•Œ๋งˆ๋‹ค HMR์ด ๋А๋ฆฌ๊ฑฐ๋‚˜ ์„œ๋ฒ„๊ฐ€ ๋ฉˆ์ถฅ๋‹ˆ๋‹ค.

์›์ธ

  1. ๋„ˆ๋ฌด ๋งŽ์€ ํŒŒ์ผ์ด HMR ๊ฒฝ๊ณ„์— ํฌํ•จ๋จ
  2. ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์ด ๋ชจ๋“ˆ ๋กœ๋”ฉ ์‹œ์ ์— ์‹คํ–‰๋จ
  3. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

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

1. HMR ๊ฒฝ๊ณ„ ์ตœ์ ํ™”

hmr-hook-register.ts์—์„œ ๊ฒฝ๊ณ„ ํŒจํ„ด ์ˆ˜์ •:
// src/bin/hmr-hook-register.ts
if (process.env.HOT === "yes" && process.env.API_ROOT_PATH) {
  const { hot } = await import("@sonamu-kit/hmr-hook");

  await hot.init({
    rootDirectory: process.env.API_ROOT_PATH,
    boundaries: [
      // ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ํŒŒ์ผ๋งŒ ํฌํ•จ
      `./src/application/**/*.model.ts`,
      `./src/application/**/*.helper.ts`
    ],
  });
}

2. ๋ชจ๋“ˆ ์ดˆ๊ธฐํ™” ์ตœ์ ํ™”

๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์€ lazy loading์œผ๋กœ:
// โŒ ๋ชจ๋“ˆ ๋กœ๋”ฉ ์‹œ ์ฆ‰์‹œ ์‹คํ–‰
const heavyData = processLargeDataset();

export class UserModelClass extends BaseModel {
  // ...
}

// โœ… ํ•„์š”ํ•  ๋•Œ๋งŒ ์‹คํ–‰
let heavyData: any;

export class UserModelClass extends BaseModel {
  private getHeavyData() {
    if (!heavyData) {
      heavyData = processLargeDataset();
    }
    return heavyData;
  }
}

3. ๋ฉ”๋ชจ๋ฆฌ ํ”„๋กœํŒŒ์ผ๋ง

# Node.js ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ
node --expose-gc --inspect node_modules/.bin/sonamu dev

# Chrome DevTools์—์„œ ๋ฉ”๋ชจ๋ฆฌ ํ”„๋กœํŒŒ์ผ๋ง
chrome://inspect

HMR ๊ฒฝ๊ณ„ ์„ค์ • ์˜ค๋ฅ˜

์ฆ์ƒ

Error: HMR boundary pattern is invalid: /src/**/*.model.ts

์›์ธ

HMR ๊ฒฝ๊ณ„ ํŒจํ„ด์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

hmr-hook-register.ts์—์„œ ์˜ฌ๋ฐ”๋ฅธ glob ํŒจํ„ด ์‚ฌ์šฉ:
// src/bin/hmr-hook-register.ts
await hot.init({
  rootDirectory: process.env.API_ROOT_PATH,
  boundaries: [
    // โœ… ์˜ฌ๋ฐ”๋ฅธ ํŒจํ„ด (์ƒ๋Œ€ ๊ฒฝ๋กœ)
    `./src/application/**/*.model.ts`,
    `./src/application/**/*.helper.ts`,
    
    // โŒ ์ž˜๋ชป๋œ ํŒจํ„ด
    // "/src/**/*.ts",  // ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์Œ
    // "./src/**/*.{ts,js}",  // brace expansion ๋ฏธ์ง€์›
  ],
});

ํŠน์ • ํŒŒ์ผ๋งŒ HMR์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Œ

์ฆ์ƒ

๋Œ€๋ถ€๋ถ„์˜ ํŒŒ์ผ์€ HMR์ด ์ž˜ ์ž‘๋™ํ•˜๋Š”๋ฐ, ํŠน์ • ํŒŒ์ผ๋งŒ ๋ณ€๊ฒฝ ์‹œ ์žฌ์‹œ์ž‘๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์›์ธ

  1. ํŒŒ์ผ์ด HMR ๊ฒฝ๊ณ„ ํŒจํ„ด๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์Œ
  2. ํŒŒ์ผ์ด ๋ช…์‹œ์ ์œผ๋กœ ์ œ์™ธ๋จ
  3. ํŒŒ์ผ์ด ๋‹ค๋ฅธ ๋ชจ๋“ˆ์—์„œ ์บ์‹œ๋จ

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

1. ํŒŒ์ผ์ด ๊ฒฝ๊ณ„์— ํฌํ•จ๋˜๋Š”์ง€ ํ™•์ธ

# HMR ๋กœ๊น… ํ™œ์„ฑํ™”
DEBUG=hmr:* pnpm dev
ํŒŒ์ผ ์ €์žฅ ์‹œ ๋กœ๊ทธ ํ™•์ธ:
hmr:boundary /src/application/user/user.model.ts matched +0ms
hmr:reload Reloading module: user.model +5ms

2. ์บ์‹œ ํด๋ฆฌ์–ด

// ๋ชจ๋“ˆ ์บ์‹œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์„œ๋ฒ„ ์žฌ์‹œ์ž‘

3. ํŒŒ์ผ ์ด๋ฆ„ ํŒจํ„ด ํ™•์ธ

// HMR ๊ฒฝ๊ณ„์— ๋งž๊ฒŒ ํŒŒ์ผ ์ด๋ฆ„ ๋ณ€๊ฒฝ
// user-service.ts โ†’ user.helper.ts

ESM/CommonJS ํ˜ผ์šฉ ๋ฌธ์ œ

์ฆ์ƒ

Error [ERR_REQUIRE_ESM]: require() of ES Module not supported

์›์ธ

HMR ์‹œ์Šคํ…œ์ด ESM ๋ชจ๋“ˆ์„ CommonJS๋กœ ๋กœ๋”ฉํ•˜๋ ค ํ–ˆ์Šต๋‹ˆ๋‹ค.

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

1. package.json ์„ค์ •

{
  "type": "module"
}

2. ํŒŒ์ผ ํ™•์žฅ์ž ๋ช…์‹œ

// โŒ
import { UserModel } from "./user.model";

// โœ…
import { UserModel } from "./user.model.js";  // ESM์—์„œ๋Š” .js ์‚ฌ์šฉ

3. tsconfig.json ์„ค์ •

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler"
  }
}

๊ด€๋ จ ๋ฌธ์„œ