what-compiler 0.8.4 → 0.10.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.
@@ -106,6 +106,13 @@ function extractPageConfig(source) {
106
106
  return { mode: "client" };
107
107
  }
108
108
  }
109
+ function detectPageExports(source) {
110
+ return {
111
+ hasLoader: /export\s+(?:async\s+)?(?:const|let|var|function)\s+loader\b/.test(source),
112
+ hasGetStaticPaths: /export\s+(?:async\s+)?(?:const|let|var|function)\s+getStaticPaths\b/.test(source),
113
+ hasPageConfig: /export\s+const\s+page\b/.test(source)
114
+ };
115
+ }
109
116
  function generateRoutesModule(pagesDir, rootDir) {
110
117
  const { pages, layouts, apiRoutes } = scanPages(pagesDir);
111
118
  const imports = [];
@@ -122,9 +129,11 @@ function generateRoutesModule(pagesDir, rootDir) {
122
129
  const relPath = toImportPath(page.filePath, rootDir);
123
130
  imports.push(`import ${varName} from '${relPath}';`);
124
131
  let pageConfig = { mode: "client" };
132
+ let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
125
133
  try {
126
134
  const source = fs.readFileSync(page.filePath, "utf-8");
127
135
  pageConfig = extractPageConfig(source);
136
+ detected = detectPageExports(source);
128
137
  } catch {
129
138
  }
130
139
  const layoutVar = findLayout(page.routePath, layoutMap);
@@ -132,7 +141,8 @@ function generateRoutesModule(pagesDir, rootDir) {
132
141
  path: page.routePath,
133
142
  component: varName,
134
143
  mode: pageConfig.mode || "client",
135
- layout: layoutVar || null
144
+ layout: layoutVar || null,
145
+ hasLoader: detected.hasLoader
136
146
  };
137
147
  routeEntries.push(entry);
138
148
  });
@@ -154,7 +164,7 @@ function generateRoutesModule(pagesDir, rootDir) {
154
164
  "",
155
165
  "export const routes = [",
156
166
  ...routeEntries.map(
157
- (r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""} },`
167
+ (r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""}${r.hasLoader ? ", hasLoader: true" : ""} },`
158
168
  ),
159
169
  "];",
160
170
  "",
@@ -173,6 +183,70 @@ function generateRoutesModule(pagesDir, rootDir) {
173
183
  ];
174
184
  return lines.join("\n");
175
185
  }
186
+ function generateServerRoutesModule(pagesDir, rootDir) {
187
+ const { pages, layouts, apiRoutes } = scanPages(pagesDir);
188
+ const imports = [];
189
+ const routeEntries = [];
190
+ const layoutMap = /* @__PURE__ */ new Map();
191
+ layouts.forEach((layout, i) => {
192
+ const varName = `_layout${i}`;
193
+ imports.push(`import ${varName} from '${toImportPath(layout.filePath, rootDir)}';`);
194
+ layoutMap.set(layout.urlPrefix, varName);
195
+ });
196
+ pages.forEach((page, i) => {
197
+ const def = `_page${i}`;
198
+ const ns = `_page${i}_ns`;
199
+ const relPath = toImportPath(page.filePath, rootDir);
200
+ let pageConfig = { mode: "client" };
201
+ let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
202
+ try {
203
+ const source = fs.readFileSync(page.filePath, "utf-8");
204
+ pageConfig = extractPageConfig(source);
205
+ detected = detectPageExports(source);
206
+ } catch {
207
+ }
208
+ const needsNs = detected.hasLoader || detected.hasGetStaticPaths || detected.hasPageConfig;
209
+ if (needsNs) {
210
+ imports.push(`import ${def}, * as ${ns} from '${relPath}';`);
211
+ } else {
212
+ imports.push(`import ${def} from '${relPath}';`);
213
+ }
214
+ routeEntries.push({
215
+ path: page.routePath,
216
+ component: def,
217
+ ns,
218
+ mode: pageConfig.mode || "client",
219
+ layout: findLayout(page.routePath, layoutMap) || null,
220
+ ...detected
221
+ });
222
+ });
223
+ const apiEntries = [];
224
+ apiRoutes.forEach((route, i) => {
225
+ const varName = `_api${i}`;
226
+ imports.push(`import * as ${varName} from '${toImportPath(route.filePath, rootDir)}';`);
227
+ apiEntries.push({ path: route.routePath, handlers: varName });
228
+ });
229
+ const routeLine = (r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""}${r.hasLoader ? `, loader: ${r.ns}.loader` : ""}${r.hasGetStaticPaths ? `, getStaticPaths: ${r.ns}.getStaticPaths` : ""}${r.hasPageConfig ? `, page: ${r.ns}.page` : ""} },`;
230
+ const lines = [
231
+ "// Auto-generated by What Framework file router (server)",
232
+ "// Do not edit \u2014 changes will be overwritten",
233
+ "",
234
+ ...imports,
235
+ "",
236
+ "export const routes = [",
237
+ ...routeEntries.map(routeLine),
238
+ "];",
239
+ "",
240
+ "export const apiRoutes = [",
241
+ ...apiEntries.map((r) => ` { path: '${r.path}', handlers: ${r.handlers} },`),
242
+ "];",
243
+ "",
244
+ "export const pageModes = {",
245
+ ...routeEntries.map((r) => ` '${r.path}': '${r.mode}',`),
246
+ "};"
247
+ ];
248
+ return lines.join("\n");
249
+ }
176
250
  function toImportPath(filePath, rootDir) {
177
251
  const rel = path.relative(rootDir, filePath);
178
252
  return "/" + rel.split(path.sep).join("/");
@@ -188,8 +262,10 @@ function findLayout(routePath, layoutMap) {
188
262
  return null;
189
263
  }
190
264
  export {
265
+ detectPageExports,
191
266
  extractPageConfig,
192
267
  generateRoutesModule,
268
+ generateServerRoutesModule,
193
269
  scanPages
194
270
  };
195
271
  //# sourceMappingURL=file-router.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/file-router.js"],
4
- "sourcesContent": ["/**\n * File-Based Router for What Framework\n *\n * Scans a pages directory and generates route configuration.\n *\n * File conventions:\n * src/pages/index.jsx \u2192 /\n * src/pages/about.jsx \u2192 /about\n * src/pages/blog/index.jsx \u2192 /blog\n * src/pages/blog/[slug].jsx \u2192 /blog/:slug\n * src/pages/[...path].jsx \u2192 catch-all\n * src/pages/_layout.jsx \u2192 layout for that directory\n * src/pages/(auth)/login.jsx \u2192 /login (group doesn't affect URL)\n * src/pages/api/users.js \u2192 API route: /api/users\n *\n * Page declarations (optional export in each page file):\n * export const page = {\n * mode: 'client', // default \u2014 SPA, JS required\n * mode: 'server', // SSR on every request\n * mode: 'static', // pre-rendered at build time\n * mode: 'hybrid', // static HTML shell + interactive islands\n * };\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nconst PAGE_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.ts']);\nconst IGNORED_FILES = new Set(['_layout', '_error', '_loading', '_404']);\n\n/**\n * Scan a directory recursively and return all page files.\n */\nexport function scanPages(pagesDir) {\n const pages = [];\n const layouts = [];\n const apiRoutes = [];\n\n function walk(dir, urlPrefix = '') {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n // Route groups: (name)/ \u2014 strip from URL\n const groupMatch = entry.name.match(/^\\((.+)\\)$/);\n if (groupMatch) {\n walk(fullPath, urlPrefix); // Same URL prefix\n continue;\n }\n\n // API directory\n if (entry.name === 'api' && urlPrefix === '') {\n walkApi(fullPath, '/api');\n continue;\n }\n\n walk(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n // Only process page extensions\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n\n // Layout files\n if (baseName === '_layout') {\n layouts.push({\n filePath: fullPath,\n urlPrefix: urlPrefix || '/',\n });\n continue;\n }\n\n // Error/loading/404 boundaries (reserved names)\n if (IGNORED_FILES.has(baseName)) continue;\n\n // Convert file name to URL segment\n const urlSegment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? (urlPrefix || '/')\n : urlPrefix + '/' + urlSegment;\n\n pages.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n isDynamic: routePath.includes(':') || routePath.includes('*'),\n });\n }\n }\n\n function walkApi(dir, urlPrefix) {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n walkApi(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n const segment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? urlPrefix\n : urlPrefix + '/' + segment;\n\n apiRoutes.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n });\n }\n }\n\n walk(pagesDir);\n\n // Sort: static routes first, then dynamic, then catch-all\n pages.sort((a, b) => {\n const aWeight = routeWeight(a.routePath);\n const bWeight = routeWeight(b.routePath);\n return aWeight - bWeight;\n });\n\n return { pages, layouts, apiRoutes };\n}\n\n/**\n * Convert a file name to a URL segment.\n * [slug] \u2192 :slug\n * [...path] \u2192 *path (catch-all)\n * about \u2192 about\n */\nfunction fileNameToSegment(name) {\n // Catch-all: [...param]\n const catchAll = name.match(/^\\[\\.\\.\\.(\\w+)\\]$/);\n if (catchAll) return '*' + catchAll[1];\n\n // Dynamic: [param]\n const dynamic = name.match(/^\\[(\\w+)\\]$/);\n if (dynamic) return ':' + dynamic[1];\n\n // Lowercase page names for URL consistency (About.jsx \u2192 /about)\n return name.toLowerCase();\n}\n\n/**\n * Normalize a route path.\n */\nfunction normalizePath(p) {\n // Remove double slashes\n let result = p.replace(/\\/+/g, '/');\n // Remove trailing slash (except root)\n if (result.length > 1 && result.endsWith('/')) {\n result = result.slice(0, -1);\n }\n return result || '/';\n}\n\n/**\n * Route weight for sorting \u2014 static routes first.\n */\nfunction routeWeight(path) {\n if (path.includes('*')) return 100; // Catch-all last\n if (path.includes(':')) return 10; // Dynamic middle\n return 0; // Static first\n}\n\n/**\n * Extract `export const page = { ... }` from a file's source code.\n * Uses simple regex \u2014 doesn't need a full parser for this.\n */\nexport function extractPageConfig(source) {\n // Match: export const page = { ... }\n // Handles single-line and simple multi-line objects\n const match = source.match(\n /export\\s+const\\s+page\\s*=\\s*(\\{[^}]*\\})/s\n );\n\n if (!match) {\n return { mode: 'client' }; // Default\n }\n\n try {\n // Simple evaluation of the object literal\n // Only supports string/boolean/number literals for safety\n const obj = match[1]\n .replace(/'/g, '\"')\n .replace(/(\\w+)\\s*:/g, '\"$1\":')\n .replace(/,\\s*}/g, '}')\n .replace(/\\/\\/[^\\n]*/g, ''); // Strip comments\n\n return { mode: 'client', ...JSON.parse(obj) };\n } catch {\n return { mode: 'client' };\n }\n}\n\n/**\n * Generate the virtual routes module source code.\n * This is what gets imported as 'virtual:what-routes'.\n */\nexport function generateRoutesModule(pagesDir, rootDir) {\n const { pages, layouts, apiRoutes } = scanPages(pagesDir);\n\n const imports = [];\n const routeEntries = [];\n\n // Generate layout imports\n const layoutMap = new Map();\n layouts.forEach((layout, i) => {\n const varName = `_layout${i}`;\n const relPath = toImportPath(layout.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n layoutMap.set(layout.urlPrefix, varName);\n });\n\n // Generate page imports and route entries\n pages.forEach((page, i) => {\n const varName = `_page${i}`;\n const relPath = toImportPath(page.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n\n // Read file to extract page config\n let pageConfig = { mode: 'client' };\n try {\n const source = fs.readFileSync(page.filePath, 'utf-8');\n pageConfig = extractPageConfig(source);\n } catch {}\n\n // Find matching layout (closest parent)\n const layoutVar = findLayout(page.routePath, layoutMap);\n\n const entry = {\n path: page.routePath,\n component: varName,\n mode: pageConfig.mode || 'client',\n layout: layoutVar || null,\n };\n\n routeEntries.push(entry);\n });\n\n // Generate API route entries\n const apiEntries = [];\n apiRoutes.forEach((route, i) => {\n const varName = `_api${i}`;\n const relPath = toImportPath(route.filePath, rootDir);\n imports.push(`import * as ${varName} from '${relPath}';`);\n apiEntries.push({\n path: route.routePath,\n handlers: varName,\n });\n });\n\n // Build the module\n const lines = [\n '// Auto-generated by What Framework file router',\n '// Do not edit \u2014 changes will be overwritten',\n '',\n ...imports,\n '',\n 'export const routes = [',\n ...routeEntries.map(r =>\n ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ''} },`\n ),\n '];',\n '',\n `export const apiRoutes = [`,\n ...apiEntries.map(r =>\n ` { path: '${r.path}', handlers: ${r.handlers} },`\n ),\n '];',\n '',\n // Export page modes for the build system\n 'export const pageModes = {',\n ...routeEntries.map(r =>\n ` '${r.path}': '${r.mode}',`\n ),\n '};',\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Convert absolute file path to a root-relative import path.\n */\nfunction toImportPath(filePath, rootDir) {\n const rel = path.relative(rootDir, filePath);\n // Ensure forward slashes and starts with /\n return '/' + rel.split(path.sep).join('/');\n}\n\n/**\n * Find the closest layout for a given route path.\n */\nfunction findLayout(routePath, layoutMap) {\n // Walk up from the route path to find the nearest layout\n const segments = routePath.split('/').filter(Boolean);\n\n while (segments.length > 0) {\n const prefix = '/' + segments.join('/');\n if (layoutMap.has(prefix)) return layoutMap.get(prefix);\n segments.pop();\n }\n\n // Check root layout\n if (layoutMap.has('/')) return layoutMap.get('/');\n return null;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * File-Based Router for What Framework\n *\n * Scans a pages directory and generates route configuration.\n *\n * File conventions:\n * src/pages/index.jsx \u2192 /\n * src/pages/about.jsx \u2192 /about\n * src/pages/blog/index.jsx \u2192 /blog\n * src/pages/blog/[slug].jsx \u2192 /blog/:slug\n * src/pages/[...path].jsx \u2192 catch-all\n * src/pages/_layout.jsx \u2192 layout for that directory\n * src/pages/(auth)/login.jsx \u2192 /login (group doesn't affect URL)\n * src/pages/api/users.js \u2192 API route: /api/users\n *\n * Page declarations (optional export in each page file):\n * export const page = {\n * mode: 'client', // default \u2014 SPA, JS required\n * mode: 'server', // SSR on every request\n * mode: 'static', // pre-rendered at build time\n * mode: 'hybrid', // static HTML shell + interactive islands\n * };\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nconst PAGE_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.ts']);\nconst IGNORED_FILES = new Set(['_layout', '_error', '_loading', '_404']);\n\n/**\n * Scan a directory recursively and return all page files.\n */\nexport function scanPages(pagesDir) {\n const pages = [];\n const layouts = [];\n const apiRoutes = [];\n\n function walk(dir, urlPrefix = '') {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n // Route groups: (name)/ \u2014 strip from URL\n const groupMatch = entry.name.match(/^\\((.+)\\)$/);\n if (groupMatch) {\n walk(fullPath, urlPrefix); // Same URL prefix\n continue;\n }\n\n // API directory\n if (entry.name === 'api' && urlPrefix === '') {\n walkApi(fullPath, '/api');\n continue;\n }\n\n walk(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n // Only process page extensions\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n\n // Layout files\n if (baseName === '_layout') {\n layouts.push({\n filePath: fullPath,\n urlPrefix: urlPrefix || '/',\n });\n continue;\n }\n\n // Error/loading/404 boundaries (reserved names)\n if (IGNORED_FILES.has(baseName)) continue;\n\n // Convert file name to URL segment\n const urlSegment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? (urlPrefix || '/')\n : urlPrefix + '/' + urlSegment;\n\n pages.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n isDynamic: routePath.includes(':') || routePath.includes('*'),\n });\n }\n }\n\n function walkApi(dir, urlPrefix) {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n walkApi(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n const segment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? urlPrefix\n : urlPrefix + '/' + segment;\n\n apiRoutes.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n });\n }\n }\n\n walk(pagesDir);\n\n // Sort: static routes first, then dynamic, then catch-all\n pages.sort((a, b) => {\n const aWeight = routeWeight(a.routePath);\n const bWeight = routeWeight(b.routePath);\n return aWeight - bWeight;\n });\n\n return { pages, layouts, apiRoutes };\n}\n\n/**\n * Convert a file name to a URL segment.\n * [slug] \u2192 :slug\n * [...path] \u2192 *path (catch-all)\n * about \u2192 about\n */\nfunction fileNameToSegment(name) {\n // Catch-all: [...param]\n const catchAll = name.match(/^\\[\\.\\.\\.(\\w+)\\]$/);\n if (catchAll) return '*' + catchAll[1];\n\n // Dynamic: [param]\n const dynamic = name.match(/^\\[(\\w+)\\]$/);\n if (dynamic) return ':' + dynamic[1];\n\n // Lowercase page names for URL consistency (About.jsx \u2192 /about)\n return name.toLowerCase();\n}\n\n/**\n * Normalize a route path.\n */\nfunction normalizePath(p) {\n // Remove double slashes\n let result = p.replace(/\\/+/g, '/');\n // Remove trailing slash (except root)\n if (result.length > 1 && result.endsWith('/')) {\n result = result.slice(0, -1);\n }\n return result || '/';\n}\n\n/**\n * Route weight for sorting \u2014 static routes first.\n */\nfunction routeWeight(path) {\n if (path.includes('*')) return 100; // Catch-all last\n if (path.includes(':')) return 10; // Dynamic middle\n return 0; // Static first\n}\n\n/**\n * Extract `export const page = { ... }` from a file's source code.\n * Uses simple regex \u2014 doesn't need a full parser for this.\n */\nexport function extractPageConfig(source) {\n // Match: export const page = { ... }\n // Handles single-line and simple multi-line objects\n const match = source.match(\n /export\\s+const\\s+page\\s*=\\s*(\\{[^}]*\\})/s\n );\n\n if (!match) {\n return { mode: 'client' }; // Default\n }\n\n try {\n // Simple evaluation of the object literal\n // Only supports string/boolean/number literals for safety\n const obj = match[1]\n .replace(/'/g, '\"')\n .replace(/(\\w+)\\s*:/g, '\"$1\":')\n .replace(/,\\s*}/g, '}')\n .replace(/\\/\\/[^\\n]*/g, ''); // Strip comments\n\n return { mode: 'client', ...JSON.parse(obj) };\n } catch {\n return { mode: 'client' };\n }\n}\n\n/**\n * Detect which named exports a page module declares. Functions (loader,\n * getStaticPaths) cannot live in `export const page` (JSON-only), so the codegen\n * imports them as live bindings instead. \\b anchors avoid false positives like\n * `loaderState` or `getStaticPathsHelper`.\n */\nexport function detectPageExports(source) {\n return {\n hasLoader: /export\\s+(?:async\\s+)?(?:const|let|var|function)\\s+loader\\b/.test(source),\n hasGetStaticPaths: /export\\s+(?:async\\s+)?(?:const|let|var|function)\\s+getStaticPaths\\b/.test(source),\n hasPageConfig: /export\\s+const\\s+page\\b/.test(source),\n };\n}\n\n/**\n * Generate the virtual routes module source code.\n * This is what gets imported as 'virtual:what-routes'.\n */\nexport function generateRoutesModule(pagesDir, rootDir) {\n const { pages, layouts, apiRoutes } = scanPages(pagesDir);\n\n const imports = [];\n const routeEntries = [];\n\n // Generate layout imports\n const layoutMap = new Map();\n layouts.forEach((layout, i) => {\n const varName = `_layout${i}`;\n const relPath = toImportPath(layout.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n layoutMap.set(layout.urlPrefix, varName);\n });\n\n // Generate page imports and route entries\n pages.forEach((page, i) => {\n const varName = `_page${i}`;\n const relPath = toImportPath(page.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n\n // Read file to extract page config + detect loader/getStaticPaths exports\n let pageConfig = { mode: 'client' };\n let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };\n try {\n const source = fs.readFileSync(page.filePath, 'utf-8');\n pageConfig = extractPageConfig(source);\n detected = detectPageExports(source);\n } catch {}\n\n // Find matching layout (closest parent)\n const layoutVar = findLayout(page.routePath, layoutMap);\n\n const entry = {\n path: page.routePath,\n component: varName,\n mode: pageConfig.mode || 'client',\n layout: layoutVar || null,\n hasLoader: detected.hasLoader,\n };\n\n routeEntries.push(entry);\n });\n\n // Generate API route entries\n const apiEntries = [];\n apiRoutes.forEach((route, i) => {\n const varName = `_api${i}`;\n const relPath = toImportPath(route.filePath, rootDir);\n imports.push(`import * as ${varName} from '${relPath}';`);\n apiEntries.push({\n path: route.routePath,\n handlers: varName,\n });\n });\n\n // Build the module\n const lines = [\n '// Auto-generated by What Framework file router',\n '// Do not edit \u2014 changes will be overwritten',\n '',\n ...imports,\n '',\n 'export const routes = [',\n ...routeEntries.map(r =>\n ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ''}${r.hasLoader ? ', hasLoader: true' : ''} },`\n ),\n '];',\n '',\n `export const apiRoutes = [`,\n ...apiEntries.map(r =>\n ` { path: '${r.path}', handlers: ${r.handlers} },`\n ),\n '];',\n '',\n // Export page modes for the build system\n 'export const pageModes = {',\n ...routeEntries.map(r =>\n ` '${r.path}': '${r.mode}',`\n ),\n '};',\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Generate the SERVER routes module ('virtual:what-routes/server'). Same routes\n * as the client module PLUS live bindings for loader / getStaticPaths / page so\n * the deploy adapter can run them. The client module (above) deliberately omits\n * these so server loaders are never bundled into the browser.\n */\nexport function generateServerRoutesModule(pagesDir, rootDir) {\n const { pages, layouts, apiRoutes } = scanPages(pagesDir);\n\n const imports = [];\n const routeEntries = [];\n\n const layoutMap = new Map();\n layouts.forEach((layout, i) => {\n const varName = `_layout${i}`;\n imports.push(`import ${varName} from '${toImportPath(layout.filePath, rootDir)}';`);\n layoutMap.set(layout.urlPrefix, varName);\n });\n\n pages.forEach((page, i) => {\n const def = `_page${i}`;\n const ns = `_page${i}_ns`;\n const relPath = toImportPath(page.filePath, rootDir);\n\n let pageConfig = { mode: 'client' };\n let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };\n try {\n const source = fs.readFileSync(page.filePath, 'utf-8');\n pageConfig = extractPageConfig(source);\n detected = detectPageExports(source);\n } catch {}\n\n const needsNs = detected.hasLoader || detected.hasGetStaticPaths || detected.hasPageConfig;\n if (needsNs) {\n imports.push(`import ${def}, * as ${ns} from '${relPath}';`);\n } else {\n imports.push(`import ${def} from '${relPath}';`);\n }\n\n routeEntries.push({\n path: page.routePath,\n component: def,\n ns,\n mode: pageConfig.mode || 'client',\n layout: findLayout(page.routePath, layoutMap) || null,\n ...detected,\n });\n });\n\n const apiEntries = [];\n apiRoutes.forEach((route, i) => {\n const varName = `_api${i}`;\n imports.push(`import * as ${varName} from '${toImportPath(route.filePath, rootDir)}';`);\n apiEntries.push({ path: route.routePath, handlers: varName });\n });\n\n const routeLine = (r) =>\n ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'` +\n `${r.layout ? `, layout: ${r.layout}` : ''}` +\n `${r.hasLoader ? `, loader: ${r.ns}.loader` : ''}` +\n `${r.hasGetStaticPaths ? `, getStaticPaths: ${r.ns}.getStaticPaths` : ''}` +\n `${r.hasPageConfig ? `, page: ${r.ns}.page` : ''} },`;\n\n const lines = [\n '// Auto-generated by What Framework file router (server)',\n '// Do not edit \u2014 changes will be overwritten',\n '',\n ...imports,\n '',\n 'export const routes = [',\n ...routeEntries.map(routeLine),\n '];',\n '',\n 'export const apiRoutes = [',\n ...apiEntries.map(r => ` { path: '${r.path}', handlers: ${r.handlers} },`),\n '];',\n '',\n 'export const pageModes = {',\n ...routeEntries.map(r => ` '${r.path}': '${r.mode}',`),\n '};',\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Convert absolute file path to a root-relative import path.\n */\nfunction toImportPath(filePath, rootDir) {\n const rel = path.relative(rootDir, filePath);\n // Ensure forward slashes and starts with /\n return '/' + rel.split(path.sep).join('/');\n}\n\n/**\n * Find the closest layout for a given route path.\n */\nfunction findLayout(routePath, layoutMap) {\n // Walk up from the route path to find the nearest layout\n const segments = routePath.split('/').filter(Boolean);\n\n while (segments.length > 0) {\n const prefix = '/' + segments.join('/');\n if (layoutMap.has(prefix)) return layoutMap.get(prefix);\n segments.pop();\n }\n\n // Check root layout\n if (layoutMap.has('/')) return layoutMap.get('/');\n return null;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["path"]
7
7
  }
@@ -1,3 +1,4 @@
1
- import d from"fs";import p from"path";var w=new Set([".jsx",".tsx",".js",".ts"]),_=new Set(["_layout","_error","_loading","_404"]);function b(n){let e=[],c=[],m=[];function g(r,a=""){if(!d.existsSync(r))return;let h=d.readdirSync(r,{withFileTypes:!0});for(let o of h){let t=p.join(r,o.name);if(o.isDirectory()){if(o.name.match(/^\((.+)\)$/)){g(t,a);continue}if(o.name==="api"&&a===""){f(t,"/api");continue}g(t,a+"/"+y(o.name));continue}let i=p.extname(o.name);if(!w.has(i))continue;let s=p.basename(o.name,i);if(s==="_layout"){c.push({filePath:t,urlPrefix:a||"/"});continue}if(_.has(s))continue;let u=y(s),l=s==="index"?a||"/":a+"/"+u;e.push({filePath:t,routePath:x(l),isDynamic:l.includes(":")||l.includes("*")})}}function f(r,a){if(!d.existsSync(r))return;let h=d.readdirSync(r,{withFileTypes:!0});for(let o of h){let t=p.join(r,o.name);if(o.isDirectory()){f(t,a+"/"+y(o.name));continue}let i=p.extname(o.name);if(!w.has(i))continue;let s=p.basename(o.name,i),u=y(s),l=s==="index"?a:a+"/"+u;m.push({filePath:t,routePath:x(l)})}}return g(n),e.sort((r,a)=>{let h=S(r.routePath),o=S(a.routePath);return h-o}),{pages:e,layouts:c,apiRoutes:m}}function y(n){let e=n.match(/^\[\.\.\.(\w+)\]$/);if(e)return"*"+e[1];let c=n.match(/^\[(\w+)\]$/);return c?":"+c[1]:n.toLowerCase()}function x(n){let e=n.replace(/\/+/g,"/");return e.length>1&&e.endsWith("/")&&(e=e.slice(0,-1)),e||"/"}function S(n){return n.includes("*")?100:n.includes(":")?10:0}function j(n){let e=n.match(/export\s+const\s+page\s*=\s*(\{[^}]*\})/s);if(!e)return{mode:"client"};try{let c=e[1].replace(/'/g,'"').replace(/(\w+)\s*:/g,'"$1":').replace(/,\s*}/g,"}").replace(/\/\/[^\n]*/g,"");return{mode:"client",...JSON.parse(c)}}catch{return{mode:"client"}}}function W(n,e){let{pages:c,layouts:m,apiRoutes:g}=b(n),f=[],r=[],a=new Map;m.forEach((t,i)=>{let s=`_layout${i}`,u=$(t.filePath,e);f.push(`import ${s} from '${u}';`),a.set(t.urlPrefix,s)}),c.forEach((t,i)=>{let s=`_page${i}`,u=$(t.filePath,e);f.push(`import ${s} from '${u}';`);let l={mode:"client"};try{let N=d.readFileSync(t.filePath,"utf-8");l=j(N)}catch{}let P=v(t.routePath,a),E={path:t.routePath,component:s,mode:l.mode||"client",layout:P||null};r.push(E)});let h=[];return g.forEach((t,i)=>{let s=`_api${i}`,u=$(t.filePath,e);f.push(`import * as ${s} from '${u}';`),h.push({path:t.routePath,handlers:s})}),["// Auto-generated by What Framework file router","// Do not edit \u2014 changes will be overwritten","",...f,"","export const routes = [",...r.map(t=>` { path: '${t.path}', component: ${t.component}, mode: '${t.mode}'${t.layout?`, layout: ${t.layout}`:""} },`),"];","","export const apiRoutes = [",...h.map(t=>` { path: '${t.path}', handlers: ${t.handlers} },`),"];","","export const pageModes = {",...r.map(t=>` '${t.path}': '${t.mode}',`),"};"].join(`
2
- `)}function $(n,e){return"/"+p.relative(e,n).split(p.sep).join("/")}function v(n,e){let c=n.split("/").filter(Boolean);for(;c.length>0;){let m="/"+c.join("/");if(e.has(m))return e.get(m);c.pop()}return e.has("/")?e.get("/"):null}export{j as extractPageConfig,W as generateRoutesModule,b as scanPages};
1
+ import P from"fs";import m from"path";var _=new Set([".jsx",".tsx",".js",".ts"]),R=new Set(["_layout","_error","_loading","_404"]);function L(o){let n=[],h=[],f=[];function d(i,c=""){if(!P.existsSync(i))return;let l=P.readdirSync(i,{withFileTypes:!0});for(let r of l){let e=m.join(i,r.name);if(r.isDirectory()){if(r.name.match(/^\((.+)\)$/)){d(e,c);continue}if(r.name==="api"&&c===""){u(e,"/api");continue}d(e,c+"/"+S(r.name));continue}let t=m.extname(r.name);if(!_.has(t))continue;let a=m.basename(r.name,t);if(a==="_layout"){h.push({filePath:e,urlPrefix:c||"/"});continue}if(R.has(a))continue;let s=S(a),p=a==="index"?c||"/":c+"/"+s;n.push({filePath:e,routePath:N(p),isDynamic:p.includes(":")||p.includes("*")})}}function u(i,c){if(!P.existsSync(i))return;let l=P.readdirSync(i,{withFileTypes:!0});for(let r of l){let e=m.join(i,r.name);if(r.isDirectory()){u(e,c+"/"+S(r.name));continue}let t=m.extname(r.name);if(!_.has(t))continue;let a=m.basename(r.name,t),s=S(a),p=a==="index"?c:c+"/"+s;f.push({filePath:e,routePath:N(p)})}}return d(o),n.sort((i,c)=>{let l=b(i.routePath),r=b(c.routePath);return l-r}),{pages:n,layouts:h,apiRoutes:f}}function S(o){let n=o.match(/^\[\.\.\.(\w+)\]$/);if(n)return"*"+n[1];let h=o.match(/^\[(\w+)\]$/);return h?":"+h[1]:o.toLowerCase()}function N(o){let n=o.replace(/\/+/g,"/");return n.length>1&&n.endsWith("/")&&(n=n.slice(0,-1)),n||"/"}function b(o){return o.includes("*")?100:o.includes(":")?10:0}function v(o){let n=o.match(/export\s+const\s+page\s*=\s*(\{[^}]*\})/s);if(!n)return{mode:"client"};try{let h=n[1].replace(/'/g,'"').replace(/(\w+)\s*:/g,'"$1":').replace(/,\s*}/g,"}").replace(/\/\/[^\n]*/g,"");return{mode:"client",...JSON.parse(h)}}catch{return{mode:"client"}}}function j(o){return{hasLoader:/export\s+(?:async\s+)?(?:const|let|var|function)\s+loader\b/.test(o),hasGetStaticPaths:/export\s+(?:async\s+)?(?:const|let|var|function)\s+getStaticPaths\b/.test(o),hasPageConfig:/export\s+const\s+page\b/.test(o)}}function M(o,n){let{pages:h,layouts:f,apiRoutes:d}=L(o),u=[],i=[],c=new Map;f.forEach((e,t)=>{let a=`_layout${t}`,s=y(e.filePath,n);u.push(`import ${a} from '${s}';`),c.set(e.urlPrefix,a)}),h.forEach((e,t)=>{let a=`_page${t}`,s=y(e.filePath,n);u.push(`import ${a} from '${s}';`);let p={mode:"client"},$={hasLoader:!1,hasGetStaticPaths:!1,hasPageConfig:!1};try{let w=P.readFileSync(e.filePath,"utf-8");p=v(w),$=j(w)}catch{}let x=C(e.routePath,c),g={path:e.routePath,component:a,mode:p.mode||"client",layout:x||null,hasLoader:$.hasLoader};i.push(g)});let l=[];return d.forEach((e,t)=>{let a=`_api${t}`,s=y(e.filePath,n);u.push(`import * as ${a} from '${s}';`),l.push({path:e.routePath,handlers:a})}),["// Auto-generated by What Framework file router","// Do not edit \u2014 changes will be overwritten","",...u,"","export const routes = [",...i.map(e=>` { path: '${e.path}', component: ${e.component}, mode: '${e.mode}'${e.layout?`, layout: ${e.layout}`:""}${e.hasLoader?", hasLoader: true":""} },`),"];","","export const apiRoutes = [",...l.map(e=>` { path: '${e.path}', handlers: ${e.handlers} },`),"];","","export const pageModes = {",...i.map(e=>` '${e.path}': '${e.mode}',`),"};"].join(`
2
+ `)}function W(o,n){let{pages:h,layouts:f,apiRoutes:d}=L(o),u=[],i=[],c=new Map;f.forEach((t,a)=>{let s=`_layout${a}`;u.push(`import ${s} from '${y(t.filePath,n)}';`),c.set(t.urlPrefix,s)}),h.forEach((t,a)=>{let s=`_page${a}`,p=`_page${a}_ns`,$=y(t.filePath,n),x={mode:"client"},g={hasLoader:!1,hasGetStaticPaths:!1,hasPageConfig:!1};try{let E=P.readFileSync(t.filePath,"utf-8");x=v(E),g=j(E)}catch{}g.hasLoader||g.hasGetStaticPaths||g.hasPageConfig?u.push(`import ${s}, * as ${p} from '${$}';`):u.push(`import ${s} from '${$}';`),i.push({path:t.routePath,component:s,ns:p,mode:x.mode||"client",layout:C(t.routePath,c)||null,...g})});let l=[];d.forEach((t,a)=>{let s=`_api${a}`;u.push(`import * as ${s} from '${y(t.filePath,n)}';`),l.push({path:t.routePath,handlers:s})});let r=t=>` { path: '${t.path}', component: ${t.component}, mode: '${t.mode}'${t.layout?`, layout: ${t.layout}`:""}${t.hasLoader?`, loader: ${t.ns}.loader`:""}${t.hasGetStaticPaths?`, getStaticPaths: ${t.ns}.getStaticPaths`:""}${t.hasPageConfig?`, page: ${t.ns}.page`:""} },`;return["// Auto-generated by What Framework file router (server)","// Do not edit \u2014 changes will be overwritten","",...u,"","export const routes = [",...i.map(r),"];","","export const apiRoutes = [",...l.map(t=>` { path: '${t.path}', handlers: ${t.handlers} },`),"];","","export const pageModes = {",...i.map(t=>` '${t.path}': '${t.mode}',`),"};"].join(`
3
+ `)}function y(o,n){return"/"+m.relative(n,o).split(m.sep).join("/")}function C(o,n){let h=o.split("/").filter(Boolean);for(;h.length>0;){let f="/"+h.join("/");if(n.has(f))return n.get(f);h.pop()}return n.has("/")?n.get("/"):null}export{j as detectPageExports,v as extractPageConfig,M as generateRoutesModule,W as generateServerRoutesModule,L as scanPages};
3
4
  //# sourceMappingURL=file-router.min.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/file-router.js"],
4
- "sourcesContent": ["/**\n * File-Based Router for What Framework\n *\n * Scans a pages directory and generates route configuration.\n *\n * File conventions:\n * src/pages/index.jsx \u2192 /\n * src/pages/about.jsx \u2192 /about\n * src/pages/blog/index.jsx \u2192 /blog\n * src/pages/blog/[slug].jsx \u2192 /blog/:slug\n * src/pages/[...path].jsx \u2192 catch-all\n * src/pages/_layout.jsx \u2192 layout for that directory\n * src/pages/(auth)/login.jsx \u2192 /login (group doesn't affect URL)\n * src/pages/api/users.js \u2192 API route: /api/users\n *\n * Page declarations (optional export in each page file):\n * export const page = {\n * mode: 'client', // default \u2014 SPA, JS required\n * mode: 'server', // SSR on every request\n * mode: 'static', // pre-rendered at build time\n * mode: 'hybrid', // static HTML shell + interactive islands\n * };\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nconst PAGE_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.ts']);\nconst IGNORED_FILES = new Set(['_layout', '_error', '_loading', '_404']);\n\n/**\n * Scan a directory recursively and return all page files.\n */\nexport function scanPages(pagesDir) {\n const pages = [];\n const layouts = [];\n const apiRoutes = [];\n\n function walk(dir, urlPrefix = '') {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n // Route groups: (name)/ \u2014 strip from URL\n const groupMatch = entry.name.match(/^\\((.+)\\)$/);\n if (groupMatch) {\n walk(fullPath, urlPrefix); // Same URL prefix\n continue;\n }\n\n // API directory\n if (entry.name === 'api' && urlPrefix === '') {\n walkApi(fullPath, '/api');\n continue;\n }\n\n walk(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n // Only process page extensions\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n\n // Layout files\n if (baseName === '_layout') {\n layouts.push({\n filePath: fullPath,\n urlPrefix: urlPrefix || '/',\n });\n continue;\n }\n\n // Error/loading/404 boundaries (reserved names)\n if (IGNORED_FILES.has(baseName)) continue;\n\n // Convert file name to URL segment\n const urlSegment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? (urlPrefix || '/')\n : urlPrefix + '/' + urlSegment;\n\n pages.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n isDynamic: routePath.includes(':') || routePath.includes('*'),\n });\n }\n }\n\n function walkApi(dir, urlPrefix) {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n walkApi(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n const segment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? urlPrefix\n : urlPrefix + '/' + segment;\n\n apiRoutes.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n });\n }\n }\n\n walk(pagesDir);\n\n // Sort: static routes first, then dynamic, then catch-all\n pages.sort((a, b) => {\n const aWeight = routeWeight(a.routePath);\n const bWeight = routeWeight(b.routePath);\n return aWeight - bWeight;\n });\n\n return { pages, layouts, apiRoutes };\n}\n\n/**\n * Convert a file name to a URL segment.\n * [slug] \u2192 :slug\n * [...path] \u2192 *path (catch-all)\n * about \u2192 about\n */\nfunction fileNameToSegment(name) {\n // Catch-all: [...param]\n const catchAll = name.match(/^\\[\\.\\.\\.(\\w+)\\]$/);\n if (catchAll) return '*' + catchAll[1];\n\n // Dynamic: [param]\n const dynamic = name.match(/^\\[(\\w+)\\]$/);\n if (dynamic) return ':' + dynamic[1];\n\n // Lowercase page names for URL consistency (About.jsx \u2192 /about)\n return name.toLowerCase();\n}\n\n/**\n * Normalize a route path.\n */\nfunction normalizePath(p) {\n // Remove double slashes\n let result = p.replace(/\\/+/g, '/');\n // Remove trailing slash (except root)\n if (result.length > 1 && result.endsWith('/')) {\n result = result.slice(0, -1);\n }\n return result || '/';\n}\n\n/**\n * Route weight for sorting \u2014 static routes first.\n */\nfunction routeWeight(path) {\n if (path.includes('*')) return 100; // Catch-all last\n if (path.includes(':')) return 10; // Dynamic middle\n return 0; // Static first\n}\n\n/**\n * Extract `export const page = { ... }` from a file's source code.\n * Uses simple regex \u2014 doesn't need a full parser for this.\n */\nexport function extractPageConfig(source) {\n // Match: export const page = { ... }\n // Handles single-line and simple multi-line objects\n const match = source.match(\n /export\\s+const\\s+page\\s*=\\s*(\\{[^}]*\\})/s\n );\n\n if (!match) {\n return { mode: 'client' }; // Default\n }\n\n try {\n // Simple evaluation of the object literal\n // Only supports string/boolean/number literals for safety\n const obj = match[1]\n .replace(/'/g, '\"')\n .replace(/(\\w+)\\s*:/g, '\"$1\":')\n .replace(/,\\s*}/g, '}')\n .replace(/\\/\\/[^\\n]*/g, ''); // Strip comments\n\n return { mode: 'client', ...JSON.parse(obj) };\n } catch {\n return { mode: 'client' };\n }\n}\n\n/**\n * Generate the virtual routes module source code.\n * This is what gets imported as 'virtual:what-routes'.\n */\nexport function generateRoutesModule(pagesDir, rootDir) {\n const { pages, layouts, apiRoutes } = scanPages(pagesDir);\n\n const imports = [];\n const routeEntries = [];\n\n // Generate layout imports\n const layoutMap = new Map();\n layouts.forEach((layout, i) => {\n const varName = `_layout${i}`;\n const relPath = toImportPath(layout.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n layoutMap.set(layout.urlPrefix, varName);\n });\n\n // Generate page imports and route entries\n pages.forEach((page, i) => {\n const varName = `_page${i}`;\n const relPath = toImportPath(page.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n\n // Read file to extract page config\n let pageConfig = { mode: 'client' };\n try {\n const source = fs.readFileSync(page.filePath, 'utf-8');\n pageConfig = extractPageConfig(source);\n } catch {}\n\n // Find matching layout (closest parent)\n const layoutVar = findLayout(page.routePath, layoutMap);\n\n const entry = {\n path: page.routePath,\n component: varName,\n mode: pageConfig.mode || 'client',\n layout: layoutVar || null,\n };\n\n routeEntries.push(entry);\n });\n\n // Generate API route entries\n const apiEntries = [];\n apiRoutes.forEach((route, i) => {\n const varName = `_api${i}`;\n const relPath = toImportPath(route.filePath, rootDir);\n imports.push(`import * as ${varName} from '${relPath}';`);\n apiEntries.push({\n path: route.routePath,\n handlers: varName,\n });\n });\n\n // Build the module\n const lines = [\n '// Auto-generated by What Framework file router',\n '// Do not edit \u2014 changes will be overwritten',\n '',\n ...imports,\n '',\n 'export const routes = [',\n ...routeEntries.map(r =>\n ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ''} },`\n ),\n '];',\n '',\n `export const apiRoutes = [`,\n ...apiEntries.map(r =>\n ` { path: '${r.path}', handlers: ${r.handlers} },`\n ),\n '];',\n '',\n // Export page modes for the build system\n 'export const pageModes = {',\n ...routeEntries.map(r =>\n ` '${r.path}': '${r.mode}',`\n ),\n '};',\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Convert absolute file path to a root-relative import path.\n */\nfunction toImportPath(filePath, rootDir) {\n const rel = path.relative(rootDir, filePath);\n // Ensure forward slashes and starts with /\n return '/' + rel.split(path.sep).join('/');\n}\n\n/**\n * Find the closest layout for a given route path.\n */\nfunction findLayout(routePath, layoutMap) {\n // Walk up from the route path to find the nearest layout\n const segments = routePath.split('/').filter(Boolean);\n\n while (segments.length > 0) {\n const prefix = '/' + segments.join('/');\n if (layoutMap.has(prefix)) return layoutMap.get(prefix);\n segments.pop();\n }\n\n // Check root layout\n if (layoutMap.has('/')) return layoutMap.get('/');\n return null;\n}\n"],
5
- "mappings": "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",
6
- "names": ["fs", "path", "PAGE_EXTENSIONS", "IGNORED_FILES", "scanPages", "pagesDir", "pages", "layouts", "apiRoutes", "walk", "dir", "urlPrefix", "entries", "entry", "fullPath", "walkApi", "fileNameToSegment", "ext", "baseName", "urlSegment", "routePath", "normalizePath", "segment", "a", "b", "aWeight", "routeWeight", "bWeight", "name", "catchAll", "dynamic", "p", "result", "extractPageConfig", "source", "match", "obj", "generateRoutesModule", "rootDir", "imports", "routeEntries", "layoutMap", "layout", "varName", "relPath", "toImportPath", "page", "pageConfig", "layoutVar", "findLayout", "apiEntries", "route", "r", "filePath", "segments", "prefix"]
4
+ "sourcesContent": ["/**\n * File-Based Router for What Framework\n *\n * Scans a pages directory and generates route configuration.\n *\n * File conventions:\n * src/pages/index.jsx \u2192 /\n * src/pages/about.jsx \u2192 /about\n * src/pages/blog/index.jsx \u2192 /blog\n * src/pages/blog/[slug].jsx \u2192 /blog/:slug\n * src/pages/[...path].jsx \u2192 catch-all\n * src/pages/_layout.jsx \u2192 layout for that directory\n * src/pages/(auth)/login.jsx \u2192 /login (group doesn't affect URL)\n * src/pages/api/users.js \u2192 API route: /api/users\n *\n * Page declarations (optional export in each page file):\n * export const page = {\n * mode: 'client', // default \u2014 SPA, JS required\n * mode: 'server', // SSR on every request\n * mode: 'static', // pre-rendered at build time\n * mode: 'hybrid', // static HTML shell + interactive islands\n * };\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nconst PAGE_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.ts']);\nconst IGNORED_FILES = new Set(['_layout', '_error', '_loading', '_404']);\n\n/**\n * Scan a directory recursively and return all page files.\n */\nexport function scanPages(pagesDir) {\n const pages = [];\n const layouts = [];\n const apiRoutes = [];\n\n function walk(dir, urlPrefix = '') {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n // Route groups: (name)/ \u2014 strip from URL\n const groupMatch = entry.name.match(/^\\((.+)\\)$/);\n if (groupMatch) {\n walk(fullPath, urlPrefix); // Same URL prefix\n continue;\n }\n\n // API directory\n if (entry.name === 'api' && urlPrefix === '') {\n walkApi(fullPath, '/api');\n continue;\n }\n\n walk(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n // Only process page extensions\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n\n // Layout files\n if (baseName === '_layout') {\n layouts.push({\n filePath: fullPath,\n urlPrefix: urlPrefix || '/',\n });\n continue;\n }\n\n // Error/loading/404 boundaries (reserved names)\n if (IGNORED_FILES.has(baseName)) continue;\n\n // Convert file name to URL segment\n const urlSegment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? (urlPrefix || '/')\n : urlPrefix + '/' + urlSegment;\n\n pages.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n isDynamic: routePath.includes(':') || routePath.includes('*'),\n });\n }\n }\n\n function walkApi(dir, urlPrefix) {\n if (!fs.existsSync(dir)) return;\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n if (entry.isDirectory()) {\n walkApi(fullPath, urlPrefix + '/' + fileNameToSegment(entry.name));\n continue;\n }\n\n const ext = path.extname(entry.name);\n if (!PAGE_EXTENSIONS.has(ext)) continue;\n\n const baseName = path.basename(entry.name, ext);\n const segment = fileNameToSegment(baseName);\n const routePath = baseName === 'index'\n ? urlPrefix\n : urlPrefix + '/' + segment;\n\n apiRoutes.push({\n filePath: fullPath,\n routePath: normalizePath(routePath),\n });\n }\n }\n\n walk(pagesDir);\n\n // Sort: static routes first, then dynamic, then catch-all\n pages.sort((a, b) => {\n const aWeight = routeWeight(a.routePath);\n const bWeight = routeWeight(b.routePath);\n return aWeight - bWeight;\n });\n\n return { pages, layouts, apiRoutes };\n}\n\n/**\n * Convert a file name to a URL segment.\n * [slug] \u2192 :slug\n * [...path] \u2192 *path (catch-all)\n * about \u2192 about\n */\nfunction fileNameToSegment(name) {\n // Catch-all: [...param]\n const catchAll = name.match(/^\\[\\.\\.\\.(\\w+)\\]$/);\n if (catchAll) return '*' + catchAll[1];\n\n // Dynamic: [param]\n const dynamic = name.match(/^\\[(\\w+)\\]$/);\n if (dynamic) return ':' + dynamic[1];\n\n // Lowercase page names for URL consistency (About.jsx \u2192 /about)\n return name.toLowerCase();\n}\n\n/**\n * Normalize a route path.\n */\nfunction normalizePath(p) {\n // Remove double slashes\n let result = p.replace(/\\/+/g, '/');\n // Remove trailing slash (except root)\n if (result.length > 1 && result.endsWith('/')) {\n result = result.slice(0, -1);\n }\n return result || '/';\n}\n\n/**\n * Route weight for sorting \u2014 static routes first.\n */\nfunction routeWeight(path) {\n if (path.includes('*')) return 100; // Catch-all last\n if (path.includes(':')) return 10; // Dynamic middle\n return 0; // Static first\n}\n\n/**\n * Extract `export const page = { ... }` from a file's source code.\n * Uses simple regex \u2014 doesn't need a full parser for this.\n */\nexport function extractPageConfig(source) {\n // Match: export const page = { ... }\n // Handles single-line and simple multi-line objects\n const match = source.match(\n /export\\s+const\\s+page\\s*=\\s*(\\{[^}]*\\})/s\n );\n\n if (!match) {\n return { mode: 'client' }; // Default\n }\n\n try {\n // Simple evaluation of the object literal\n // Only supports string/boolean/number literals for safety\n const obj = match[1]\n .replace(/'/g, '\"')\n .replace(/(\\w+)\\s*:/g, '\"$1\":')\n .replace(/,\\s*}/g, '}')\n .replace(/\\/\\/[^\\n]*/g, ''); // Strip comments\n\n return { mode: 'client', ...JSON.parse(obj) };\n } catch {\n return { mode: 'client' };\n }\n}\n\n/**\n * Detect which named exports a page module declares. Functions (loader,\n * getStaticPaths) cannot live in `export const page` (JSON-only), so the codegen\n * imports them as live bindings instead. \\b anchors avoid false positives like\n * `loaderState` or `getStaticPathsHelper`.\n */\nexport function detectPageExports(source) {\n return {\n hasLoader: /export\\s+(?:async\\s+)?(?:const|let|var|function)\\s+loader\\b/.test(source),\n hasGetStaticPaths: /export\\s+(?:async\\s+)?(?:const|let|var|function)\\s+getStaticPaths\\b/.test(source),\n hasPageConfig: /export\\s+const\\s+page\\b/.test(source),\n };\n}\n\n/**\n * Generate the virtual routes module source code.\n * This is what gets imported as 'virtual:what-routes'.\n */\nexport function generateRoutesModule(pagesDir, rootDir) {\n const { pages, layouts, apiRoutes } = scanPages(pagesDir);\n\n const imports = [];\n const routeEntries = [];\n\n // Generate layout imports\n const layoutMap = new Map();\n layouts.forEach((layout, i) => {\n const varName = `_layout${i}`;\n const relPath = toImportPath(layout.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n layoutMap.set(layout.urlPrefix, varName);\n });\n\n // Generate page imports and route entries\n pages.forEach((page, i) => {\n const varName = `_page${i}`;\n const relPath = toImportPath(page.filePath, rootDir);\n imports.push(`import ${varName} from '${relPath}';`);\n\n // Read file to extract page config + detect loader/getStaticPaths exports\n let pageConfig = { mode: 'client' };\n let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };\n try {\n const source = fs.readFileSync(page.filePath, 'utf-8');\n pageConfig = extractPageConfig(source);\n detected = detectPageExports(source);\n } catch {}\n\n // Find matching layout (closest parent)\n const layoutVar = findLayout(page.routePath, layoutMap);\n\n const entry = {\n path: page.routePath,\n component: varName,\n mode: pageConfig.mode || 'client',\n layout: layoutVar || null,\n hasLoader: detected.hasLoader,\n };\n\n routeEntries.push(entry);\n });\n\n // Generate API route entries\n const apiEntries = [];\n apiRoutes.forEach((route, i) => {\n const varName = `_api${i}`;\n const relPath = toImportPath(route.filePath, rootDir);\n imports.push(`import * as ${varName} from '${relPath}';`);\n apiEntries.push({\n path: route.routePath,\n handlers: varName,\n });\n });\n\n // Build the module\n const lines = [\n '// Auto-generated by What Framework file router',\n '// Do not edit \u2014 changes will be overwritten',\n '',\n ...imports,\n '',\n 'export const routes = [',\n ...routeEntries.map(r =>\n ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ''}${r.hasLoader ? ', hasLoader: true' : ''} },`\n ),\n '];',\n '',\n `export const apiRoutes = [`,\n ...apiEntries.map(r =>\n ` { path: '${r.path}', handlers: ${r.handlers} },`\n ),\n '];',\n '',\n // Export page modes for the build system\n 'export const pageModes = {',\n ...routeEntries.map(r =>\n ` '${r.path}': '${r.mode}',`\n ),\n '};',\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Generate the SERVER routes module ('virtual:what-routes/server'). Same routes\n * as the client module PLUS live bindings for loader / getStaticPaths / page so\n * the deploy adapter can run them. The client module (above) deliberately omits\n * these so server loaders are never bundled into the browser.\n */\nexport function generateServerRoutesModule(pagesDir, rootDir) {\n const { pages, layouts, apiRoutes } = scanPages(pagesDir);\n\n const imports = [];\n const routeEntries = [];\n\n const layoutMap = new Map();\n layouts.forEach((layout, i) => {\n const varName = `_layout${i}`;\n imports.push(`import ${varName} from '${toImportPath(layout.filePath, rootDir)}';`);\n layoutMap.set(layout.urlPrefix, varName);\n });\n\n pages.forEach((page, i) => {\n const def = `_page${i}`;\n const ns = `_page${i}_ns`;\n const relPath = toImportPath(page.filePath, rootDir);\n\n let pageConfig = { mode: 'client' };\n let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };\n try {\n const source = fs.readFileSync(page.filePath, 'utf-8');\n pageConfig = extractPageConfig(source);\n detected = detectPageExports(source);\n } catch {}\n\n const needsNs = detected.hasLoader || detected.hasGetStaticPaths || detected.hasPageConfig;\n if (needsNs) {\n imports.push(`import ${def}, * as ${ns} from '${relPath}';`);\n } else {\n imports.push(`import ${def} from '${relPath}';`);\n }\n\n routeEntries.push({\n path: page.routePath,\n component: def,\n ns,\n mode: pageConfig.mode || 'client',\n layout: findLayout(page.routePath, layoutMap) || null,\n ...detected,\n });\n });\n\n const apiEntries = [];\n apiRoutes.forEach((route, i) => {\n const varName = `_api${i}`;\n imports.push(`import * as ${varName} from '${toImportPath(route.filePath, rootDir)}';`);\n apiEntries.push({ path: route.routePath, handlers: varName });\n });\n\n const routeLine = (r) =>\n ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'` +\n `${r.layout ? `, layout: ${r.layout}` : ''}` +\n `${r.hasLoader ? `, loader: ${r.ns}.loader` : ''}` +\n `${r.hasGetStaticPaths ? `, getStaticPaths: ${r.ns}.getStaticPaths` : ''}` +\n `${r.hasPageConfig ? `, page: ${r.ns}.page` : ''} },`;\n\n const lines = [\n '// Auto-generated by What Framework file router (server)',\n '// Do not edit \u2014 changes will be overwritten',\n '',\n ...imports,\n '',\n 'export const routes = [',\n ...routeEntries.map(routeLine),\n '];',\n '',\n 'export const apiRoutes = [',\n ...apiEntries.map(r => ` { path: '${r.path}', handlers: ${r.handlers} },`),\n '];',\n '',\n 'export const pageModes = {',\n ...routeEntries.map(r => ` '${r.path}': '${r.mode}',`),\n '};',\n ];\n\n return lines.join('\\n');\n}\n\n/**\n * Convert absolute file path to a root-relative import path.\n */\nfunction toImportPath(filePath, rootDir) {\n const rel = path.relative(rootDir, filePath);\n // Ensure forward slashes and starts with /\n return '/' + rel.split(path.sep).join('/');\n}\n\n/**\n * Find the closest layout for a given route path.\n */\nfunction findLayout(routePath, layoutMap) {\n // Walk up from the route path to find the nearest layout\n const segments = routePath.split('/').filter(Boolean);\n\n while (segments.length > 0) {\n const prefix = '/' + segments.join('/');\n if (layoutMap.has(prefix)) return layoutMap.get(prefix);\n segments.pop();\n }\n\n // Check root layout\n if (layoutMap.has('/')) return layoutMap.get('/');\n return null;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["fs", "path", "PAGE_EXTENSIONS", "IGNORED_FILES", "scanPages", "pagesDir", "pages", "layouts", "apiRoutes", "walk", "dir", "urlPrefix", "entries", "entry", "fullPath", "walkApi", "fileNameToSegment", "ext", "baseName", "urlSegment", "routePath", "normalizePath", "segment", "a", "b", "aWeight", "routeWeight", "bWeight", "name", "catchAll", "dynamic", "p", "result", "extractPageConfig", "source", "match", "obj", "detectPageExports", "generateRoutesModule", "rootDir", "imports", "routeEntries", "layoutMap", "layout", "i", "varName", "relPath", "toImportPath", "page", "pageConfig", "detected", "layoutVar", "findLayout", "apiEntries", "route", "r", "generateServerRoutesModule", "def", "ns", "routeLine", "filePath", "segments", "prefix"]
7
7
  }