@mandujs/core 0.18.3 → 0.18.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,6 +143,9 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
143
143
  // 파일 감시 설정
144
144
  const watchers: fs.FSWatcher[] = [];
145
145
  let debounceTimer: ReturnType<typeof setTimeout> | null = null;
146
+ // 동시 빌드 방지 (#121): 빌드 중에 변경 발생 시 다음 빌드 대기
147
+ let isBuilding = false;
148
+ let pendingBuildFile: string | null = null;
146
149
 
147
150
  // 파일이 공통 디렉토리에 있는지 확인
148
151
  const isInCommonDir = (filePath: string): boolean => {
@@ -157,9 +160,30 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
157
160
  };
158
161
 
159
162
  const handleFileChange = async (changedFile: string) => {
163
+ // 동시 빌드 방지 (#121): 빌드 중이면 대기 큐에 저장
164
+ if (isBuilding) {
165
+ pendingBuildFile = changedFile;
166
+ return;
167
+ }
168
+
169
+ isBuilding = true;
170
+ try {
171
+ await _doBuild(changedFile);
172
+ } finally {
173
+ isBuilding = false;
174
+ // 빌드 중 대기 중인 파일이 있으면 즉시 처리
175
+ if (pendingBuildFile) {
176
+ const next = pendingBuildFile;
177
+ pendingBuildFile = null;
178
+ await handleFileChange(next);
179
+ }
180
+ }
181
+ };
182
+
183
+ const _doBuild = async (changedFile: string) => {
160
184
  const normalizedPath = changedFile.replace(/\\/g, "/");
161
185
 
162
- // 공통 컴포넌트 디렉토리 변경 → 전체 재빌드
186
+ // 공통 컴포넌트 디렉토리 변경 → 전체 재빌드 (targetRouteIds 없이)
163
187
  if (isInCommonDir(changedFile)) {
164
188
  console.log(`\n🔄 Common file changed: ${path.basename(changedFile)}`);
165
189
  console.log(` Rebuilding all islands...`);
@@ -223,13 +247,15 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
223
247
  const route = manifest.routes.find((r) => r.id === routeId);
224
248
  if (!route || !route.clientModule) return;
225
249
 
226
- console.log(`\n🔄 Rebuilding: ${routeId}`);
250
+ console.log(`\n🔄 Rebuilding island: ${routeId}`);
227
251
  const startTime = performance.now();
228
252
 
229
253
  try {
254
+ // 단일 island만 재빌드 (Runtime/Router/Vendor 스킵, #122)
230
255
  const result = await buildClientBundles(manifest, rootDir, {
231
256
  minify: false,
232
257
  sourcemap: true,
258
+ targetRouteIds: [routeId],
233
259
  });
234
260
 
235
261
  const buildTime = performance.now() - startTime;
@@ -286,7 +312,7 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
286
312
  console.log(`👀 Watching ${watchers.length} directories for changes...`);
287
313
  if (commonWatchDirs.size > 0) {
288
314
  const commonDirNames = Array.from(commonWatchDirs)
289
- .map(d => path.relative(rootDir, d) || ".")
315
+ .map(d => (path.relative(rootDir, d) || ".").replace(/\\/g, "/"))
290
316
  .join(", ");
291
317
  console.log(`📦 Common dirs (full rebuild): ${commonDirNames}`);
292
318
  }
@@ -428,6 +454,7 @@ export function generateHMRClientScript(port: number): string {
428
454
  let reconnectAttempts = 0;
429
455
  const maxReconnectAttempts = ${TIMEOUTS.HMR_MAX_RECONNECT};
430
456
  const reconnectDelay = ${TIMEOUTS.HMR_RECONNECT_DELAY};
457
+ const staleIslands = new Set();
431
458
 
432
459
  function connect() {
433
460
  try {
@@ -464,8 +491,9 @@ export function generateHMRClientScript(port: number): string {
464
491
  function scheduleReconnect() {
465
492
  if (reconnectAttempts < maxReconnectAttempts) {
466
493
  reconnectAttempts++;
467
- console.log('[Mandu HMR] Reconnecting... (' + reconnectAttempts + '/' + maxReconnectAttempts + ')');
468
- setTimeout(connect, reconnectDelay * reconnectAttempts);
494
+ var delay = Math.min(reconnectDelay * Math.pow(2, reconnectAttempts - 1), 30000);
495
+ console.log('[Mandu HMR] Reconnecting in ' + delay + 'ms (' + reconnectAttempts + '/' + maxReconnectAttempts + ')');
496
+ setTimeout(connect, delay);
469
497
  }
470
498
  }
471
499
 
@@ -483,6 +511,7 @@ export function generateHMRClientScript(port: number): string {
483
511
  case 'island-update':
484
512
  const routeId = message.data?.routeId;
485
513
  console.log('[Mandu HMR] Island updated:', routeId);
514
+ staleIslands.add(routeId);
486
515
 
487
516
  // 현재 페이지의 island인지 확인
488
517
  const island = document.querySelector('[data-mandu-island="' + routeId + '"]');
@@ -533,7 +562,19 @@ export function generateHMRClientScript(port: number): string {
533
562
  const overlay = document.createElement('div');
534
563
  overlay.id = 'mandu-hmr-error';
535
564
  overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.9);color:#ff6b6b;font-family:monospace;padding:40px;z-index:99999;overflow:auto;';
536
- overlay.innerHTML = '<h2 style="color:#ff6b6b;margin:0 0 20px;">🔥 Build Error</h2><pre style="white-space:pre-wrap;word-break:break-all;">' + (message || 'Unknown error') + '</pre><button onclick="this.parentElement.remove()" style="position:fixed;top:20px;right:20px;background:#333;color:#fff;border:none;padding:10px 20px;cursor:pointer;">Close</button>';
565
+ const h2 = document.createElement('h2');
566
+ h2.style.cssText = 'color:#ff6b6b;margin:0 0 20px;';
567
+ h2.textContent = '🔥 Build Error';
568
+ const pre = document.createElement('pre');
569
+ pre.style.cssText = 'white-space:pre-wrap;word-break:break-all;';
570
+ pre.textContent = message || 'Unknown error';
571
+ const btn = document.createElement('button');
572
+ btn.style.cssText = 'position:fixed;top:20px;right:20px;background:#333;color:#fff;border:none;padding:10px 20px;cursor:pointer;';
573
+ btn.textContent = 'Close';
574
+ btn.onclick = function() { overlay.remove(); };
575
+ overlay.appendChild(h2);
576
+ overlay.appendChild(pre);
577
+ overlay.appendChild(btn);
537
578
  document.body.appendChild(overlay);
538
579
  }
539
580
 
@@ -549,6 +590,22 @@ export function generateHMRClientScript(port: number): string {
549
590
  if (ws) ws.close();
550
591
  });
551
592
 
593
+ // 페이지 이동 시 stale island 감지 후 리로드 (#115)
594
+ function checkStaleIslandsOnNavigation() {
595
+ if (staleIslands.size === 0) return;
596
+ for (const id of staleIslands) {
597
+ if (document.querySelector('[data-mandu-island="' + id + '"]')) {
598
+ console.log('[Mandu HMR] Stale island detected after navigation, reloading...');
599
+ location.reload();
600
+ return;
601
+ }
602
+ }
603
+ }
604
+ window.addEventListener('popstate', checkStaleIslandsOnNavigation);
605
+ window.addEventListener('pageshow', function(e) {
606
+ if (e.persisted) checkStaleIslandsOnNavigation();
607
+ });
608
+
552
609
  // Ping 전송 (연결 유지)
553
610
  setInterval(function() {
554
611
  if (ws && ws.readyState === WebSocket.OPEN) {
@@ -111,4 +111,10 @@ export interface BundlerOptions {
111
111
  * 주의: splitting=true인 경우 청크 파일 관리가 필요합니다.
112
112
  */
113
113
  splitting?: boolean;
114
+ /**
115
+ * 빌드할 Island routeId 목록 (부분 빌드용, #122)
116
+ * - 지정 시 해당 Island만 재빌드 (Runtime/Router/Vendor 스킵)
117
+ * - 미지정 시 전체 빌드 (기본값)
118
+ */
119
+ targetRouteIds?: string[];
114
120
  }
@@ -26,7 +26,7 @@ export interface ManduConfig {
26
26
  };
27
27
  };
28
28
  guard?: {
29
- preset?: "mandu" | "fsd" | "clean" | "hexagonal" | "atomic";
29
+ preset?: "mandu" | "fsd" | "clean" | "hexagonal" | "atomic" | "cqrs";
30
30
  srcDir?: string;
31
31
  exclude?: string[];
32
32
  realtime?: boolean;
@@ -71,7 +71,7 @@ const ServerConfigSchema = z
71
71
  */
72
72
  const GuardConfigSchema = z
73
73
  .object({
74
- preset: z.enum(["mandu", "fsd", "clean", "hexagonal", "atomic"]).default("mandu"),
74
+ preset: z.enum(["mandu", "fsd", "clean", "hexagonal", "atomic", "cqrs"]).default("mandu"),
75
75
  srcDir: z.string().default("src"),
76
76
  exclude: z.array(z.string()).default([]),
77
77
  realtime: z.boolean().default(true),