Modular router && remove $$
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 9s

This commit is contained in:
2026-05-05 16:27:53 +02:00
parent ab0e6e0697
commit 439809b1e7
20 changed files with 2007 additions and 722 deletions

101
src/router.js Normal file
View File

@@ -0,0 +1,101 @@
// src/router.js
import { h, watch, $, render, onUnmount, isFunc } from './sigpro.js';
import fs from 'node:fs';
import path from 'node:path';
const router = routes => {
const getHash = () => window.location.hash.slice(1) || "/";
const path = $(getHash());
const handler = () => path(getHash());
window.addEventListener("hashchange", handler);
onUnmount(() => window.removeEventListener("hashchange", handler));
const hook = h("div", { class: "router-hook" });
let currentView = null;
watch([path], () => {
const cur = path();
const route = routes.find(r => {
const p1 = r.path.split("/").filter(Boolean);
const p2 = cur.split("/").filter(Boolean);
return p1.length === p2.length && p1.every((p, i) => p[0] === ":" || p === p2[i]);
}) || routes.find(r => r.path === "*");
if (route) {
currentView?.destroy();
const params = {};
route.path.split("/").filter(Boolean).forEach((p, i) => {
if (p[0] === ":") params[p.slice(1)] = cur.split("/").filter(Boolean)[i];
});
router.params(params);
currentView = render(() => isFunc(route.component) ? route.component(params) : route.component);
hook.replaceChildren(currentView.container);
}
});
return hook;
};
router.params = $({});
router.to = p => window.location.hash = p.replace(/^#?\/?/, "#/");
router.back = () => window.history.back();
router.path = () => window.location.hash.replace(/^#/, "") || "/";
function sigproRouter() {
const virtualModuleId = 'virtual:sigpro-routes';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const getFiles = (dir) => {
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir, { recursive: true })
.filter(file => /\.(js|jsx)$/.test(file) && !path.basename(file).startsWith('_'))
.map(file => path.resolve(dir, file));
};
const pathToUrl = (pagesDir, filePath) => {
let relative = path.relative(pagesDir, filePath)
.replace(/\\/g, '/')
.replace(/\.(js|jsx)$/, '')
.replace(/\/index$/, '')
.replace(/^index$/, '');
return ('/' + relative)
.replace(/\/+/g, '/')
.replace(/\[\.\.\.([^\]]+)\]/g, '*')
.replace(/\[([^\]]+)\]/g, ':$1')
.replace(/\/$/, '') || '/';
};
return {
name: 'sigpro-router',
resolveId(id) {
if (id === virtualModuleId) return resolvedVirtualModuleId;
},
load(id) {
if (id !== resolvedVirtualModuleId) return;
const root = process.cwd();
const pagesDir = path.resolve(root, 'src/pages');
const files = getFiles(pagesDir).sort((a, b) => {
const urlA = pathToUrl(pagesDir, a);
const urlB = pathToUrl(pagesDir, b);
if (urlA.includes(':') && !urlB.includes(':')) return 1;
if (!urlA.includes(':') && urlB.includes(':')) return -1;
return urlB.length - urlA.length;
});
let routeEntries = '';
files.forEach((fullPath) => {
const urlPath = pathToUrl(pagesDir, fullPath);
const relativeImport = './' + path.relative(root, fullPath).replace(/\\/g, '/');
routeEntries += ` { path: '${urlPath}', component: () => import('/${relativeImport}') },\n`;
});
if (!routeEntries.includes("path: '*'")) {
routeEntries += ` { path: '*', component: () => ({ default: () => document.createTextNode('404 - Not Found') }) },\n`;
}
return `export const routes = [\n${routeEntries}];`;
}
};
}
// ============================================================
// Exportación final (lo que se consume desde 'sigpro/router')
// ============================================================
export { router, sigproRouter };