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

View File

@@ -1,5 +1,6 @@
import { $, $$, watch, batch, h, Fragment, mount, when, each, router, onUnmount, isArr, isFunc, isObj } from "./sigpro.js"
import { $, watch, batch, h, Fragment, mount, when, each, onUnmount, isArr, isFunc, isObj } from "./sigpro.js"
import { router } from "./router.js"
if (typeof window !== "undefined") {
Object.assign(window, { $, $$, watch, h, Fragment, when, each, router, mount, batch, onUnmount, isArr, isFunc, isObj })
Object.assign(window, { $, watch, h, Fragment, when, each, router, mount, batch, onUnmount, isArr, isFunc, isObj })
}

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 };

View File

@@ -9,8 +9,6 @@ let activeOwner = null
let isFlushing = false
let batchDepth = 0
const effectQueue = new Set()
const proxyCache = new WeakMap()
const ITER = Symbol('iter')
const MOUNTED_NODES = new WeakMap()
const SVG_NS = "http://www.w3.org/2000/svg"
@@ -161,51 +159,6 @@ const $ = (val, key = null) => {
}
}
const $$ = (target) => {
if (!isObj(target)) return target
const cached = proxyCache.get(target)
if (cached) return cached
const subs = new Map()
const getSubs = (key) => {
let set = subs.get(key)
if (!set) subs.set(key, set = new Set())
return set
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (typeof key !== 'symbol') trackUpdate(getSubs(key))
return $$(Reflect.get(target, key, receiver))
},
set(target, key, value, receiver) {
const hadKey = Reflect.has(target, key)
const oldValue = Reflect.get(target, key, receiver)
const result = Reflect.set(target, key, value, receiver)
if (result && !Object.is(oldValue, value)) {
trackUpdate(getSubs(key), true)
if (!hadKey) trackUpdate(getSubs(ITER), true)
}
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
if (result) {
trackUpdate(getSubs(key), true)
trackUpdate(getSubs(ITER), true)
}
return result
},
ownKeys(target) {
trackUpdate(getSubs(ITER))
return Reflect.ownKeys(target)
}
})
proxyCache.set(target, proxy)
return proxy
}
const watch = (sources, cb) => {
if (cb === undefined) {
const effect = createEffect(sources)
@@ -459,39 +412,6 @@ const each = (src, itemFn, keyField) => {
return root
}
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(/^#/, "") || "/"
const Fragment = (props) => props.children;
const mount = (comp, target) => {
@@ -510,4 +430,4 @@ if (typeof window !== "undefined") {
.forEach(tag => { window[tag] = (props, children) => h(tag, props, children) })
}
export { $, $$, watch, batch, h, Fragment, mount, when, each, router, onUnmount, isArr, isFunc, isObj }
export { $, watch, batch, h, Fragment, render, mount, when, each, onUnmount, isArr, isFunc, isObj }

1
src/sigpro.min.js vendored Normal file

File diff suppressed because one or more lines are too long