@mandujs/core 0.18.6 → 0.18.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.18.6",
3
+ "version": "0.18.8",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -103,8 +103,18 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
103
103
  const normalizedPath = absPath.replace(/\\/g, "/");
104
104
  clientModuleToRoute.set(normalizedPath, route.id);
105
105
 
106
- // 감시할 디렉토리 추가
106
+ // Also register *.client.tsx/ts files in the same directory (#140)
107
+ // e.g. if clientModule is app/page.island.tsx, also map app/page.client.tsx → same routeId
107
108
  const dir = path.dirname(absPath);
109
+ const baseStem = path.basename(absPath).replace(/\.(island|client)\.(tsx?|jsx?)$/, "");
110
+ for (const ext of [".client.tsx", ".client.ts", ".client.jsx", ".client.js"]) {
111
+ const clientPath = path.join(dir, baseStem + ext).replace(/\\/g, "/");
112
+ if (clientPath !== normalizedPath) {
113
+ clientModuleToRoute.set(clientPath, route.id);
114
+ }
115
+ }
116
+
117
+ // 감시할 디렉토리 추가
108
118
  watchDirs.add(dir);
109
119
  }
110
120
  }
@@ -224,21 +234,17 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
224
234
  // clientModule 매핑에서 routeId 찾기
225
235
  let routeId = clientModuleToRoute.get(normalizedPath);
226
236
 
227
- // .client.ts 또는 .client.tsx 파일인 경우 파일명에서 routeId 추출
228
- if (!routeId) {
229
- let basename: string | null = null;
230
-
231
- if (changedFile.endsWith(".client.ts")) {
232
- basename = path.basename(changedFile, ".client.ts");
233
- } else if (changedFile.endsWith(".client.tsx")) {
234
- basename = path.basename(changedFile, ".client.tsx");
235
- }
236
-
237
- if (basename) {
238
- const route = manifest.routes.find((r) => r.id === basename);
239
- if (route) {
240
- routeId = route.id;
241
- }
237
+ // Fallback for *.client.tsx/ts: find route whose clientModule is in the same directory (#140)
238
+ // basename matching (e.g. "page" !== "index") is unreliable — use directory-based matching instead
239
+ if (!routeId && (changedFile.endsWith(".client.ts") || changedFile.endsWith(".client.tsx"))) {
240
+ const changedDir = path.dirname(path.resolve(rootDir, changedFile)).replace(/\\/g, "/");
241
+ const matchedRoute = manifest.routes.find((r) => {
242
+ if (!r.clientModule) return false;
243
+ const routeDir = path.dirname(path.resolve(rootDir, r.clientModule)).replace(/\\/g, "/");
244
+ return routeDir === changedDir;
245
+ });
246
+ if (matchedRoute) {
247
+ routeId = matchedRoute.id;
242
248
  }
243
249
  }
244
250
 
@@ -410,6 +410,13 @@ export function initializeRouter(): void {
410
410
  // 링크 클릭 이벤트 위임
411
411
  document.addEventListener("click", handleLinkClick);
412
412
 
413
+ // DevTools 자동 초기화 (개발 모드에서 SSR이 __MANDU_DEV_TOOLS__를 주입한 경우)
414
+ if ((window as any).__MANDU_DEV_TOOLS__) {
415
+ import("../devtools/init").then(({ initManduKitchen }) => {
416
+ initManduKitchen({ position: "bottom-right" });
417
+ }).catch(() => {});
418
+ }
419
+
413
420
  console.log("[Mandu Router] Initialized");
414
421
  }
415
422
 
@@ -375,6 +375,18 @@ export class ManduFilling<TLoaderData = unknown> {
375
375
  return Array.from(this.config.handlers.keys());
376
376
  }
377
377
 
378
+ /**
379
+ * Convert to named handler exports compatible with Mandu route.ts files.
380
+ * Usage: export const { GET, POST } = filling.toHandlers();
381
+ */
382
+ toHandlers(): Partial<Record<HttpMethod, (req: Request) => Promise<Response>>> {
383
+ const result: Partial<Record<HttpMethod, (req: Request) => Promise<Response>>> = {};
384
+ for (const method of this.config.handlers.keys()) {
385
+ result[method] = (req: Request) => this.handle(req, {}, undefined);
386
+ }
387
+ return result;
388
+ }
389
+
378
390
  hasMethod(method: HttpMethod): boolean {
379
391
  return this.config.handlers.has(method);
380
392
  }
@@ -238,6 +238,12 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
238
238
  hmrScript = generateHMRScript(hmrPort);
239
239
  }
240
240
 
241
+ // DevTools 부트스트랩 스크립트 (개발 모드)
242
+ let devtoolsScript = "";
243
+ if (isDev) {
244
+ devtoolsScript = generateDevtoolsBootstrapScript();
245
+ }
246
+
241
247
  return `<!doctype html>
242
248
  <html lang="${escapeHtmlAttr(lang)}">
243
249
  <head>
@@ -255,6 +261,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
255
261
  ${needsHydration ? REACT_INTERNALS_SHIM_SCRIPT : ""}
256
262
  ${routerScript}
257
263
  ${hmrScript}
264
+ ${devtoolsScript}
258
265
  ${bodyEndTags}
259
266
  </body>
260
267
  </html>`;
@@ -364,6 +371,18 @@ function generateHMRScript(port: number): string {
364
371
  </script>`;
365
372
  }
366
373
 
374
+ /**
375
+ * DevTools 부트스트랩 스크립트 생성 (개발 모드 전용)
376
+ */
377
+ function generateDevtoolsBootstrapScript(): string {
378
+ return `<script>
379
+ (function() {
380
+ if (typeof window === 'undefined') return;
381
+ window.__MANDU_DEV_TOOLS__ = true;
382
+ })();
383
+ </script>`;
384
+ }
385
+
367
386
  export function createHTMLResponse(html: string, status: number = 200): Response {
368
387
  return new Response(html, {
369
388
  status,