@float.js/core 2.1.0 → 2.2.1
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/README.md +38 -0
- package/dist/cli/index.js +361 -120
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +389 -148
- package/dist/index.js.map +1 -1
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.js +32 -2
- package/dist/router/index.js.map +1 -1
- package/dist/server/index.js +331 -90
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/router/index.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ interface Route {
|
|
|
19
19
|
isOptionalCatchAll: boolean;
|
|
20
20
|
/** Nested layouts */
|
|
21
21
|
layouts: string[];
|
|
22
|
+
/** Loading component path */
|
|
23
|
+
loading?: string;
|
|
24
|
+
/** Error boundary path */
|
|
25
|
+
error?: string;
|
|
22
26
|
}
|
|
23
27
|
interface RouterOptions {
|
|
24
28
|
/** Root directory of the app (default: 'app') */
|
package/dist/router/index.js
CHANGED
|
@@ -54,6 +54,26 @@ function findLayouts(routePath, allLayouts) {
|
|
|
54
54
|
}
|
|
55
55
|
return layouts;
|
|
56
56
|
}
|
|
57
|
+
function findLoading(routePath, allLoading) {
|
|
58
|
+
const segments = routePath.split("/").filter(Boolean);
|
|
59
|
+
for (let i = segments.length; i >= 0; i--) {
|
|
60
|
+
const currentPath = i === 0 ? "/" : "/" + segments.slice(0, i).join("/");
|
|
61
|
+
if (allLoading.has(currentPath)) {
|
|
62
|
+
return allLoading.get(currentPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
function findError(routePath, allErrors) {
|
|
68
|
+
const segments = routePath.split("/").filter(Boolean);
|
|
69
|
+
for (let i = segments.length; i >= 0; i--) {
|
|
70
|
+
const currentPath = i === 0 ? "/" : "/" + segments.slice(0, i).join("/");
|
|
71
|
+
if (allErrors.has(currentPath)) {
|
|
72
|
+
return allErrors.get(currentPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
57
77
|
async function scanRoutes(rootDir, options = {}) {
|
|
58
78
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
59
79
|
const appDir = path.join(rootDir, opts.appDir);
|
|
@@ -65,12 +85,20 @@ async function scanRoutes(rootDir, options = {}) {
|
|
|
65
85
|
ignore: ["**/node_modules/**", "**/_*/**"]
|
|
66
86
|
});
|
|
67
87
|
const layoutMap = /* @__PURE__ */ new Map();
|
|
88
|
+
const loadingMap = /* @__PURE__ */ new Map();
|
|
89
|
+
const errorMap = /* @__PURE__ */ new Map();
|
|
68
90
|
for (const file of files) {
|
|
69
91
|
const type = getRouteType(file);
|
|
92
|
+
const { urlPath } = filePathToUrlPath(file, "");
|
|
70
93
|
if (type === "layout") {
|
|
71
|
-
const { urlPath } = filePathToUrlPath(file, "");
|
|
72
94
|
const layoutPath = urlPath === "/" ? "/" : urlPath.replace(/\/layout$/, "") || "/";
|
|
73
95
|
layoutMap.set(layoutPath, path.join(appDir, file));
|
|
96
|
+
} else if (type === "loading") {
|
|
97
|
+
const loadingPath = urlPath === "/" ? "/" : urlPath.replace(/\/loading$/, "") || "/";
|
|
98
|
+
loadingMap.set(loadingPath, path.join(appDir, file));
|
|
99
|
+
} else if (type === "error") {
|
|
100
|
+
const errorPath = urlPath === "/" ? "/" : urlPath.replace(/\/error$/, "") || "/";
|
|
101
|
+
errorMap.set(errorPath, path.join(appDir, file));
|
|
74
102
|
}
|
|
75
103
|
}
|
|
76
104
|
const routes = [];
|
|
@@ -88,7 +116,9 @@ async function scanRoutes(rootDir, options = {}) {
|
|
|
88
116
|
params,
|
|
89
117
|
isCatchAll,
|
|
90
118
|
isOptionalCatchAll,
|
|
91
|
-
layouts: type === "page" ? findLayouts(urlPath, layoutMap) : []
|
|
119
|
+
layouts: type === "page" ? findLayouts(urlPath, layoutMap) : [],
|
|
120
|
+
loading: type === "page" ? findLoading(urlPath, loadingMap) : void 0,
|
|
121
|
+
error: type === "page" ? findError(urlPath, errorMap) : void 0
|
|
92
122
|
};
|
|
93
123
|
routes.push(route);
|
|
94
124
|
}
|
package/dist/router/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/router/index.ts"],"sourcesContent":["/**\n * Float.js Router\n * File-based routing system\n */\n\nimport fg from 'fast-glob';\nimport path from 'node:path';\n\nexport interface Route {\n /** URL path pattern (e.g., /users/:id) */\n path: string;\n /** File path relative to app directory */\n filePath: string;\n /** Absolute file path */\n absolutePath: string;\n /** Route type */\n type: 'page' | 'layout' | 'api' | 'error' | 'loading';\n /** Dynamic segments */\n params: string[];\n /** Is catch-all route */\n isCatchAll: boolean;\n /** Is optional catch-all */\n isOptionalCatchAll: boolean;\n /** Nested layouts */\n layouts: string[];\n}\n\nexport interface RouterOptions {\n /** Root directory of the app (default: 'app') */\n appDir?: string;\n /** Base path for all routes */\n basePath?: string;\n /** File extensions to consider */\n extensions?: string[];\n}\n\nconst DEFAULT_OPTIONS: Required<RouterOptions> = {\n appDir: 'app',\n basePath: '',\n extensions: ['.tsx', '.ts', '.jsx', '.js'],\n};\n\n/**\n * Convert file path to URL path\n * \n * Examples:\n * - app/page.tsx -> /\n * - app/about/page.tsx -> /about\n * - app/users/[id]/page.tsx -> /users/:id\n * - app/docs/[...slug]/page.tsx -> /docs/*\n * - app/shop/[[...slug]]/page.tsx -> /shop/*?\n */\nfunction filePathToUrlPath(filePath: string, appDir: string): { \n urlPath: string; \n params: string[];\n isCatchAll: boolean;\n isOptionalCatchAll: boolean;\n} {\n // Remove app dir prefix and file name\n let urlPath = filePath\n .replace(new RegExp(`^${appDir}/`), '')\n .replace(/\\/?(page|layout|route|error|loading|not-found)\\.(tsx?|jsx?)$/, '');\n\n const params: string[] = [];\n let isCatchAll = false;\n let isOptionalCatchAll = false;\n\n // Convert [param] to :param\n urlPath = urlPath.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n // Optional catch-all [[...slug]]\n if (param.startsWith('...') && filePath.includes('[[')) {\n isOptionalCatchAll = true;\n const paramName = param.replace('...', '');\n params.push(paramName);\n return `*${paramName}?`;\n }\n // Catch-all [...slug]\n if (param.startsWith('...')) {\n isCatchAll = true;\n const paramName = param.replace('...', '');\n params.push(paramName);\n return `*${paramName}`;\n }\n // Regular dynamic param [id]\n params.push(param);\n return `:${param}`;\n });\n\n // Ensure leading slash\n urlPath = '/' + urlPath;\n \n // Clean up double slashes and trailing slash\n urlPath = urlPath.replace(/\\/+/g, '/').replace(/\\/$/, '') || '/';\n\n return { urlPath, params, isCatchAll, isOptionalCatchAll };\n}\n\n/**\n * Determine route type from file name\n */\nfunction getRouteType(filePath: string): Route['type'] {\n // Check if file name (without path) matches special file types\n const fileName = filePath.split('/').pop() || filePath;\n \n if (fileName.match(/^route\\.(tsx?|jsx?)$/)) return 'api';\n if (fileName.match(/^layout\\.(tsx?|jsx?)$/)) return 'layout';\n if (fileName.match(/^error\\.(tsx?|jsx?)$/)) return 'error';\n if (fileName.match(/^loading\\.(tsx?|jsx?)$/)) return 'loading';\n return 'page';\n}\n\n/**\n * Find all layouts that apply to a route\n */\nfunction findLayouts(routePath: string, allLayouts: Map<string, string>): string[] {\n const layouts: string[] = [];\n const segments = routePath.split('/').filter(Boolean);\n \n // Check from root to deepest\n let currentPath = '';\n \n // Root layout\n if (allLayouts.has('/')) {\n layouts.push(allLayouts.get('/')!);\n }\n \n for (const segment of segments) {\n currentPath += '/' + segment;\n if (allLayouts.has(currentPath)) {\n layouts.push(allLayouts.get(currentPath)!);\n }\n }\n \n return layouts;\n}\n\n/**\n * Scan app directory and build routes\n */\nexport async function scanRoutes(\n rootDir: string,\n options: RouterOptions = {}\n): Promise<Route[]> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const appDir = path.join(rootDir, opts.appDir);\n const extensions = opts.extensions.map(ext => ext.replace('.', '')).join(',');\n\n // Find all route files\n const pattern = `**/{page,layout,route,error,loading}.{${extensions}}`;\n const files = await fg(pattern, {\n cwd: appDir,\n onlyFiles: true,\n ignore: ['**/node_modules/**', '**/_*/**'],\n });\n\n // First pass: collect all layouts\n const layoutMap = new Map<string, string>();\n \n for (const file of files) {\n const type = getRouteType(file);\n if (type === 'layout') {\n const { urlPath } = filePathToUrlPath(file, '');\n const layoutPath = urlPath === '/' ? '/' : urlPath.replace(/\\/layout$/, '') || '/';\n layoutMap.set(layoutPath, path.join(appDir, file));\n }\n }\n\n // Second pass: build all routes (only pages and API routes)\n const routes: Route[] = [];\n\n for (const file of files) {\n const type = getRouteType(file);\n \n // Skip layouts, errors, loading - they are not matchable routes\n if (type === 'layout' || type === 'error' || type === 'loading') {\n continue;\n }\n const { urlPath, params, isCatchAll, isOptionalCatchAll } = filePathToUrlPath(file, '');\n \n const route: Route = {\n path: opts.basePath + urlPath,\n filePath: file,\n absolutePath: path.join(appDir, file),\n type,\n params,\n isCatchAll,\n isOptionalCatchAll,\n layouts: type === 'page' ? findLayouts(urlPath, layoutMap) : [],\n };\n\n routes.push(route);\n }\n\n // Sort routes: static first, then dynamic, catch-all last\n routes.sort((a, b) => {\n if (a.isCatchAll !== b.isCatchAll) return a.isCatchAll ? 1 : -1;\n if (a.params.length !== b.params.length) return a.params.length - b.params.length;\n return a.path.localeCompare(b.path);\n });\n\n return routes;\n}\n\n/**\n * Match a URL path to a route\n */\nexport function matchRoute(url: string, routes: Route[]): { \n route: Route | null; \n params: Record<string, string>;\n} {\n const urlParts = url.split('/').filter(Boolean);\n\n for (const route of routes) {\n if (route.type !== 'page' && route.type !== 'api') continue;\n\n const routeParts = route.path.split('/').filter(Boolean);\n const params: Record<string, string> = {};\n let matched = true;\n let urlIndex = 0;\n\n for (let i = 0; i < routeParts.length; i++) {\n const routePart = routeParts[i];\n\n // Catch-all\n if (routePart.startsWith('*')) {\n const paramName = routePart.replace(/^\\*/, '').replace(/\\?$/, '');\n params[paramName] = urlParts.slice(urlIndex).join('/');\n return { route, params };\n }\n\n // Dynamic segment\n if (routePart.startsWith(':')) {\n const paramName = routePart.slice(1);\n if (urlIndex >= urlParts.length) {\n matched = false;\n break;\n }\n params[paramName] = urlParts[urlIndex];\n urlIndex++;\n continue;\n }\n\n // Static segment\n if (urlParts[urlIndex] !== routePart) {\n matched = false;\n break;\n }\n urlIndex++;\n }\n\n // Check if we consumed all URL parts\n if (matched && urlIndex === urlParts.length) {\n return { route, params };\n }\n }\n\n return { route: null, params: {} };\n}\n\nexport type { Route as FloatRoute, RouterOptions as FloatRouterOptions };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/router/index.ts"],"sourcesContent":["/**\n * Float.js Router\n * File-based routing system\n */\n\nimport fg from 'fast-glob';\nimport path from 'node:path';\n\nexport interface Route {\n /** URL path pattern (e.g., /users/:id) */\n path: string;\n /** File path relative to app directory */\n filePath: string;\n /** Absolute file path */\n absolutePath: string;\n /** Route type */\n type: 'page' | 'layout' | 'api' | 'error' | 'loading';\n /** Dynamic segments */\n params: string[];\n /** Is catch-all route */\n isCatchAll: boolean;\n /** Is optional catch-all */\n isOptionalCatchAll: boolean;\n /** Nested layouts */\n layouts: string[];\n /** Loading component path */\n loading?: string;\n /** Error boundary path */\n error?: string;\n}\n\nexport interface RouterOptions {\n /** Root directory of the app (default: 'app') */\n appDir?: string;\n /** Base path for all routes */\n basePath?: string;\n /** File extensions to consider */\n extensions?: string[];\n}\n\nconst DEFAULT_OPTIONS: Required<RouterOptions> = {\n appDir: 'app',\n basePath: '',\n extensions: ['.tsx', '.ts', '.jsx', '.js'],\n};\n\n/**\n * Convert file path to URL path\n * \n * Examples:\n * - app/page.tsx -> /\n * - app/about/page.tsx -> /about\n * - app/users/[id]/page.tsx -> /users/:id\n * - app/docs/[...slug]/page.tsx -> /docs/*\n * - app/shop/[[...slug]]/page.tsx -> /shop/*?\n */\nfunction filePathToUrlPath(filePath: string, appDir: string): { \n urlPath: string; \n params: string[];\n isCatchAll: boolean;\n isOptionalCatchAll: boolean;\n} {\n // Remove app dir prefix and file name\n let urlPath = filePath\n .replace(new RegExp(`^${appDir}/`), '')\n .replace(/\\/?(page|layout|route|error|loading|not-found)\\.(tsx?|jsx?)$/, '');\n\n const params: string[] = [];\n let isCatchAll = false;\n let isOptionalCatchAll = false;\n\n // Convert [param] to :param\n urlPath = urlPath.replace(/\\[([^\\]]+)\\]/g, (_, param) => {\n // Optional catch-all [[...slug]]\n if (param.startsWith('...') && filePath.includes('[[')) {\n isOptionalCatchAll = true;\n const paramName = param.replace('...', '');\n params.push(paramName);\n return `*${paramName}?`;\n }\n // Catch-all [...slug]\n if (param.startsWith('...')) {\n isCatchAll = true;\n const paramName = param.replace('...', '');\n params.push(paramName);\n return `*${paramName}`;\n }\n // Regular dynamic param [id]\n params.push(param);\n return `:${param}`;\n });\n\n // Ensure leading slash\n urlPath = '/' + urlPath;\n \n // Clean up double slashes and trailing slash\n urlPath = urlPath.replace(/\\/+/g, '/').replace(/\\/$/, '') || '/';\n\n return { urlPath, params, isCatchAll, isOptionalCatchAll };\n}\n\n/**\n * Determine route type from file name\n */\nfunction getRouteType(filePath: string): Route['type'] {\n // Check if file name (without path) matches special file types\n const fileName = filePath.split('/').pop() || filePath;\n \n if (fileName.match(/^route\\.(tsx?|jsx?)$/)) return 'api';\n if (fileName.match(/^layout\\.(tsx?|jsx?)$/)) return 'layout';\n if (fileName.match(/^error\\.(tsx?|jsx?)$/)) return 'error';\n if (fileName.match(/^loading\\.(tsx?|jsx?)$/)) return 'loading';\n return 'page';\n}\n\n/**\n * Find all layouts that apply to a route\n */\nfunction findLayouts(routePath: string, allLayouts: Map<string, string>): string[] {\n const layouts: string[] = [];\n const segments = routePath.split('/').filter(Boolean);\n \n // Check from root to deepest\n let currentPath = '';\n \n // Root layout\n if (allLayouts.has('/')) {\n layouts.push(allLayouts.get('/')!);\n }\n \n for (const segment of segments) {\n currentPath += '/' + segment;\n if (allLayouts.has(currentPath)) {\n layouts.push(allLayouts.get(currentPath)!);\n }\n }\n \n return layouts;\n}\n\n/**\n * Find loading component for a route (closest ancestor)\n */\nfunction findLoading(routePath: string, allLoading: Map<string, string>): string | undefined {\n const segments = routePath.split('/').filter(Boolean);\n \n // Check from most specific to root\n for (let i = segments.length; i >= 0; i--) {\n const currentPath = i === 0 ? '/' : '/' + segments.slice(0, i).join('/');\n if (allLoading.has(currentPath)) {\n return allLoading.get(currentPath);\n }\n }\n \n return undefined;\n}\n\n/**\n * Find error boundary for a route (closest ancestor)\n */\nfunction findError(routePath: string, allErrors: Map<string, string>): string | undefined {\n const segments = routePath.split('/').filter(Boolean);\n \n // Check from most specific to root\n for (let i = segments.length; i >= 0; i--) {\n const currentPath = i === 0 ? '/' : '/' + segments.slice(0, i).join('/');\n if (allErrors.has(currentPath)) {\n return allErrors.get(currentPath);\n }\n }\n \n return undefined;\n}\n\n/**\n * Scan app directory and build routes\n */\nexport async function scanRoutes(\n rootDir: string,\n options: RouterOptions = {}\n): Promise<Route[]> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n const appDir = path.join(rootDir, opts.appDir);\n const extensions = opts.extensions.map(ext => ext.replace('.', '')).join(',');\n\n // Find all route files\n const pattern = `**/{page,layout,route,error,loading}.{${extensions}}`;\n const files = await fg(pattern, {\n cwd: appDir,\n onlyFiles: true,\n ignore: ['**/node_modules/**', '**/_*/**'],\n });\n\n // First pass: collect all layouts, loading, errors\n const layoutMap = new Map<string, string>();\n const loadingMap = new Map<string, string>();\n const errorMap = new Map<string, string>();\n \n for (const file of files) {\n const type = getRouteType(file);\n const { urlPath } = filePathToUrlPath(file, '');\n \n if (type === 'layout') {\n const layoutPath = urlPath === '/' ? '/' : urlPath.replace(/\\/layout$/, '') || '/';\n layoutMap.set(layoutPath, path.join(appDir, file));\n } else if (type === 'loading') {\n const loadingPath = urlPath === '/' ? '/' : urlPath.replace(/\\/loading$/, '') || '/';\n loadingMap.set(loadingPath, path.join(appDir, file));\n } else if (type === 'error') {\n const errorPath = urlPath === '/' ? '/' : urlPath.replace(/\\/error$/, '') || '/';\n errorMap.set(errorPath, path.join(appDir, file));\n }\n }\n\n // Second pass: build all routes (only pages and API routes)\n const routes: Route[] = [];\n\n for (const file of files) {\n const type = getRouteType(file);\n \n // Skip layouts, errors, loading - they are not matchable routes\n if (type === 'layout' || type === 'error' || type === 'loading') {\n continue;\n }\n const { urlPath, params, isCatchAll, isOptionalCatchAll } = filePathToUrlPath(file, '');\n \n const route: Route = {\n path: opts.basePath + urlPath,\n filePath: file,\n absolutePath: path.join(appDir, file),\n type,\n params,\n isCatchAll,\n isOptionalCatchAll,\n layouts: type === 'page' ? findLayouts(urlPath, layoutMap) : [],\n loading: type === 'page' ? findLoading(urlPath, loadingMap) : undefined,\n error: type === 'page' ? findError(urlPath, errorMap) : undefined,\n };\n\n routes.push(route);\n }\n\n // Sort routes: static first, then dynamic, catch-all last\n routes.sort((a, b) => {\n if (a.isCatchAll !== b.isCatchAll) return a.isCatchAll ? 1 : -1;\n if (a.params.length !== b.params.length) return a.params.length - b.params.length;\n return a.path.localeCompare(b.path);\n });\n\n return routes;\n}\n\n/**\n * Match a URL path to a route\n */\nexport function matchRoute(url: string, routes: Route[]): { \n route: Route | null; \n params: Record<string, string>;\n} {\n const urlParts = url.split('/').filter(Boolean);\n\n for (const route of routes) {\n if (route.type !== 'page' && route.type !== 'api') continue;\n\n const routeParts = route.path.split('/').filter(Boolean);\n const params: Record<string, string> = {};\n let matched = true;\n let urlIndex = 0;\n\n for (let i = 0; i < routeParts.length; i++) {\n const routePart = routeParts[i];\n\n // Catch-all\n if (routePart.startsWith('*')) {\n const paramName = routePart.replace(/^\\*/, '').replace(/\\?$/, '');\n params[paramName] = urlParts.slice(urlIndex).join('/');\n return { route, params };\n }\n\n // Dynamic segment\n if (routePart.startsWith(':')) {\n const paramName = routePart.slice(1);\n if (urlIndex >= urlParts.length) {\n matched = false;\n break;\n }\n params[paramName] = urlParts[urlIndex];\n urlIndex++;\n continue;\n }\n\n // Static segment\n if (urlParts[urlIndex] !== routePart) {\n matched = false;\n break;\n }\n urlIndex++;\n }\n\n // Check if we consumed all URL parts\n if (matched && urlIndex === urlParts.length) {\n return { route, params };\n }\n }\n\n return { route: null, params: {} };\n}\n\nexport type { Route as FloatRoute, RouterOptions as FloatRouterOptions };\n"],"mappings":"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","names":[]}
|