@cloudwerk/vite-plugin 0.7.0 → 0.8.0

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
@@ -95,6 +95,8 @@ interface ClientComponentInfo {
95
95
  bundlePath: string;
96
96
  /** Absolute file path */
97
97
  absolutePath: string;
98
+ /** Named export name (undefined means default export) */
99
+ exportName?: string;
98
100
  }
99
101
  /**
100
102
  * Information about a CSS import detected in a layout or page.
package/dist/index.js CHANGED
@@ -1101,9 +1101,12 @@ function generateClientEntry(clientComponents, cssImports, options, manifest) {
1101
1101
  }
1102
1102
  function generateStaticImportsAndMap(clientComponents) {
1103
1103
  const components = Array.from(clientComponents.values());
1104
- const imports = components.map(
1105
- (info, index) => `import Component${index} from '${info.absolutePath}'`
1106
- ).join("\n");
1104
+ const imports = components.map((info, index) => {
1105
+ if (info.exportName) {
1106
+ return `import { ${info.exportName} as Component${index} } from '${info.absolutePath}'`;
1107
+ }
1108
+ return `import Component${index} from '${info.absolutePath}'`;
1109
+ }).join("\n");
1107
1110
  const componentMapEntries = components.map(
1108
1111
  (info, index) => ` '${info.componentId}': Component${index}`
1109
1112
  ).join(",\n");
@@ -1195,7 +1198,10 @@ import { jsx } from 'hono/jsx/jsx-runtime'`,
1195
1198
  });
1196
1199
  }
1197
1200
  const bundleMap = Object.fromEntries(
1198
- Array.from(clientComponents.values()).map((info) => [info.componentId, `/@fs${info.absolutePath}`])
1201
+ Array.from(clientComponents.values()).map((info) => [
1202
+ info.componentId,
1203
+ { path: `/@fs${info.absolutePath}`, exportName: info.exportName ?? null }
1204
+ ])
1199
1205
  );
1200
1206
  return `/**
1201
1207
  * Generated Cloudwerk Client Entry (Hono JSX)
@@ -1244,19 +1250,19 @@ async function hydrate() {
1244
1250
  continue
1245
1251
  }
1246
1252
 
1247
- const bundlePath = bundles[componentId]
1248
- if (!bundlePath) {
1253
+ const bundle = bundles[componentId]
1254
+ if (!bundle) {
1249
1255
  console.warn('[Cloudwerk] Unknown client component:', componentId)
1250
1256
  continue
1251
1257
  }
1252
1258
 
1253
1259
  try {
1254
1260
  const props = propsJson ? JSON.parse(propsJson) : {}
1255
- const module = await loadComponent(bundlePath)
1256
- const Component = module.default
1261
+ const module = await loadComponent(bundle.path)
1262
+ const Component = bundle.exportName ? module[bundle.exportName] : module.default
1257
1263
 
1258
1264
  if (!Component) {
1259
- console.error('[Cloudwerk] No default export in component:', componentId)
1265
+ console.error('[Cloudwerk] No export found in component:', componentId)
1260
1266
  continue
1261
1267
  }
1262
1268
 
@@ -1472,6 +1478,57 @@ function findDefaultExport(ast) {
1472
1478
  }
1473
1479
  return { type: null, name: null, index: -1 };
1474
1480
  }
1481
+ function findNamedExports(ast) {
1482
+ const results = [];
1483
+ for (let i = 0; i < ast.body.length; i++) {
1484
+ const node = ast.body[i];
1485
+ if (node.type === "ExportDeclaration") {
1486
+ const decl = node.declaration;
1487
+ if (decl.type === "FunctionDeclaration" && decl.identifier) {
1488
+ const name = decl.identifier.value;
1489
+ if (/^[A-Z]/.test(name)) {
1490
+ results.push({
1491
+ name,
1492
+ type: "function",
1493
+ index: i,
1494
+ spanStart: node.span.start,
1495
+ spanEnd: node.span.end,
1496
+ isAsync: decl.async
1497
+ });
1498
+ }
1499
+ }
1500
+ if (decl.type === "ClassDeclaration" && decl.identifier) {
1501
+ const name = decl.identifier.value;
1502
+ if (/^[A-Z]/.test(name)) {
1503
+ results.push({
1504
+ name,
1505
+ type: "class",
1506
+ index: i,
1507
+ spanStart: node.span.start,
1508
+ spanEnd: node.span.end
1509
+ });
1510
+ }
1511
+ }
1512
+ if (decl.type === "VariableDeclaration") {
1513
+ for (const declarator of decl.declarations) {
1514
+ if (declarator.id.type === "Identifier") {
1515
+ const name = declarator.id.value;
1516
+ if (/^[A-Z]/.test(name)) {
1517
+ results.push({
1518
+ name,
1519
+ type: "const",
1520
+ index: i,
1521
+ spanStart: node.span.start,
1522
+ spanEnd: node.span.end
1523
+ });
1524
+ }
1525
+ }
1526
+ }
1527
+ }
1528
+ }
1529
+ }
1530
+ return results;
1531
+ }
1475
1532
  function transformClientComponent(code, options) {
1476
1533
  const { componentId, bundlePath } = options;
1477
1534
  try {
@@ -1481,7 +1538,8 @@ function transformClientComponent(code, options) {
1481
1538
  comments: true
1482
1539
  });
1483
1540
  const exportInfo = findDefaultExport(ast);
1484
- if (exportInfo.type === null) {
1541
+ const namedExports = findNamedExports(ast);
1542
+ if (exportInfo.type === null && namedExports.length === 0) {
1485
1543
  return {
1486
1544
  code,
1487
1545
  success: false,
@@ -1492,97 +1550,134 @@ function transformClientComponent(code, options) {
1492
1550
  const wrapperImport = `import { createClientComponentWrapper as __createWrapper } from '@cloudwerk/ui/client'
1493
1551
  `;
1494
1552
  const metaObj = JSON.stringify({ componentId, bundlePath });
1495
- switch (exportInfo.type) {
1496
- case "function": {
1497
- if (exportInfo.name) {
1498
- const asyncPrefix = exportInfo.isAsync ? "async " : "";
1499
- transformed = transformed.replace(
1500
- new RegExp(`export\\s+default\\s+${asyncPrefix}function\\s+${exportInfo.name}`),
1501
- `${asyncPrefix}function ${exportInfo.name}`
1502
- );
1503
- transformed = wrapperImport + transformed;
1504
- transformed += `
1553
+ const wrappedExports = [];
1554
+ if (namedExports.length > 0) {
1555
+ const sorted = [...namedExports].sort((a, b) => b.spanStart - a.spanStart);
1556
+ for (const namedExport of sorted) {
1557
+ const namedMetaObj = JSON.stringify({
1558
+ componentId: `${componentId}__${namedExport.name}`,
1559
+ bundlePath
1560
+ });
1561
+ const originalName = `__${namedExport.name}_original`;
1562
+ switch (namedExport.type) {
1563
+ case "function": {
1564
+ const asyncPrefix = namedExport.isAsync ? "async " : "";
1565
+ transformed = transformed.replace(
1566
+ new RegExp(`export\\s+${asyncPrefix}function\\s+${namedExport.name}\\s*\\(`),
1567
+ `${asyncPrefix}function ${originalName}(`
1568
+ );
1569
+ break;
1570
+ }
1571
+ case "class": {
1572
+ transformed = transformed.replace(
1573
+ new RegExp(`export\\s+class\\s+${namedExport.name}\\s`),
1574
+ `class ${originalName} `
1575
+ );
1576
+ break;
1577
+ }
1578
+ case "const": {
1579
+ transformed = transformed.replace(
1580
+ new RegExp(`export\\s+(const|let|var)\\s+${namedExport.name}\\s*=`),
1581
+ `const ${originalName} =`
1582
+ );
1583
+ break;
1584
+ }
1585
+ }
1586
+ transformed += `
1587
+ export const ${namedExport.name} = __createWrapper(${originalName}, ${namedMetaObj})
1588
+ `;
1589
+ wrappedExports.push(namedExport.name);
1590
+ }
1591
+ }
1592
+ if (exportInfo.type !== null) {
1593
+ switch (exportInfo.type) {
1594
+ case "function": {
1595
+ if (exportInfo.name) {
1596
+ const asyncPrefix = exportInfo.isAsync ? "async " : "";
1597
+ transformed = transformed.replace(
1598
+ new RegExp(`export\\s+default\\s+${asyncPrefix}function\\s+${exportInfo.name}`),
1599
+ `${asyncPrefix}function ${exportInfo.name}`
1600
+ );
1601
+ transformed += `
1505
1602
  const __WrappedComponent = __createWrapper(${exportInfo.name}, ${metaObj})
1506
1603
  export default __WrappedComponent
1507
1604
  `;
1508
- } else {
1509
- const asyncPrefix = exportInfo.isAsync ? "async " : "";
1605
+ } else {
1606
+ const asyncPrefix = exportInfo.isAsync ? "async " : "";
1607
+ transformed = transformed.replace(
1608
+ new RegExp(`export\\s+default\\s+${asyncPrefix}function\\s*\\(`),
1609
+ `const __OriginalComponent = ${asyncPrefix}function(`
1610
+ );
1611
+ transformed += `
1612
+ const __WrappedComponent = __createWrapper(__OriginalComponent, ${metaObj})
1613
+ export default __WrappedComponent
1614
+ `;
1615
+ }
1616
+ break;
1617
+ }
1618
+ case "arrow": {
1510
1619
  transformed = transformed.replace(
1511
- new RegExp(`export\\s+default\\s+${asyncPrefix}function\\s*\\(`),
1512
- `const __OriginalComponent = ${asyncPrefix}function(`
1620
+ /export\s+default/,
1621
+ "const __OriginalComponent ="
1513
1622
  );
1514
- transformed = wrapperImport + transformed;
1515
1623
  transformed += `
1516
1624
  const __WrappedComponent = __createWrapper(__OriginalComponent, ${metaObj})
1517
1625
  export default __WrappedComponent
1518
1626
  `;
1627
+ break;
1519
1628
  }
1520
- break;
1521
- }
1522
- case "arrow": {
1523
- transformed = transformed.replace(
1524
- /export\s+default/,
1525
- "const __OriginalComponent ="
1526
- );
1527
- transformed = wrapperImport + transformed;
1528
- transformed += `
1629
+ case "class": {
1630
+ if (exportInfo.name) {
1631
+ transformed = transformed.replace(
1632
+ new RegExp(`export\\s+default\\s+class\\s+${exportInfo.name}`),
1633
+ `class ${exportInfo.name}`
1634
+ );
1635
+ transformed += `
1636
+ const __WrappedComponent = __createWrapper(${exportInfo.name}, ${metaObj})
1637
+ export default __WrappedComponent
1638
+ `;
1639
+ } else {
1640
+ transformed = transformed.replace(
1641
+ /export\s+default\s+class\s*\{/,
1642
+ "const __OriginalComponent = class {"
1643
+ );
1644
+ transformed += `
1529
1645
  const __WrappedComponent = __createWrapper(__OriginalComponent, ${metaObj})
1530
1646
  export default __WrappedComponent
1531
1647
  `;
1532
- break;
1533
- }
1534
- case "class": {
1535
- if (exportInfo.name) {
1648
+ }
1649
+ break;
1650
+ }
1651
+ case "identifier": {
1536
1652
  transformed = transformed.replace(
1537
- new RegExp(`export\\s+default\\s+class\\s+${exportInfo.name}`),
1538
- `class ${exportInfo.name}`
1653
+ /export\s+default\s+\w+\s*;?\s*$/m,
1654
+ ""
1539
1655
  );
1540
- transformed = wrapperImport + transformed;
1541
1656
  transformed += `
1542
1657
  const __WrappedComponent = __createWrapper(${exportInfo.name}, ${metaObj})
1543
1658
  export default __WrappedComponent
1544
1659
  `;
1545
- } else {
1660
+ break;
1661
+ }
1662
+ case "named-export": {
1546
1663
  transformed = transformed.replace(
1547
- /export\s+default\s+class\s*\{/,
1548
- "const __OriginalComponent = class {"
1664
+ /export\s*\{\s*\w+\s+as\s+default\s*\}\s*;?/,
1665
+ ""
1549
1666
  );
1550
- transformed = wrapperImport + transformed;
1551
1667
  transformed += `
1552
- const __WrappedComponent = __createWrapper(__OriginalComponent, ${metaObj})
1553
- export default __WrappedComponent
1554
- `;
1555
- }
1556
- break;
1557
- }
1558
- case "identifier": {
1559
- transformed = transformed.replace(
1560
- /export\s+default\s+\w+\s*;?\s*$/m,
1561
- ""
1562
- );
1563
- transformed = wrapperImport + transformed;
1564
- transformed += `
1565
1668
  const __WrappedComponent = __createWrapper(${exportInfo.name}, ${metaObj})
1566
1669
  export default __WrappedComponent
1567
1670
  `;
1568
- break;
1569
- }
1570
- case "named-export": {
1571
- transformed = transformed.replace(
1572
- /export\s*\{\s*\w+\s+as\s+default\s*\}\s*;?/,
1573
- ""
1574
- );
1575
- transformed = wrapperImport + transformed;
1576
- transformed += `
1577
- const __WrappedComponent = __createWrapper(${exportInfo.name}, ${metaObj})
1578
- export default __WrappedComponent
1579
- `;
1580
- break;
1671
+ break;
1672
+ }
1581
1673
  }
1582
1674
  }
1675
+ transformed = wrapperImport + transformed;
1583
1676
  return {
1584
1677
  code: transformed,
1585
- success: true
1678
+ success: true,
1679
+ wrappedExports: wrappedExports.length > 0 ? wrappedExports : void 0,
1680
+ hasDefaultExport: exportInfo.type !== null
1586
1681
  };
1587
1682
  } catch (error) {
1588
1683
  const message = error instanceof Error ? error.message : String(error);
@@ -2726,6 +2821,25 @@ function cloudwerkPlugin(options = {}) {
2726
2821
  map: null
2727
2822
  };
2728
2823
  }
2824
+ if (result.wrappedExports && result.wrappedExports.length > 0) {
2825
+ for (const exportName of result.wrappedExports) {
2826
+ const namedComponentId = `${componentId}__${exportName}`;
2827
+ const namedBundlePath = `${state.options.hydrationEndpoint}/${namedComponentId}.js`;
2828
+ const namedInfo = {
2829
+ componentId: namedComponentId,
2830
+ bundlePath: namedBundlePath,
2831
+ absolutePath: id,
2832
+ exportName
2833
+ };
2834
+ state.clientComponents.set(`${id}::${exportName}`, namedInfo);
2835
+ if (state.options.verbose) {
2836
+ console.log(`[cloudwerk] Detected named client component: ${namedComponentId}`);
2837
+ }
2838
+ }
2839
+ if (!result.hasDefaultExport) {
2840
+ state.clientComponents.delete(id);
2841
+ }
2842
+ }
2729
2843
  return {
2730
2844
  code: result.code,
2731
2845
  map: null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/vite-plugin",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Vite plugin for Cloudwerk file-based routing with virtual entry generation",
5
5
  "repository": {
6
6
  "type": "git",