@cloudwerk/vite-plugin 0.6.7 → 0.6.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/dist/index.d.ts CHANGED
@@ -228,6 +228,6 @@ declare function generateServerEntry(manifest: RouteManifest, scanResult: ScanRe
228
228
  * @param options - Resolved plugin options
229
229
  * @returns Generated JavaScript code
230
230
  */
231
- declare function generateClientEntry(clientComponents: Map<string, ClientComponentInfo>, cssImports: Map<string, CssImportInfo[]>, options: ResolvedCloudwerkOptions): string;
231
+ declare function generateClientEntry(clientComponents: Map<string, ClientComponentInfo>, cssImports: Map<string, CssImportInfo[]>, options: ResolvedCloudwerkOptions, manifest?: RouteManifest): string;
232
232
 
233
233
  export { type AssetManifest, type AssetManifestEntry, type ClientComponentInfo, type CloudwerkVitePluginOptions, type CssImportInfo, type GenerateServerEntryOptions, RESOLVED_VIRTUAL_IDS, type ResolvedCloudwerkOptions, VIRTUAL_MODULE_IDS, cloudwerkPlugin, cloudwerkPlugin as default, generateClientEntry, generateServerEntry };
package/dist/index.js CHANGED
@@ -140,11 +140,11 @@ function generateServerEntry(manifest, scanResult, options, entryOptions) {
140
140
  if (hasOptionalCatchAll) {
141
141
  const basePath = route.urlPattern.replace(/\/:[^/]+\{\.\*\}$/, "") || "/";
142
142
  pageRegistrations.push(
143
- ` registerPage(app, '${basePath}', ${varName}, [${layoutChain}], [${middlewareChain}], ${errorModule || "null"}, ${notFoundModule || "null"})`
143
+ ` registerPage(app, '${basePath}', ${varName}, [${layoutChain}], [${middlewareChain}], ${errorModule || "null"}, ${notFoundModule || "null"}, '${route.urlPattern}')`
144
144
  );
145
145
  }
146
146
  pageRegistrations.push(
147
- ` registerPage(app, '${route.urlPattern}', ${varName}, [${layoutChain}], [${middlewareChain}], ${errorModule || "null"}, ${notFoundModule || "null"})`
147
+ ` registerPage(app, '${route.urlPattern}', ${varName}, [${layoutChain}], [${middlewareChain}], ${errorModule || "null"}, ${notFoundModule || "null"}, '${route.urlPattern}')`
148
148
  );
149
149
  } else if (route.fileType === "route") {
150
150
  const varName = `route_${routeIndex++}`;
@@ -363,7 +363,7 @@ async function renderNotFoundPage(notFoundModule, layoutModules, layoutLoaderDat
363
363
 
364
364
  const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']
365
365
 
366
- function registerPage(app, pattern, pageModule, layoutModules, middlewareModules, errorModule, notFoundModule) {
366
+ function registerPage(app, pattern, pageModule, layoutModules, middlewareModules, errorModule, notFoundModule, routeId) {
367
367
  // Apply middleware (wrap with adapter to convert Cloudwerk middleware to Hono middleware)
368
368
  for (const mw of middlewareModules) {
369
369
  app.use(pattern, createMiddlewareAdapter(mw))
@@ -428,7 +428,7 @@ function registerPage(app, pattern, pageModule, layoutModules, middlewareModules
428
428
  }
429
429
 
430
430
  // Render the page with hydration script injection
431
- return await renderWithHydration(element)
431
+ return await renderWithHydration(element, 200, routeId, pageProps, layoutLoaderData)
432
432
  } catch (error) {
433
433
  // Handle NotFoundError (check both instanceof and name for module duplication)
434
434
  if (error instanceof NotFoundError || error?.name === 'NotFoundError') {
@@ -460,7 +460,7 @@ function registerPage(app, pattern, pageModule, layoutModules, middlewareModules
460
460
  * - CSS links are injected before </head>
461
461
  * - Vite client (dev) and hydration script are injected before </body>
462
462
  */
463
- async function renderWithHydration(element, status = 200) {
463
+ async function renderWithHydration(element, status = 200, routeId, pageProps, layoutData) {
464
464
  // Render element to HTML string using the active renderer
465
465
  ${rendererName === "react" ? `// React: use renderToString from react-dom/server
466
466
  const { renderToString } = await import('react-dom/server')
@@ -478,7 +478,13 @@ async function renderWithHydration(element, status = 200) {
478
478
  // Inject scripts before </body>
479
479
  // - Vite client for HMR (dev only)
480
480
  // - Hydration script for client components
481
- const scripts = VITE_CLIENT + '<script type="module" src="${clientEntryPath}"></script>'
481
+ let scripts = VITE_CLIENT
482
+ ${rendererName === "react" ? `// React: embed serialized page data for full-tree hydration
483
+ if (routeId) {
484
+ const pageData = JSON.stringify({ routeId, pageProps: pageProps || {}, layoutData: layoutData || [] })
485
+ scripts += '<script id="__CLOUDWERK_DATA__" type="application/json">' + pageData + '</script>'
486
+ }` : ""}
487
+ scripts += '<script type="module" src="${clientEntryPath}"></script>'
482
488
  const bodyCloseRegex = /<\\/body>/i
483
489
  if (bodyCloseRegex.test(html)) {
484
490
  html = html.replace(bodyCloseRegex, scripts + '</body>')
@@ -1085,11 +1091,11 @@ function generateCssImportStatements(cssPaths) {
1085
1091
  }
1086
1092
  return cssPaths.map((cssPath) => `import '${cssPath}'`).join("\n") + "\n\n";
1087
1093
  }
1088
- function generateClientEntry(clientComponents, cssImports, options) {
1094
+ function generateClientEntry(clientComponents, cssImports, options, manifest) {
1089
1095
  const { renderer, hydrationEndpoint, isProduction } = options;
1090
1096
  const cssPaths = collectCssImports(cssImports);
1091
1097
  if (renderer === "react") {
1092
- return generateReactClientEntry(clientComponents, cssPaths, hydrationEndpoint, isProduction);
1098
+ return generateReactClientEntry(clientComponents, cssPaths, hydrationEndpoint, isProduction, manifest);
1093
1099
  }
1094
1100
  return generateHonoClientEntry(clientComponents, cssPaths, hydrationEndpoint, isProduction);
1095
1101
  }
@@ -1280,105 +1286,102 @@ if (document.readyState === 'loading') {
1280
1286
  export { hydrate }
1281
1287
  `;
1282
1288
  }
1283
- function generateReactClientEntry(clientComponents, cssPaths, _hydrationEndpoint, isProduction = false) {
1289
+ function generateReactClientEntry(_clientComponents, cssPaths, _hydrationEndpoint, isProduction = false, manifest) {
1284
1290
  const cssImportStatements = generateCssImportStatements(cssPaths);
1285
- if (isProduction) {
1286
- return generateProductionClientEntry(clientComponents, {
1287
- header: "Generated Cloudwerk Client Entry (React - Production)",
1288
- cssImports: cssImportStatements,
1289
- rendererImports: `import { hydrateRoot } from 'react-dom/client'
1290
- import { createElement } from 'react'`,
1291
- additionalDeclarations: `
1292
- // Root cache for React 18 concurrent mode
1293
- const rootCache = new Map()
1294
- `,
1295
- hydrateCall: `// Hydrate the component using React 18 hydrateRoot
1296
- const root = hydrateRoot(el, createElement(Component, props))
1297
- rootCache.set(el, root)`
1298
- });
1291
+ if (!manifest) {
1292
+ return `/**
1293
+ * Generated Cloudwerk Client Entry (React - No Routes)
1294
+ * This file is auto-generated by @cloudwerk/vite-plugin - do not edit
1295
+ */
1296
+
1297
+ ${cssImportStatements}console.debug('[Cloudwerk] No route manifest available for React hydration')
1298
+ `;
1299
+ }
1300
+ const pageImports = [];
1301
+ const layoutImports = [];
1302
+ const routeMapEntries = [];
1303
+ const importedLayouts = /* @__PURE__ */ new Map();
1304
+ let pageIndex = 0;
1305
+ let layoutIndex = 0;
1306
+ for (const route of manifest.routes) {
1307
+ if (route.fileType !== "page") continue;
1308
+ const pageVar = `page_${pageIndex++}`;
1309
+ const importPath = isProduction ? route.absolutePath : `/@fs${route.absolutePath}`;
1310
+ pageImports.push(`import * as ${pageVar} from '${importPath}'`);
1311
+ const layoutVars = [];
1312
+ for (const layoutPath of route.layouts) {
1313
+ if (!importedLayouts.has(layoutPath)) {
1314
+ const layoutVar = `layout_${layoutIndex++}`;
1315
+ const layoutImportPath = isProduction ? layoutPath : `/@fs${layoutPath}`;
1316
+ layoutImports.push(`import * as ${layoutVar} from '${layoutImportPath}'`);
1317
+ importedLayouts.set(layoutPath, layoutVar);
1318
+ }
1319
+ layoutVars.push(importedLayouts.get(layoutPath));
1320
+ }
1321
+ routeMapEntries.push(
1322
+ ` '${route.urlPattern}': { page: ${pageVar}.default, layouts: [${layoutVars.map((v) => `${v}.default`).join(", ")}] }`
1323
+ );
1299
1324
  }
1300
- const bundleMap = Object.fromEntries(
1301
- Array.from(clientComponents.values()).map((info) => [info.componentId, `/@fs${info.absolutePath}`])
1302
- );
1303
1325
  return `/**
1304
- * Generated Cloudwerk Client Entry (React)
1326
+ * Generated Cloudwerk Client Entry (React - Full Tree Hydration)
1305
1327
  * This file is auto-generated by @cloudwerk/vite-plugin - do not edit
1306
1328
  */
1307
1329
 
1308
- ${cssImportStatements}import { hydrateRoot, createRoot } from 'react-dom/client'
1330
+ ${cssImportStatements}import { hydrateRoot } from 'react-dom/client'
1309
1331
  import { createElement } from 'react'
1310
1332
 
1311
- // Bundle map for component lookups
1312
- const bundles = ${JSON.stringify(bundleMap, null, 2)}
1313
-
1314
- // Module cache to avoid re-importing
1315
- const moduleCache = new Map()
1333
+ // Page imports
1334
+ ${pageImports.join("\n")}
1316
1335
 
1317
- // Root cache for React 18 concurrent mode
1318
- const rootCache = new Map()
1336
+ // Layout imports
1337
+ ${layoutImports.join("\n")}
1319
1338
 
1320
- /**
1321
- * Load a component module, using cache for repeated imports.
1322
- */
1323
- async function loadComponent(bundlePath) {
1324
- if (moduleCache.has(bundlePath)) {
1325
- return moduleCache.get(bundlePath)
1326
- }
1327
- const module = await import(/* @vite-ignore */ bundlePath)
1328
- moduleCache.set(bundlePath, module)
1329
- return module
1339
+ // Route map: routeId -> { page, layouts }
1340
+ const routes = {
1341
+ ${routeMapEntries.join(",\n")}
1330
1342
  }
1331
1343
 
1332
1344
  /**
1333
- * Hydrate all marked elements on the page.
1345
+ * Hydrate the full React tree from serialized page data.
1334
1346
  */
1335
- async function hydrate() {
1336
- const elements = document.querySelectorAll('[data-hydrate-id]')
1337
- if (elements.length === 0) {
1338
- console.debug('[Cloudwerk] No client components to hydrate')
1347
+ function hydrate() {
1348
+ const dataEl = document.getElementById('__CLOUDWERK_DATA__')
1349
+ if (!dataEl) {
1350
+ console.debug('[Cloudwerk] No page data found for React hydration')
1339
1351
  return
1340
1352
  }
1341
1353
 
1342
- console.debug('[Cloudwerk] Hydrating', elements.length, 'client components')
1354
+ let pageData
1355
+ try {
1356
+ pageData = JSON.parse(dataEl.textContent || '{}')
1357
+ } catch (e) {
1358
+ console.error('[Cloudwerk] Failed to parse page data:', e)
1359
+ return
1360
+ }
1343
1361
 
1344
- for (const el of elements) {
1345
- const componentId = el.getAttribute('data-hydrate-id')
1346
- const propsJson = el.getAttribute('data-hydrate-props')
1362
+ const route = routes[pageData.routeId]
1363
+ if (!route) {
1364
+ console.error('[Cloudwerk] Unknown route:', pageData.routeId)
1365
+ return
1366
+ }
1347
1367
 
1348
- if (!componentId) {
1349
- console.warn('[Cloudwerk] Element missing data-hydrate-id')
1350
- continue
1351
- }
1368
+ // Reconstruct the component tree (same as server)
1369
+ let element = createElement(route.page, pageData.pageProps || {})
1352
1370
 
1353
- const bundlePath = bundles[componentId]
1354
- if (!bundlePath) {
1355
- console.warn('[Cloudwerk] Unknown client component:', componentId)
1356
- continue
1371
+ // Wrap with layouts (inside-out, same order as server)
1372
+ for (let i = route.layouts.length - 1; i >= 0; i--) {
1373
+ const layoutProps = {
1374
+ children: element,
1375
+ params: pageData.pageProps?.params,
1376
+ ...(pageData.layoutData?.[i] || {}),
1357
1377
  }
1378
+ element = createElement(route.layouts[i], layoutProps)
1379
+ }
1358
1380
 
1359
- try {
1360
- const props = propsJson ? JSON.parse(propsJson) : {}
1361
- const module = await loadComponent(bundlePath)
1362
- const Component = module.default
1363
-
1364
- if (!Component) {
1365
- console.error('[Cloudwerk] No default export in component:', componentId)
1366
- continue
1367
- }
1368
-
1369
- // Hydrate the component using React 18 hydrateRoot
1370
- const root = hydrateRoot(el, createElement(Component, props))
1371
- rootCache.set(el, root)
1372
-
1373
- // Clean up hydration attributes
1374
- el.removeAttribute('data-hydrate-id')
1375
- el.removeAttribute('data-hydrate-props')
1381
+ // Hydrate the full tree
1382
+ hydrateRoot(document, element)
1376
1383
 
1377
- console.debug('[Cloudwerk] Hydrated:', componentId)
1378
- } catch (error) {
1379
- console.error('[Cloudwerk] Failed to hydrate component:', componentId, error)
1380
- }
1381
- }
1384
+ console.debug('[Cloudwerk] Full-tree hydration complete for route:', pageData.routeId)
1382
1385
  }
1383
1386
 
1384
1387
  // Run hydration when DOM is ready
@@ -2500,7 +2503,8 @@ function cloudwerkPlugin(options = {}) {
2500
2503
  state.clientEntryCache = generateClientEntry(
2501
2504
  state.clientComponents,
2502
2505
  state.cssImports,
2503
- state.options
2506
+ state.options,
2507
+ state.manifest
2504
2508
  );
2505
2509
  }
2506
2510
  return state.clientEntryCache;
@@ -2627,8 +2631,12 @@ function cloudwerkPlugin(options = {}) {
2627
2631
  bundlePath: id
2628
2632
  // Use file path for Vite to resolve in dev mode
2629
2633
  });
2630
- if (!result.success && state.options.verbose) {
2631
- console.warn(`[cloudwerk] ${result.error}`);
2634
+ if (!result.success) {
2635
+ console.warn(`[cloudwerk] Warning: Failed to wrap client component ${componentId}: ${result.error}`);
2636
+ return {
2637
+ code: transformedCode.replace(/['"]use client['"]\s*;?\s*\n?/g, ""),
2638
+ map: null
2639
+ };
2632
2640
  }
2633
2641
  return {
2634
2642
  code: result.code,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/vite-plugin",
3
- "version": "0.6.7",
3
+ "version": "0.6.8",
4
4
  "description": "Vite plugin for Cloudwerk file-based routing with virtual entry generation",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@swc/core": "^1.3.100",
22
22
  "@cloudwerk/core": "^0.15.3",
23
- "@cloudwerk/ui": "^0.15.3"
23
+ "@cloudwerk/ui": "^0.15.6"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "vite": "^5.0.0 || ^6.0.0",