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

@@ -3,7 +3,7 @@ Blazing fast, zero-overhead, vanilla JS renderer with atomic reactivity.
# `SigPro`
[![npm version](https://img.shields.io/npm/v/sigpro.svg)](https://www.npmjs.com/package/sigpro)
[![brotli size](https://img.shields.io/bundlejs/size/sigpro?compression=brotli)](https://bundlejs.com/?q=sigpro)
[![js size](https://img.shields.io/badge/js_size-2.8_kB_brotli-blue)]
[![license](https://img.shields.io/npm/l/sigpro)](https://github.com/natxocc/sigpro/blob/main/LICENSE)
[**Explore the Docs →**](https://sigpro.natxocc.com/#/)
@@ -62,7 +62,7 @@ Create reactive, persistent components with a syntax that feels like Vanilla JS,
```
```javascript
import "sigpro";
import { $, mount } from "sigpro";
const Counter = () => {
// Simple signal
@@ -89,7 +89,7 @@ mount(Counter, "#app");
| Feature | **SigPro** | React / Vue | Svelte |
| :--- | :--- | :--- | :--- |
| **Payload (Gzipped)** | **~3KB** | ~30KB - 50KB | ~5KB (Compiled Runtime) |
| **Payload (Gzipped)** | **<3KB** | ~30KB - 50KB | ~5KB (Compiled Runtime) |
| **State Logic** | **Atomic Signals** | Virtual DOM Diffing | Compiler Dirty Bits |
| **Update Speed** | **Direct Node Access** | Component Re-render | Block Reconciliation |
| **Native Persistence** | **Included ($)** | Requires Plugins | Manual |

776
dist/router.js vendored Normal file
View File

@@ -0,0 +1,776 @@
// src/sigpro.js
var isFunc = (f) => typeof f === "function";
var isObj = (o) => o && typeof o === "object";
var isArr = Array.isArray;
var doc = typeof document !== "undefined" ? document : null;
var ensureNode = (n) => n?._isRuntime ? n.container : n instanceof Node ? n : doc.createTextNode(n == null ? "" : String(n));
var activeEffect = null;
var activeOwner = null;
var isFlushing = false;
var batchDepth = 0;
var effectQueue = new Set;
var MOUNTED_NODES = new WeakMap;
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
var SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(","));
var dispose = (eff) => {
if (!eff || eff._disposed)
return;
eff._disposed = true;
const stack = [eff];
while (stack.length) {
const e = stack.pop();
if (e._cleanups) {
e._cleanups.forEach((fn) => fn());
e._cleanups.clear();
}
if (e._children) {
e._children.forEach((child) => stack.push(child));
e._children.clear();
}
if (e._deps) {
e._deps.forEach((depSet) => depSet.delete(e));
e._deps.clear();
}
}
};
var onUnmount = (fn) => {
if (activeOwner)
(activeOwner._cleanups ||= new Set).add(fn);
};
var untrack = (fn) => {
const p = activeEffect;
activeEffect = null;
try {
return fn();
} finally {
activeEffect = p;
}
};
var createEffect = (fn, isComputed = false) => {
const effect = () => {
if (effect._disposed)
return;
if (effect._deps)
effect._deps.forEach((s) => s.delete(effect));
if (effect._cleanups) {
effect._cleanups.forEach((c) => c());
effect._cleanups.clear();
}
const prevEffect = activeEffect;
const prevOwner = activeOwner;
activeEffect = activeOwner = effect;
try {
return effect._result = fn();
} catch (e) {
console.error("[SigPro]", e);
} finally {
activeEffect = prevEffect;
activeOwner = prevOwner;
}
};
effect._deps = effect._cleanups = effect._children = null;
effect._disposed = false;
effect._isComputed = isComputed;
effect._depth = activeEffect ? activeEffect._depth + 1 : 0;
effect._mounts = [];
effect._parent = activeOwner;
if (activeOwner)
(activeOwner._children ||= new Set).add(effect);
return effect;
};
var flush = () => {
if (isFlushing)
return;
isFlushing = true;
const sorted = Array.from(effectQueue).sort((a, b) => a._depth - b._depth);
effectQueue.clear();
for (const e of sorted)
if (!e._disposed)
e();
isFlushing = false;
};
var trackUpdate = (subs, trigger = false) => {
if (!trigger && activeEffect && !activeEffect._disposed) {
subs.add(activeEffect);
(activeEffect._deps ||= new Set).add(subs);
} else if (trigger && subs.size > 0) {
let hasQueue = false;
for (const e of subs) {
if (e === activeEffect || e._disposed)
continue;
if (e._isComputed) {
e._dirty = true;
if (e._subs)
trackUpdate(e._subs, true);
} else {
effectQueue.add(e);
hasQueue = true;
}
}
if (hasQueue && !isFlushing && batchDepth === 0)
queueMicrotask(flush);
}
};
var $ = (val, key = null) => {
const subs = new Set;
if (isFunc(val)) {
let cache;
const computed = () => {
if (computed._dirty) {
const prev = activeEffect;
activeEffect = computed;
try {
const next = val();
if (!Object.is(cache, next)) {
cache = next;
trackUpdate(subs, true);
}
} finally {
activeEffect = prev;
}
computed._dirty = false;
}
trackUpdate(subs);
return cache;
};
computed._isComputed = true;
computed._subs = subs;
computed._dirty = true;
computed._deps = null;
computed._disposed = false;
return computed;
}
if (key)
try {
val = JSON.parse(localStorage.getItem(key)) ?? val;
} catch (e) {}
return (...args) => {
if (args.length) {
const next = isFunc(args[0]) ? args[0](val) : args[0];
if (!Object.is(val, next)) {
val = next;
if (key)
localStorage.setItem(key, JSON.stringify(val));
trackUpdate(subs, true);
}
}
trackUpdate(subs);
return val;
};
};
var watch = (sources, cb) => {
if (cb === undefined) {
const effect2 = createEffect(sources);
effect2();
return () => dispose(effect2);
}
const effect = createEffect(() => {
const vals = isArr(sources) ? sources.map((s) => s()) : sources();
untrack(() => cb(vals));
});
effect();
return () => dispose(effect);
};
var cleanupNode = (node) => {
if (!node)
return;
if (node._cleanups) {
node._cleanups.forEach((fn) => fn());
node._cleanups.clear();
}
if (node._ownerEffect)
dispose(node._ownerEffect);
if (node.childNodes)
node.childNodes.forEach((n) => cleanupNode(n));
};
var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i;
var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]);
var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on");
var validateAttr = (key, val) => {
if (val == null || val === false)
return null;
if (isDangerousAttr(key)) {
const sVal = String(val);
if (DANGEROUS_PROTOCOL.test(sVal))
return "#";
}
return val;
};
var h = (tag, props = {}, children = []) => {
if (props instanceof Node || isArr(props) || !isObj(props)) {
children = props;
props = {};
}
if (isFunc(tag)) {
const effect = createEffect(() => {
const result2 = tag(props, {
children,
emit: (ev, ...args) => props[`on${ev[0].toUpperCase()}${ev.slice(1)}`]?.(...args)
});
effect._result = result2;
return result2;
});
effect();
const result = effect._result;
if (result == null)
return null;
const node = result instanceof Node || isArr(result) && result.every((n) => n instanceof Node) ? result : doc.createTextNode(String(result));
const attach = (n) => {
if (isObj(n) && !n._isRuntime) {
n._mounts = effect._mounts || [];
n._cleanups = effect._cleanups || new Set;
n._ownerEffect = effect;
}
};
isArr(node) ? node.forEach(attach) : attach(node);
return node;
}
const isSVG = SVG_TAGS.has(tag);
const el = isSVG ? doc.createElementNS(SVG_NS, tag) : doc.createElement(tag);
el._cleanups = new Set;
for (const k of Object.keys(props)) {
let v = props[k];
if (k === "ref") {
isFunc(v) ? v(el) : v.current = el;
continue;
}
if (isSVG && k.startsWith("xlink:")) {
const cleanVal = validateAttr(k.slice(6), v);
cleanVal == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal);
continue;
}
if (k.startsWith("on")) {
const ev = k.slice(2).toLowerCase();
el.addEventListener(ev, v);
const off = () => el.removeEventListener(ev, v);
el._cleanups.add(off);
onUnmount(off);
} else if (isFunc(v)) {
const effect = createEffect(() => {
const val = validateAttr(k, v());
if (k === "class")
el.className = val || "";
else if (val == null)
el.removeAttribute(k);
else if (k === "style" && typeof val === "string")
el.setAttribute("style", val);
else if (k in el && !isSVG)
el[k] = val;
else
el.setAttribute(k, val === true ? "" : val);
});
effect();
el._cleanups.add(() => dispose(effect));
onUnmount(() => dispose(effect));
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
const evType = k === "checked" ? "change" : "input";
el.addEventListener(evType, (ev) => v(ev.target[k]));
}
} else {
const val = validateAttr(k, v);
if (val != null) {
if (k === "style" && typeof val === "string")
el.setAttribute("style", val);
else if (k in el && !isSVG)
el[k] = val;
else
el.setAttribute(k, val === true ? "" : val);
}
}
}
const append = (c) => {
if (isArr(c))
return c.forEach(append);
if (isFunc(c)) {
const anchor = doc.createTextNode("");
el.appendChild(anchor);
let currentNodes = [];
const effect = createEffect(() => {
const res = c();
const next = (isArr(res) ? res : [res]).map(ensureNode);
currentNodes.forEach((n) => {
if (n._isRuntime)
n.destroy();
else
cleanupNode(n);
if (n.parentNode)
n.remove();
});
let ref = anchor;
for (let i = next.length - 1;i >= 0; i--) {
const node = next[i];
if (node.parentNode !== ref.parentNode)
ref.parentNode?.insertBefore(node, ref);
if (node._mounts)
node._mounts.forEach((fn) => fn());
ref = node;
}
currentNodes = next;
});
effect();
el._cleanups.add(() => dispose(effect));
onUnmount(() => dispose(effect));
} else {
const node = ensureNode(c);
el.appendChild(node);
if (node._mounts)
node._mounts.forEach((fn) => fn());
}
};
append(children);
return el;
};
var render = (renderFn) => {
const cleanups = new Set;
const previousOwner = activeOwner;
const previousEffect = activeEffect;
const container = doc.createElement("div");
container.style.display = "contents";
container.setAttribute("role", "presentation");
activeOwner = { _cleanups: cleanups };
activeEffect = null;
const processResult = (result) => {
if (!result)
return;
if (result._isRuntime) {
cleanups.add(result.destroy);
container.appendChild(result.container);
} else if (isArr(result)) {
result.forEach(processResult);
} else {
container.appendChild(result instanceof Node ? result : doc.createTextNode(String(result == null ? "" : result)));
}
};
try {
processResult(renderFn({ onCleanup: (fn) => cleanups.add(fn) }));
} finally {
activeOwner = previousOwner;
activeEffect = previousEffect;
}
return {
_isRuntime: true,
container,
destroy: () => {
cleanups.forEach((fn) => fn());
cleanupNode(container);
container.remove();
}
};
};
if (typeof window !== "undefined") {
"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
}
// src/router.js
var { default: fs} = (() => ({}));
// node:path
function assertPath(path) {
if (typeof path !== "string")
throw TypeError("Path must be a string. Received " + JSON.stringify(path));
}
function normalizeStringPosix(path, allowAboveRoot) {
var res = "", lastSegmentLength = 0, lastSlash = -1, dots = 0, code;
for (var i = 0;i <= path.length; ++i) {
if (i < path.length)
code = path.charCodeAt(i);
else if (code === 47)
break;
else
code = 47;
if (code === 47) {
if (lastSlash === i - 1 || dots === 1)
;
else if (lastSlash !== i - 1 && dots === 2) {
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
if (res.length > 2) {
var lastSlashIndex = res.lastIndexOf("/");
if (lastSlashIndex !== res.length - 1) {
if (lastSlashIndex === -1)
res = "", lastSegmentLength = 0;
else
res = res.slice(0, lastSlashIndex), lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
lastSlash = i, dots = 0;
continue;
}
} else if (res.length === 2 || res.length === 1) {
res = "", lastSegmentLength = 0, lastSlash = i, dots = 0;
continue;
}
}
if (allowAboveRoot) {
if (res.length > 0)
res += "/..";
else
res = "..";
lastSegmentLength = 2;
}
} else {
if (res.length > 0)
res += "/" + path.slice(lastSlash + 1, i);
else
res = path.slice(lastSlash + 1, i);
lastSegmentLength = i - lastSlash - 1;
}
lastSlash = i, dots = 0;
} else if (code === 46 && dots !== -1)
++dots;
else
dots = -1;
}
return res;
}
function _format(sep, pathObject) {
var dir = pathObject.dir || pathObject.root, base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
if (!dir)
return base;
if (dir === pathObject.root)
return dir + base;
return dir + sep + base;
}
function resolve() {
var resolvedPath = "", resolvedAbsolute = false, cwd;
for (var i = arguments.length - 1;i >= -1 && !resolvedAbsolute; i--) {
var path;
if (i >= 0)
path = arguments[i];
else {
if (cwd === undefined)
cwd = process.cwd();
path = cwd;
}
if (assertPath(path), path.length === 0)
continue;
resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = path.charCodeAt(0) === 47;
}
if (resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute), resolvedAbsolute)
if (resolvedPath.length > 0)
return "/" + resolvedPath;
else
return "/";
else if (resolvedPath.length > 0)
return resolvedPath;
else
return ".";
}
function normalize(path) {
if (assertPath(path), path.length === 0)
return ".";
var isAbsolute = path.charCodeAt(0) === 47, trailingSeparator = path.charCodeAt(path.length - 1) === 47;
if (path = normalizeStringPosix(path, !isAbsolute), path.length === 0 && !isAbsolute)
path = ".";
if (path.length > 0 && trailingSeparator)
path += "/";
if (isAbsolute)
return "/" + path;
return path;
}
function isAbsolute(path) {
return assertPath(path), path.length > 0 && path.charCodeAt(0) === 47;
}
function join() {
if (arguments.length === 0)
return ".";
var joined;
for (var i = 0;i < arguments.length; ++i) {
var arg = arguments[i];
if (assertPath(arg), arg.length > 0)
if (joined === undefined)
joined = arg;
else
joined += "/" + arg;
}
if (joined === undefined)
return ".";
return normalize(joined);
}
function relative(from, to) {
if (assertPath(from), assertPath(to), from === to)
return "";
if (from = resolve(from), to = resolve(to), from === to)
return "";
var fromStart = 1;
for (;fromStart < from.length; ++fromStart)
if (from.charCodeAt(fromStart) !== 47)
break;
var fromEnd = from.length, fromLen = fromEnd - fromStart, toStart = 1;
for (;toStart < to.length; ++toStart)
if (to.charCodeAt(toStart) !== 47)
break;
var toEnd = to.length, toLen = toEnd - toStart, length = fromLen < toLen ? fromLen : toLen, lastCommonSep = -1, i = 0;
for (;i <= length; ++i) {
if (i === length) {
if (toLen > length) {
if (to.charCodeAt(toStart + i) === 47)
return to.slice(toStart + i + 1);
else if (i === 0)
return to.slice(toStart + i);
} else if (fromLen > length) {
if (from.charCodeAt(fromStart + i) === 47)
lastCommonSep = i;
else if (i === 0)
lastCommonSep = 0;
}
break;
}
var fromCode = from.charCodeAt(fromStart + i), toCode = to.charCodeAt(toStart + i);
if (fromCode !== toCode)
break;
else if (fromCode === 47)
lastCommonSep = i;
}
var out = "";
for (i = fromStart + lastCommonSep + 1;i <= fromEnd; ++i)
if (i === fromEnd || from.charCodeAt(i) === 47)
if (out.length === 0)
out += "..";
else
out += "/..";
if (out.length > 0)
return out + to.slice(toStart + lastCommonSep);
else {
if (toStart += lastCommonSep, to.charCodeAt(toStart) === 47)
++toStart;
return to.slice(toStart);
}
}
function _makeLong(path) {
return path;
}
function dirname(path) {
if (assertPath(path), path.length === 0)
return ".";
var code = path.charCodeAt(0), hasRoot = code === 47, end = -1, matchedSlash = true;
for (var i = path.length - 1;i >= 1; --i)
if (code = path.charCodeAt(i), code === 47) {
if (!matchedSlash) {
end = i;
break;
}
} else
matchedSlash = false;
if (end === -1)
return hasRoot ? "/" : ".";
if (hasRoot && end === 1)
return "//";
return path.slice(0, end);
}
function basename(path, ext) {
if (ext !== undefined && typeof ext !== "string")
throw TypeError('"ext" argument must be a string');
assertPath(path);
var start = 0, end = -1, matchedSlash = true, i;
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
if (ext.length === path.length && ext === path)
return "";
var extIdx = ext.length - 1, firstNonSlashEnd = -1;
for (i = path.length - 1;i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else {
if (firstNonSlashEnd === -1)
matchedSlash = false, firstNonSlashEnd = i + 1;
if (extIdx >= 0)
if (code === ext.charCodeAt(extIdx)) {
if (--extIdx === -1)
end = i;
} else
extIdx = -1, end = firstNonSlashEnd;
}
}
if (start === end)
end = firstNonSlashEnd;
else if (end === -1)
end = path.length;
return path.slice(start, end);
} else {
for (i = path.length - 1;i >= 0; --i)
if (path.charCodeAt(i) === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else if (end === -1)
matchedSlash = false, end = i + 1;
if (end === -1)
return "";
return path.slice(start, end);
}
}
function extname(path) {
assertPath(path);
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, preDotState = 0;
for (var i = path.length - 1;i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1)
matchedSlash = false, end = i + 1;
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1)
preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
return "";
return path.slice(startDot, end);
}
function format(pathObject) {
if (pathObject === null || typeof pathObject !== "object")
throw TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
return _format("/", pathObject);
}
function parse(path) {
assertPath(path);
var ret = { root: "", dir: "", base: "", ext: "", name: "" };
if (path.length === 0)
return ret;
var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
if (isAbsolute2)
ret.root = "/", start = 1;
else
start = 0;
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, i = path.length - 1, preDotState = 0;
for (;i >= start; --i) {
if (code = path.charCodeAt(i), code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1)
matchedSlash = false, end = i + 1;
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1)
preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
if (end !== -1)
if (startPart === 0 && isAbsolute2)
ret.base = ret.name = path.slice(1, end);
else
ret.base = ret.name = path.slice(startPart, end);
} else {
if (startPart === 0 && isAbsolute2)
ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
else
ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
ret.ext = path.slice(startDot, end);
}
if (startPart > 0)
ret.dir = path.slice(0, startPart - 1);
else if (isAbsolute2)
ret.dir = "/";
return ret;
}
var sep = "/";
var delimiter = ":";
var posix = ((p) => (p.posix = p, p))({ resolve, normalize, isAbsolute, join, relative, _makeLong, dirname, basename, extname, format, parse, sep, delimiter, win32: null, posix: null });
var path_default = posix;
// src/router.js
var 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 = "\x00" + virtualModuleId;
const getFiles = (dir) => {
if (!fs.existsSync(dir))
return [];
return fs.readdirSync(dir, { recursive: true }).filter((file) => /\.(js|jsx)$/.test(file) && !path_default.basename(file).startsWith("_")).map((file) => path_default.resolve(dir, file));
};
const pathToUrl = (pagesDir, filePath) => {
let relative2 = path_default.relative(pagesDir, filePath).replace(/\\/g, "/").replace(/\.(js|jsx)$/, "").replace(/\/index$/, "").replace(/^index$/, "");
return ("/" + relative2).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_default.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_default.relative(root, fullPath).replace(/\\/g, "/");
routeEntries += ` { path: '${urlPath}', component: () => import('/${relativeImport}') },
`;
});
if (!routeEntries.includes("path: '*'")) {
routeEntries += ` { path: '*', component: () => ({ default: () => document.createTextNode('404 - Not Found') }) },
`;
}
return `export const routes = [
${routeEntries}];`;
}
};
}
export {
sigproRouter,
router
};

84
dist/sigpro.esm.js vendored
View File

@@ -9,8 +9,6 @@ var activeOwner = null;
var isFlushing = false;
var batchDepth = 0;
var effectQueue = new Set;
var proxyCache = new WeakMap;
var ITER = Symbol("iter");
var MOUNTED_NODES = new WeakMap;
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
@@ -171,52 +169,6 @@ var $ = (val, key = null) => {
return val;
};
};
var $$ = (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(target2, key, receiver) {
if (typeof key !== "symbol")
trackUpdate(getSubs(key));
return $$(Reflect.get(target2, key, receiver));
},
set(target2, key, value, receiver) {
const hadKey = Reflect.has(target2, key);
const oldValue = Reflect.get(target2, key, receiver);
const result = Reflect.set(target2, key, value, receiver);
if (result && !Object.is(oldValue, value)) {
trackUpdate(getSubs(key), true);
if (!hadKey)
trackUpdate(getSubs(ITER), true);
}
return result;
},
deleteProperty(target2, key) {
const result = Reflect.deleteProperty(target2, key);
if (result) {
trackUpdate(getSubs(key), true);
trackUpdate(getSubs(ITER), true);
}
return result;
},
ownKeys(target2) {
trackUpdate(getSubs(ITER));
return Reflect.ownKeys(target2);
}
});
proxyCache.set(target, proxy);
return proxy;
};
var watch = (sources, cb) => {
if (cb === undefined) {
const effect2 = createEffect(sources);
@@ -466,39 +418,6 @@ var each = (src, itemFn, keyField) => {
});
return root;
};
var 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(/^#/, "") || "/";
var Fragment = (props) => props.children;
var mount = (comp, target) => {
const t = typeof target === "string" ? doc.querySelector(target) : target;
@@ -519,7 +438,7 @@ if (typeof window !== "undefined") {
export {
when,
watch,
router,
render,
onUnmount,
mount,
isObj,
@@ -529,6 +448,5 @@ export {
each,
batch,
Fragment,
$$,
$
};

File diff suppressed because one or more lines are too long

447
dist/sigpro.js vendored
View File

@@ -1,4 +1,42 @@
(() => {
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
function __accessProp(key) {
return this[key];
}
var __toESMCache_node;
var __toESMCache_esm;
var __toESM = (mod, isNodeMode, target) => {
var canCache = mod != null && typeof mod === "object";
if (canCache) {
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
var cached = cache.get(mod);
if (cached)
return cached;
}
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: __accessProp.bind(mod, key),
enumerable: true
});
if (canCache)
cache.set(mod, to);
return to;
};
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined")
return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/sigpro.js
var isFunc = (f) => typeof f === "function";
var isObj = (o) => o && typeof o === "object";
@@ -10,8 +48,6 @@
var isFlushing = false;
var batchDepth = 0;
var effectQueue = new Set;
var proxyCache = new WeakMap;
var ITER = Symbol("iter");
var MOUNTED_NODES = new WeakMap;
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
@@ -172,52 +208,6 @@
return val;
};
};
var $$ = (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(target2, key, receiver) {
if (typeof key !== "symbol")
trackUpdate(getSubs(key));
return $$(Reflect.get(target2, key, receiver));
},
set(target2, key, value, receiver) {
const hadKey = Reflect.has(target2, key);
const oldValue = Reflect.get(target2, key, receiver);
const result = Reflect.set(target2, key, value, receiver);
if (result && !Object.is(oldValue, value)) {
trackUpdate(getSubs(key), true);
if (!hadKey)
trackUpdate(getSubs(ITER), true);
}
return result;
},
deleteProperty(target2, key) {
const result = Reflect.deleteProperty(target2, key);
if (result) {
trackUpdate(getSubs(key), true);
trackUpdate(getSubs(ITER), true);
}
return result;
},
ownKeys(target2) {
trackUpdate(getSubs(ITER));
return Reflect.ownKeys(target2);
}
});
proxyCache.set(target, proxy);
return proxy;
};
var watch = (sources, cb) => {
if (cb === undefined) {
const effect2 = createEffect(sources);
@@ -467,6 +457,348 @@
});
return root;
};
var Fragment = (props) => props.children;
var mount = (comp, target) => {
const t = typeof target === "string" ? doc.querySelector(target) : target;
if (!t)
return;
if (MOUNTED_NODES.has(t))
MOUNTED_NODES.get(t).destroy();
const inst = render(isFunc(comp) ? comp : () => comp);
t.replaceChildren(inst.container);
MOUNTED_NODES.set(t, inst);
return inst;
};
if (typeof window !== "undefined") {
"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
}
// src/router.js
var import_node_fs = (() => ({}));
// node:path
function assertPath(path) {
if (typeof path !== "string")
throw TypeError("Path must be a string. Received " + JSON.stringify(path));
}
function normalizeStringPosix(path, allowAboveRoot) {
var res = "", lastSegmentLength = 0, lastSlash = -1, dots = 0, code;
for (var i = 0;i <= path.length; ++i) {
if (i < path.length)
code = path.charCodeAt(i);
else if (code === 47)
break;
else
code = 47;
if (code === 47) {
if (lastSlash === i - 1 || dots === 1)
;
else if (lastSlash !== i - 1 && dots === 2) {
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
if (res.length > 2) {
var lastSlashIndex = res.lastIndexOf("/");
if (lastSlashIndex !== res.length - 1) {
if (lastSlashIndex === -1)
res = "", lastSegmentLength = 0;
else
res = res.slice(0, lastSlashIndex), lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
lastSlash = i, dots = 0;
continue;
}
} else if (res.length === 2 || res.length === 1) {
res = "", lastSegmentLength = 0, lastSlash = i, dots = 0;
continue;
}
}
if (allowAboveRoot) {
if (res.length > 0)
res += "/..";
else
res = "..";
lastSegmentLength = 2;
}
} else {
if (res.length > 0)
res += "/" + path.slice(lastSlash + 1, i);
else
res = path.slice(lastSlash + 1, i);
lastSegmentLength = i - lastSlash - 1;
}
lastSlash = i, dots = 0;
} else if (code === 46 && dots !== -1)
++dots;
else
dots = -1;
}
return res;
}
function _format(sep, pathObject) {
var dir = pathObject.dir || pathObject.root, base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
if (!dir)
return base;
if (dir === pathObject.root)
return dir + base;
return dir + sep + base;
}
function resolve() {
var resolvedPath = "", resolvedAbsolute = false, cwd;
for (var i = arguments.length - 1;i >= -1 && !resolvedAbsolute; i--) {
var path;
if (i >= 0)
path = arguments[i];
else {
if (cwd === undefined)
cwd = process.cwd();
path = cwd;
}
if (assertPath(path), path.length === 0)
continue;
resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = path.charCodeAt(0) === 47;
}
if (resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute), resolvedAbsolute)
if (resolvedPath.length > 0)
return "/" + resolvedPath;
else
return "/";
else if (resolvedPath.length > 0)
return resolvedPath;
else
return ".";
}
function normalize(path) {
if (assertPath(path), path.length === 0)
return ".";
var isAbsolute = path.charCodeAt(0) === 47, trailingSeparator = path.charCodeAt(path.length - 1) === 47;
if (path = normalizeStringPosix(path, !isAbsolute), path.length === 0 && !isAbsolute)
path = ".";
if (path.length > 0 && trailingSeparator)
path += "/";
if (isAbsolute)
return "/" + path;
return path;
}
function isAbsolute(path) {
return assertPath(path), path.length > 0 && path.charCodeAt(0) === 47;
}
function join() {
if (arguments.length === 0)
return ".";
var joined;
for (var i = 0;i < arguments.length; ++i) {
var arg = arguments[i];
if (assertPath(arg), arg.length > 0)
if (joined === undefined)
joined = arg;
else
joined += "/" + arg;
}
if (joined === undefined)
return ".";
return normalize(joined);
}
function relative(from, to) {
if (assertPath(from), assertPath(to), from === to)
return "";
if (from = resolve(from), to = resolve(to), from === to)
return "";
var fromStart = 1;
for (;fromStart < from.length; ++fromStart)
if (from.charCodeAt(fromStart) !== 47)
break;
var fromEnd = from.length, fromLen = fromEnd - fromStart, toStart = 1;
for (;toStart < to.length; ++toStart)
if (to.charCodeAt(toStart) !== 47)
break;
var toEnd = to.length, toLen = toEnd - toStart, length = fromLen < toLen ? fromLen : toLen, lastCommonSep = -1, i = 0;
for (;i <= length; ++i) {
if (i === length) {
if (toLen > length) {
if (to.charCodeAt(toStart + i) === 47)
return to.slice(toStart + i + 1);
else if (i === 0)
return to.slice(toStart + i);
} else if (fromLen > length) {
if (from.charCodeAt(fromStart + i) === 47)
lastCommonSep = i;
else if (i === 0)
lastCommonSep = 0;
}
break;
}
var fromCode = from.charCodeAt(fromStart + i), toCode = to.charCodeAt(toStart + i);
if (fromCode !== toCode)
break;
else if (fromCode === 47)
lastCommonSep = i;
}
var out = "";
for (i = fromStart + lastCommonSep + 1;i <= fromEnd; ++i)
if (i === fromEnd || from.charCodeAt(i) === 47)
if (out.length === 0)
out += "..";
else
out += "/..";
if (out.length > 0)
return out + to.slice(toStart + lastCommonSep);
else {
if (toStart += lastCommonSep, to.charCodeAt(toStart) === 47)
++toStart;
return to.slice(toStart);
}
}
function _makeLong(path) {
return path;
}
function dirname(path) {
if (assertPath(path), path.length === 0)
return ".";
var code = path.charCodeAt(0), hasRoot = code === 47, end = -1, matchedSlash = true;
for (var i = path.length - 1;i >= 1; --i)
if (code = path.charCodeAt(i), code === 47) {
if (!matchedSlash) {
end = i;
break;
}
} else
matchedSlash = false;
if (end === -1)
return hasRoot ? "/" : ".";
if (hasRoot && end === 1)
return "//";
return path.slice(0, end);
}
function basename(path, ext) {
if (ext !== undefined && typeof ext !== "string")
throw TypeError('"ext" argument must be a string');
assertPath(path);
var start = 0, end = -1, matchedSlash = true, i;
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
if (ext.length === path.length && ext === path)
return "";
var extIdx = ext.length - 1, firstNonSlashEnd = -1;
for (i = path.length - 1;i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else {
if (firstNonSlashEnd === -1)
matchedSlash = false, firstNonSlashEnd = i + 1;
if (extIdx >= 0)
if (code === ext.charCodeAt(extIdx)) {
if (--extIdx === -1)
end = i;
} else
extIdx = -1, end = firstNonSlashEnd;
}
}
if (start === end)
end = firstNonSlashEnd;
else if (end === -1)
end = path.length;
return path.slice(start, end);
} else {
for (i = path.length - 1;i >= 0; --i)
if (path.charCodeAt(i) === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else if (end === -1)
matchedSlash = false, end = i + 1;
if (end === -1)
return "";
return path.slice(start, end);
}
}
function extname(path) {
assertPath(path);
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, preDotState = 0;
for (var i = path.length - 1;i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1)
matchedSlash = false, end = i + 1;
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1)
preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
return "";
return path.slice(startDot, end);
}
function format(pathObject) {
if (pathObject === null || typeof pathObject !== "object")
throw TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
return _format("/", pathObject);
}
function parse(path) {
assertPath(path);
var ret = { root: "", dir: "", base: "", ext: "", name: "" };
if (path.length === 0)
return ret;
var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
if (isAbsolute2)
ret.root = "/", start = 1;
else
start = 0;
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, i = path.length - 1, preDotState = 0;
for (;i >= start; --i) {
if (code = path.charCodeAt(i), code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1)
matchedSlash = false, end = i + 1;
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1)
preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
if (end !== -1)
if (startPart === 0 && isAbsolute2)
ret.base = ret.name = path.slice(1, end);
else
ret.base = ret.name = path.slice(startPart, end);
} else {
if (startPart === 0 && isAbsolute2)
ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
else
ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
ret.ext = path.slice(startDot, end);
}
if (startPart > 0)
ret.dir = path.slice(0, startPart - 1);
else if (isAbsolute2)
ret.dir = "/";
return ret;
}
var sep = "/";
var delimiter = ":";
var posix = ((p) => (p.posix = p, p))({ resolve, normalize, isAbsolute, join, relative, _makeLong, dirname, basename, extname, format, parse, sep, delimiter, win32: null, posix: null });
// src/router.js
var router = (routes) => {
const getHash = () => window.location.hash.slice(1) || "/";
const path = $(getHash());
@@ -500,26 +832,9 @@
router.to = (p) => window.location.hash = p.replace(/^#?\/?/, "#/");
router.back = () => window.history.back();
router.path = () => window.location.hash.replace(/^#/, "") || "/";
var Fragment = (props) => props.children;
var mount = (comp, target) => {
const t = typeof target === "string" ? doc.querySelector(target) : target;
if (!t)
return;
if (MOUNTED_NODES.has(t))
MOUNTED_NODES.get(t).destroy();
const inst = render(isFunc(comp) ? comp : () => comp);
t.replaceChildren(inst.container);
MOUNTED_NODES.set(t, inst);
return inst;
};
if (typeof window !== "undefined") {
"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
}
// src/build_umd.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 });
}
})();

2
dist/sigpro.min.js vendored

File diff suppressed because one or more lines are too long

77
dist/vite.js vendored
View File

@@ -1,77 +0,0 @@
/**
* SigPro Vite Plugin - File-based Routing
* @module sigpro/vite
*/
import fs from 'node:fs';
import path from 'node:path';
export default function sigproRouter() {
const virtualModuleId = 'virtual:sigpro-routes';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
// Helper para escanear archivos
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));
};
// Transformador de ruta de archivo a URL de router
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');
// Obtenemos y ordenamos archivos (rutas estáticas primero, luego dinámicas)
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);
// Hacemos la ruta relativa al proyecto para que el import de Vite sea limpio
const relativeImport = './' + path.relative(root, fullPath).replace(/\\/g, '/');
routeEntries += ` { path: '${urlPath}', component: async () => (await import('/${relativeImport}')).default },\n`;
});
// Fallback 404 si no existe una ruta comodín
if (!routeEntries.includes("path: '*'")) {
routeEntries += ` { path: '*', component: () => document.createTextNode('404 - Not Found') },\n`;
}
return `export const routes = [\n${routeEntries}];`;
}
};
}
export { sigproRouter };

View File

@@ -1,6 +1,6 @@
<div class="w-full -mt-10"><section class="relative py-20 overflow-hidden border-b border-base-200/30 text-center flex flex-col items-center"><div class="relative z-10 max-w-5xl mx-auto px-6 flex flex-col items-center"><div class="flex justify-center mb-10"><img src="logo.svg" alt="SigPro Logo" class="w-48 h-48 md:w-64 md:h-64 object-contain drop-shadow-2xl"></div><h1 class="text-7xl md:text-9xl font-black tracking-tighter mb-4 bg-clip-text text-transparent bg-linear-to-r from-primary via-secondary to-accent !text-center w-full">SigPro</h1><div class="text-2xl md:text-3xl font-bold text-base-content/90 mb-4 !text-center w-full">Atomic Unified Reactive Engine</div><div class="text-xl text-base-content/60 max-w-3xl mx-auto mb-10 leading-relaxed italic text-balance font-light !text-center w-full">"The efficiency of direct DOM manipulation with the elegance of functional reactivity."</div><div class="flex flex-wrap justify-center gap-4 w-full"><a href="#/install" class="btn btn-primary btn-lg shadow-xl shadow-primary/20 group px-10 border-none">Get Started <span class="group-hover:translate-x-1 transition-transform inline-block">→</span></a><button onclick="window.open('https://git.natxocc.com/natxocc/sigpro')" class="btn btn-outline btn-lg border-base-300 hover:bg-base-300 hover:text-base-content">Gitea</button></div></div><div class="absolute top-0 left-1/2 -translate-x-1/2 w-full h-full -z-0 opacity-10 pointer-events-none"><div class="absolute top-10 left-1/4 w-96 h-96 bg-primary filter blur-3xl rounded-full animate-pulse"></div><div class="absolute bottom-10 right-1/4 w-96 h-96 bg-accent filter blur-3xl rounded-full animate-pulse" style="animation-delay: 2.5s"></div></div></section></div>
<section class="max-w-6xl mx-auto px-6 py-16"><div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 items-stretch"><div class="card bg-base-200/30 border border-base-300 hover:border-primary/40 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black text-primary italic">FUNCTIONAL</h3><p class="text-sm opacity-70">No strings. No templates. Pure JS function calls for instant DOM mounting.</p></div></div><div class="card bg-base-200/30 border border-base-300 hover:border-secondary/40 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black text-secondary italic">ATOMIC</h3><p class="text-sm opacity-70">Finegrained signals update exactly what changes. No VDOM diffing overhead.</p></div></div><div class="card bg-base-200/30 border border-base-300 hover:border-accent/40 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black text-accent italic">ULTRATHIN</h3><p class="text-sm opacity-70">Sub3KB runtime. Infinitely smaller bundle than React, Vue or even Svelte.</p></div></div><div class="card bg-base-200/30 border border-base-300 hover:border-base-content/20 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black italic text-base-content">COMPILERFREE</h3><p class="text-sm opacity-70">Standard Vanilla JS. What you write is what the browser executes. Period.</p></div></div></div></section>
<section class="max-w-6xl mx-auto px-6 py-16"><div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 items-stretch"><div class="card bg-base-200/30 border border-base-300 hover:border-primary/40 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black text-primary italic">FUNCTIONAL</h3><p class="text-sm opacity-70">No strings. No templates. Pure JS function calls for instant DOM mounting.</p></div></div><div class="card bg-base-200/30 border border-base-300 hover:border-secondary/40 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black text-secondary italic">ATOMIC</h3><p class="text-sm opacity-70">Finegrained signals update exactly what changes. No VDOM diffing overhead.</p></div></div><div class="card bg-base-200/30 border border-base-300 hover:border-accent/40 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black text-accent italic">ULTRATHIN</h3><p class="text-sm opacity-70">less than 3KB runtime. Infinitely smaller bundle than React, Vue or even Svelte.</p></div></div><div class="card bg-base-200/30 border border-base-300 hover:border-base-content/20 transition-all p-1"><div class="card-body p-6"><h3 class="card-title text-xl font-black italic text-base-content">COMPILERFREE</h3><p class="text-sm opacity-70">Standard Vanilla JS. What you write is what the browser executes. Period.</p></div></div></div></section>
<div class="max-w-6xl mx-auto px-6 py-8"><h2 class="text-4xl font-black mb-6">Functional DOM Construction</h2><p class="text-lg opacity-80 mb-6">SigPro replaces slow "Template Parsing" with <strong>HighEfficiency Function Calls</strong>. While other frameworks force the browser to parse strings of HTML or execute complex JSX transformations, SigPro uses a direct functional approach.</p>

View File

@@ -2,15 +2,14 @@
* **Introduction**
* [Installation](install.md)
* [Vite Plugin](vite/plugin.md)
* [Router](router.md)
* **API Reference**
* [Quick Start](api/quick.md)
* [Signals & Proxies](api/signal.md)
* [$ignal](api/signal.md)
* [watch](api/watch.md)
* [when](api/when.md)
* [each](api/each.md)
* [router](api/router.md)
* [mount](api/mount.md)
* [h](api/h.md)

View File

@@ -169,7 +169,7 @@ batch(() => {
Hashbased SPA router. Returns a DOM node that renders the current route.
```javascript
import { router } from 'sigpro'
import { router } from 'sigpro/router' // import router
const routes = [
{ path: '/', component: () => div({}, 'Home') },

View File

@@ -1,6 +1,6 @@
# The Signal Function: `$( )`
# The Signal Function: `$()`
The `$( )` function is the core constructor of SigPro. It defines how data is stored, computed, and persisted.
The `$()` function is the **only** reactive primitive in SigPro. It defines how data is stored, computed, and persisted. For complex nested objects, you compose signals naturally.
## Function Signature
@@ -99,175 +99,181 @@ When calling the setter, you can pass an **updater function** to access the curr
---
# The Reactive Object: `$$( )`
## Composing Signals for Complex State
The `$$( )` function creates a reactive proxy for complex nested objects. Unlike `$()`, which tracks a single value, `$$()` tracks **every property access** automatically.
## Function Signature
```typescript
$$<T extends object>(obj: T): T
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`obj`** | `object` | Yes | The object to make reactive. Properties are tracked recursively. |
**Returns:** A reactive proxy that behaves like the original object but triggers updates when any property changes.
---
## Usage Patterns
For nested objects, **compose signals** instead of using magic proxies. This gives you explicit control over reactivity and memory.
### 1. Simple Object
<div id="demo-dollar-simple"></div>
<div id="demo-compose-simple"></div>
```javascript
{
const state = $$({ count: 0, name: "Juan" });
watch(() => console.log(`Count is now ${state.count}`));
const count = $(0);
const name = $("Juan");
// Optionally create a derived combined state
const state = $(() => ({ count: count(), name: name() }));
const App = () => div([
p(() => `Count: ${state.count}, Name: ${state.name}`),
button({ onClick: () => state.count++ }, "Increment count"),
button({ onClick: () => state.name = state.name === "Juan" ? "Ana" : "Juan" }, "Toggle name")
p(() => `Count: ${count()}, Name: ${name()}`),
button({ onClick: () => count(count() + 1) }, "Increment count"),
button({ onClick: () => name(name() === "Juan" ? "Ana" : "Juan") }, "Toggle name")
]);
setTimeout(() => mount(App, '#demo-dollar-simple'), 50);
setTimeout(() => mount(App, '#demo-compose-simple'), 50);
}
```
### 2. Deep Reactivity
### 2. Deeply Nested State
<div id="demo-dollar-deep"></div>
<div id="demo-compose-deep"></div>
```javascript
{
const user = $$({
profile: {
name: "Juan",
address: { city: "Madrid", zip: "28001" }
}
});
const profileName = $("Juan");
const profileCity = $("Madrid");
const profileZip = $("28001");
watch(() => user.profile.address.city, () => console.log("City changed"));
// Computed derived values
const fullAddress = $(() => `${profileCity()}, ${profileZip()}`);
watch(profileCity, () => console.log("City changed to:", profileCity()));
const App = () => div([
p(() => `City: ${user.profile.address.city}`),
button({ onClick: () => user.profile.address.city = "Barcelona" }, "Change to Barcelona")
p(() => `Name: ${profileName()}`),
p(() => `City: ${profileCity()}`),
p(() => `Full address: ${fullAddress()}`),
button({ onClick: () => profileCity("Barcelona") }, "Change to Barcelona")
]);
setTimeout(() => mount(App, '#demo-dollar-deep'), 50);
setTimeout(() => mount(App, '#demo-compose-deep'), 50);
}
```
### 3. Arrays
<div id="demo-dollar-array"></div>
<div id="demo-compose-array"></div>
```javascript
{
const todos = $$([
const todos = $([
{ id: 1, text: "Learn SigPro", done: false },
{ id: 2, text: "Build an app", done: false }
]);
watch(() => todos.length, () => console.log(`You have ${todos.length} todos`));
const todoCount = $(() => todos().length);
watch(todoCount, () => console.log(`You have ${todoCount()} todos`));
const App = () => div([
ul(() => todos.map(todo => li(todo.text + (todo.done ? " ✓" : "")))),
button({ onClick: () => todos.push({ id: Date.now(), text: "New todo", done: false }) }, "Add todo"),
button({ onClick: () => todos[0].done = !todos[0].done }, "Toggle first todo")
ul(() => todos().map(todo => li(todo.text + (todo.done ? " ✓" : "")))),
button({ onClick: () => todos(prev => [...prev, { id: Date.now(), text: "New todo", done: false }]) }, "Add todo"),
button({ onClick: () => {
const updated = [...todos()];
updated[0] = { ...updated[0], done: !updated[0].done };
todos(updated);
}}, "Toggle first todo")
]);
setTimeout(() => mount(App, '#demo-dollar-array'), 50);
setTimeout(() => mount(App, '#demo-compose-array'), 50);
}
```
### 4. Mixed with Signals
### 4. Complete Form Example
<div id="demo-dollar-mixed"></div>
<div id="demo-compose-form"></div>
```javascript
{
const form = $$({
fields: { email: "", password: "" },
isValid: $(false)
});
const email = $("");
const password = $("");
const isValid = $(() => email().includes("@") && password().length > 6);
const canSubmit = $(() =>
form.fields.email.includes("@") &&
form.fields.password.length > 6
);
watch(canSubmit, valid => form.isValid(valid));
watch(isValid, valid => console.log("Form valid:", valid));
const App = () => div([
input({ type: "email", placeholder: "Email", value: () => form.fields.email, onInput: e => form.fields.email = e.target.value }),
input({ type: "password", placeholder: "Password", value: () => form.fields.password, onInput: e => form.fields.password = e.target.value }),
p(() => `Form valid: ${form.isValid() ? "Yes" : "No"}`)
input({
type: "email",
placeholder: "Email",
value: email,
onInput: e => email(e.target.value)
}),
input({
type: "password",
placeholder: "Password",
value: password,
onInput: e => password(e.target.value)
}),
p(() => `Form valid: ${isValid() ? "Yes" : "No"}`)
]);
setTimeout(() => mount(App, '#demo-dollar-mixed'), 50);
setTimeout(() => mount(App, '#demo-compose-form'), 50);
}
```
---
## Key Differences: `$()` vs `$$()`
## Best Practices for Complex State
| Feature | `$()` Signal | `$$()` Reactive |
| :--- | :--- | :--- |
| **Primitives** | ✅ Works directly | ❌ Needs wrapper object |
| **Objects** | Manual tracking | ✅ Automatic deep tracking |
| **Nested properties** | ❌ Not reactive | ✅ Fully reactive |
| **Arrays** | Requires reassignment | ✅ Methods (push, pop, etc.) work |
| **Syntax** | `count()` / `count(5)` | `state.count = 5` |
| **LocalStorage** | ✅ Built-in | ❌ (use `$()` for persistence) |
| **Performance** | Lighter | Slightly heavier (Proxy) |
| **Destructuring** | ✅ Safe | ❌ Breaks reactivity |
---
## When to Use Each
### Use `$()` when:
<div id="demo-use-dollar"></div>
### ✅ DO: Compose signals explicitly
```javascript
{
const count = $(0);
const firstName = $("John");
const lastName = $("Doe");
const fullName = $(() => `${firstName()} ${lastName()}`);
const App = () => div([
p(() => `Count: ${count()}`),
button({ onClick: () => count(count() + 1) }, "Count up"),
p(() => `Full name: ${fullName()}`),
input({ value: firstName, placeholder: "First name" }),
input({ value: lastName, placeholder: "Last name" })
]);
setTimeout(() => mount(App, '#demo-use-dollar'), 50);
// Clear, predictable, and memory-safe
const user = {
name: $("Juan"),
email: $("juan@example.com"),
preferences: {
theme: $("dark"),
notifications: $(true)
}
}
// Computed values derived from composition
const userDisplay = $(() => `${user.name()} <${user.email()}>`)
```
### Use `$$()` when:
<div id="demo-use-dollar-dollar"></div>
### ✅ DO: Create store patterns
```javascript
{
const form = $$({ email: "", password: "" });
const settings = $$({ theme: "dark", notifications: true });
const App = () => div([
input({ placeholder: "Email", onInput: e => form.email = e.target.value }),
input({ placeholder: "Password", type: "password", onInput: e => form.password = e.target.value }),
p(() => `Email: ${form.email}, Password: ${form.password}`),
button({ onClick: () => settings.theme = settings.theme === "dark" ? "light" : "dark" }, "Toggle theme"),
p(() => `Current theme: ${settings.theme}`)
]);
setTimeout(() => mount(App, '#demo-use-dollar-dollar'), 50);
const createUserStore = () => {
const name = $("")
const email = $("")
const isValid = $(() => name().length > 0 && email().includes("@"))
const actions = {
setName: (value) => name(value),
setEmail: (value) => email(value),
reset: () => {
name("")
email("")
}
}
return { name, email, isValid, ...actions }
}
const userStore = createUserStore()
```
### ❌ DON'T: Try to wrap objects with signals
```javascript
// Wrong - loses reactivity on nested properties
const user = $({ name: "Juan", email: "..." })
user().name = "Ana" // ❌ Not reactive!
// Correct - each property its own signal
const userName = $("Juan")
const userEmail = $("...")
```
### ❌ DON'T: Destructure signals in reactive contexts
```javascript
// Wrong - breaks tracking
const { name, email } = user
watch(() => name(), ...) // ❌ 'name' is not tracked properly
// Correct - use the original signal
watch(() => user.name(), ...) // ✅
```
---
@@ -276,115 +282,121 @@ $$<T extends object>(obj: T): T
### ✅ DO:
```javascript
// Access properties directly
state.count = 10;
state.user.name = "Ana";
todos.push(newItem);
// Update by recreating objects for arrays
todos(prev => [...prev, newTodo])
// Track in effects
watch(() => state.count, () => {});
watch(() => state.user.name, () => {});
// Update objects immutably
const current = user()
user({ ...current, name: "Ana" })
// Track individual signals
watch(() => user.name(), () => {})
watch(() => user.email(), () => {})
```
### ❌ DON'T:
```javascript
// Destructuring breaks reactivity
const { count, user } = state; // ❌ count and user are not reactive
// Mutate objects directly
user().name = "Ana" // ❌ Not reactive
// Reassigning the whole object
state = { count: 10 }; // ❌ Loses reactivity
// Mutate arrays in place
todos().push(newTodo) // ❌ Not reactive
// Using primitive directly
const count = $$(0); // ❌ Doesn't work (use $() instead)
// Destructure in component bodies
const { name, email } = user // ❌ Breaks reactivity
```
---
## Automatic Cleanup
Like all SigPro reactive primitives, `$$()` integrates with the cleanup system:
All signals integrate with the cleanup system:
- Effects tracking reactive properties are automatically disposed
- No manual cleanup needed
- Works with `watch`, `when`, and `each`
```javascript
// Effects are automatically disposed when components unmount
const name = $("Juan")
watch(name, () => console.log("Name changed"))
---
## Technical Comparison
| Aspect | `$()` | `$$()` |
| :--- | :--- | :--- |
| **Implementation** | Closure with Set | Proxy with WeakMap |
| **Tracking** | Explicit (function call) | Implicit (property access) |
| **Memory** | Minimal | Slightly more (WeakMap cache) |
| **Use Case** | Simple state | Complex state |
| **Learning Curve** | Low | Low (feels like plain JS) |
// Manual cleanup if needed
const stop = watch(name, callback)
stop() // Clean up manually
```
---
## Complete Example
<div id="demo-complete"></div>
<div id="demo-complete-final"></div>
```javascript
{
const app = {
theme: $("dark", "theme_complete"),
sidebarOpen: $(true),
user: $$({ name: "", email: "", preferences: { notifications: true, language: "es" } }),
isLoggedIn: $(() => !!app.user.name),
login(name, email) {
app.user.name = name;
app.user.email = email;
},
logout() {
app.user.name = "";
app.user.email = "";
app.user.preferences.notifications = true;
}
};
// All state as explicit signals
const theme = $("dark", "theme_complete")
const sidebarOpen = $(true)
const userName = $("")
const userEmail = $("")
const notifications = $(true)
const language = $("es")
// Computed signals
const isLoggedIn = $(() => !!userName() && !!userEmail())
// Actions as plain functions
const login = (name, email) => {
userName(name)
userEmail(email)
}
const logout = () => {
userName("")
userEmail("")
notifications(true) // Reset on logout
}
// Components using signals directly
const LoginForm = () => div([
input({ placeholder: "Name", onInput: e => app.user.name = e.target.value }),
input({ placeholder: "Email", onInput: e => app.user.email = e.target.value }),
button({ onClick: () => app.login(app.user.name, app.user.email) }, "Login")
]);
input({
placeholder: "Name",
onInput: e => userName(e.target.value)
}),
input({
placeholder: "Email",
onInput: e => userEmail(e.target.value)
}),
button({
onClick: () => login(userName(), userEmail())
}, "Login")
])
const UserProfile = () => div([
h2(() => `Welcome ${app.user.name}`),
p(() => `Email: ${app.user.email}`),
p(() => `Notifications: ${app.user.preferences.notifications ? "ON" : "OFF"}`),
button({ onClick: () => app.user.preferences.notifications = !app.user.preferences.notifications }, "Toggle Notifications"),
button({ onClick: app.logout }, "Logout")
]);
h2(() => `Welcome ${userName()}`),
p(() => `Email: ${userEmail()}`),
p(() => `Notifications: ${notifications() ? "ON" : "OFF"}`),
p(() => `Language: ${language()}`),
button({
onClick: () => notifications(!notifications())
}, "Toggle Notifications"),
button({ onClick: logout }, "Logout")
])
const App = () => div({ class: "complete-example" }, [
when(() => app.isLoggedIn(), () => UserProfile(), () => LoginForm())
]);
when(() => isLoggedIn(), () => UserProfile(), () => LoginForm())
])
setTimeout(() => mount(App, '#demo-complete'), 50);
setTimeout(() => mount(App, '#demo-complete-final'), 50)
}
```
---
## Migration from `$()` to `$$()`
## Summary
If you have code using nested signals:
With **only `$()`** as your reactive primitive:
```javascript
// Before - Manual nesting
const user = $({
name: $(""),
email: $("")
});
user().name("Juan"); // Need to call inner signal
// After - Automatic nesting
const user = $$({
name: "",
email: ""
});
user.name = "Juan"; // Direct assignment
```
-**Explicit** - You know exactly what's reactive
-**Memory safe** - No hidden proxies or WeakMap caches
-**Predictable** - No magic, just signals
-**Performant** - Minimal overhead
-**Debuggable** - Clear data flow
Complex state is built by **composing signals**, not by wrapping objects. This gives you the same power as reactive proxies but with better control and fewer surprises.

View File

@@ -182,7 +182,7 @@ SigPro stands out by removing the "Build Step" tax and the "Virtual DOM" overhea
| Feature | **SigPro** | **SolidJS** | **Svelte** | **React** | **Vue** |
| :----------------- | :--------------- | :----------- | :----------- | :---------- | :---------- |
| **Bundle Size** | **~3KB** | ~7KB | ~4KB | ~40KB+ | ~30KB |
| **Bundle Size** | **<3KB** | ~7KB | ~4KB | ~40KB+ | ~30KB |
| **DOM Strategy** | **Direct DOM** | Direct DOM | Compiled DOM | Virtual DOM | Virtual DOM |
| **Reactivity** | **Fine-grained** | Fine-grained | Compiled | Re-renders | Proxies |
| **Build Step** | **Optional** | Required | Required | Required | Optional |

View File

@@ -17,7 +17,7 @@ router(routes: Route[]): HTMLElement
**Returns:** A `div` element (with class `"router-hook"`) that acts as the router outlet. The router automatically destroys the previous view and mounts the matched component when the hash changes.
> **Availability:** `router` and its helper methods (`router.to`, `router.back`, `router.path`, `router.params`) are exported from the SigPro module. In **ESM** you must import them (`import { router } from 'sigpro'`). In the **IIFE** classic script, they are automatically available on `window`. The examples below assume the functions are already in scope.
> **Availability:** `router` and its helper methods (`router.to`, `router.back`, `router.path`, `router.params`) are exported from the SigPro module. In **ESM** you must import them (`import { router } from 'sigpro/router'`). In the **IIFE** classic script, they are automatically available on `window`. The examples below assume the functions are already in scope.
---
@@ -187,3 +187,152 @@ mount(App, "#app");
| `router.back()` | Goes back in history. |
| `router.path()` | Returns the current path without `#`. |
| `router.params()` | Reactive signal of the current route parameters. |
# Vite Plugin: File-based Routing
The `sigproRouter` plugin for Vite automates route generation by scanning your `pages` directory. It creates a **virtual module** that you can import directly into your code, eliminating the need to maintain a manual routes array.
## 1. Project Structure
To use the plugin, organize your files within the `src/pages` directory. The folder hierarchy directly determines your application's URL structure. SigPro uses brackets `[param]` for dynamic segments.
<div class="mockup-code bg-base-300 text-base-content shadow-xl my-8">
<pre><code>my-sigpro-app/
├── src/
│ ├── pages/
│ │ ├── index.js → #/
│ │ ├── about.js → #/about
│ │ ├── users/
│ │ │ └── [id].js → #/users/:id
│ │ └── blog/
│ │ ├── index.js → #/blog
│ │ └── [slug].js → #/blog/:slug
│ ├── App.js (Main Layout)
│ └── main.js (Entry Point)
├── vite.config.js
└── package.json</code></pre>
</div>
---
## 2. Setup & Configuration
Add the plugin to your `vite.config.js`. It works out of the box with zero configuration.
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import { sigproRouter } from 'sigpro/router';
export default defineConfig({
plugins: [sigproRouter()]
});
```
---
## 3. Implementation
Thanks to **SigPro's synchronous initialization**, you no longer need to wrap your mounting logic in `.then()` blocks.
<div class="tabs tabs-box w-full mt-8 mb-12 bg-base-200/50 p-2 border border-base-300">
<input type="radio" name="route_impl" class="tab" aria-label="Option A: Direct in main.js" checked />
<div class="tab-content bg-base-100 border-base-300 rounded-lg p-6 mt-2">
```javascript
// src/main.js
import { mount } from 'sigpro';
import { router } from 'sigpro/router';
import { routes } from 'virtual:sigpro-routes';
// The Core already has Router ready
mount(router(routes), '#app');
```
</div>
<input type="radio" name="route_impl" class="tab" aria-label="Option B: Inside App.js (Persistent Layout)" />
<div class="tab-content bg-base-100 border-base-300 rounded-lg p-6 mt-2">
```javascript
// src/App.js
import { routes } from 'virtual:sigpro-routes';
export default () => div({ class: 'layout' }, [
header([
h1("SigPro App"),
nav([
button({ onclick: () => Router.go('/') }, "Home"),
button({ onclick: () => Router.go('/blog') }, "Blog")
])
]),
// Only the content inside <main> will be swapped reactively
main(Router(routes))
]);
```
</div>
</div>
---
## 4. Route Mapping Reference
The plugin follows a simple convention to transform your file system into a routing map.
<div class="overflow-x-auto my-8">
<table class="table table-zebra w-full">
<thead class="bg-base-200">
<tr>
<th>File Path</th>
<th>Generated Path</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>index.js</code></td>
<td class="font-mono text-primary font-bold">/</td>
<td>The application root.</td>
</tr>
<tr>
<td><code>about.js</code></td>
<td class="font-mono text-primary font-bold">/about</td>
<td>A static page.</td>
</tr>
<tr>
<td><code>[id].js</code></td>
<td class="font-mono text-primary font-bold">/:id</td>
<td>Dynamic parameter (passed to the component).</td>
</tr>
<tr>
<td><code>blog/index.js</code></td>
<td class="font-mono text-primary font-bold">/blog</td>
<td>Folder index page.</td>
</tr>
<tr>
<td><code>_utils.js</code></td>
<td class="italic opacity-50 text-error">Ignored</td>
<td>Files starting with <code>_</code> are excluded from routing.</td>
</tr>
</tbody>
</table>
</div>
---
## 5. How it Works (Vite Virtual Module)
The plugin generates a virtual module named `virtual:sigpro-routes`. This module exports an array of objects compatible with `Router()`:
```javascript
// Internal representation generated by the plugin
export const routes = [
{ path: '/', component: () => import('/src/pages/index.js') },
{ path: '/users/:id', component: () => import('/src/pages/users/[id].js') },
// ...
];
```
Because it uses dynamic `import()`, Vite automatically performs **Code Splitting**, meaning each page is its own small JS file that only loads when the user navigates to it.

View File

@@ -1,4 +1,42 @@
(() => {
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
function __accessProp(key) {
return this[key];
}
var __toESMCache_node;
var __toESMCache_esm;
var __toESM = (mod, isNodeMode, target) => {
var canCache = mod != null && typeof mod === "object";
if (canCache) {
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
var cached = cache.get(mod);
if (cached)
return cached;
}
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: __accessProp.bind(mod, key),
enumerable: true
});
if (canCache)
cache.set(mod, to);
return to;
};
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined")
return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/sigpro.js
var isFunc = (f) => typeof f === "function";
var isObj = (o) => o && typeof o === "object";
@@ -10,8 +48,6 @@
var isFlushing = false;
var batchDepth = 0;
var effectQueue = new Set;
var proxyCache = new WeakMap;
var ITER = Symbol("iter");
var MOUNTED_NODES = new WeakMap;
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
@@ -172,52 +208,6 @@
return val;
};
};
var $$ = (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(target2, key, receiver) {
if (typeof key !== "symbol")
trackUpdate(getSubs(key));
return $$(Reflect.get(target2, key, receiver));
},
set(target2, key, value, receiver) {
const hadKey = Reflect.has(target2, key);
const oldValue = Reflect.get(target2, key, receiver);
const result = Reflect.set(target2, key, value, receiver);
if (result && !Object.is(oldValue, value)) {
trackUpdate(getSubs(key), true);
if (!hadKey)
trackUpdate(getSubs(ITER), true);
}
return result;
},
deleteProperty(target2, key) {
const result = Reflect.deleteProperty(target2, key);
if (result) {
trackUpdate(getSubs(key), true);
trackUpdate(getSubs(ITER), true);
}
return result;
},
ownKeys(target2) {
trackUpdate(getSubs(ITER));
return Reflect.ownKeys(target2);
}
});
proxyCache.set(target, proxy);
return proxy;
};
var watch = (sources, cb) => {
if (cb === undefined) {
const effect2 = createEffect(sources);
@@ -467,6 +457,348 @@
});
return root;
};
var Fragment = (props) => props.children;
var mount = (comp, target) => {
const t = typeof target === "string" ? doc.querySelector(target) : target;
if (!t)
return;
if (MOUNTED_NODES.has(t))
MOUNTED_NODES.get(t).destroy();
const inst = render(isFunc(comp) ? comp : () => comp);
t.replaceChildren(inst.container);
MOUNTED_NODES.set(t, inst);
return inst;
};
if (typeof window !== "undefined") {
"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
}
// src/router.js
var import_node_fs = (() => ({}));
// node:path
function assertPath(path) {
if (typeof path !== "string")
throw TypeError("Path must be a string. Received " + JSON.stringify(path));
}
function normalizeStringPosix(path, allowAboveRoot) {
var res = "", lastSegmentLength = 0, lastSlash = -1, dots = 0, code;
for (var i = 0;i <= path.length; ++i) {
if (i < path.length)
code = path.charCodeAt(i);
else if (code === 47)
break;
else
code = 47;
if (code === 47) {
if (lastSlash === i - 1 || dots === 1)
;
else if (lastSlash !== i - 1 && dots === 2) {
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
if (res.length > 2) {
var lastSlashIndex = res.lastIndexOf("/");
if (lastSlashIndex !== res.length - 1) {
if (lastSlashIndex === -1)
res = "", lastSegmentLength = 0;
else
res = res.slice(0, lastSlashIndex), lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
lastSlash = i, dots = 0;
continue;
}
} else if (res.length === 2 || res.length === 1) {
res = "", lastSegmentLength = 0, lastSlash = i, dots = 0;
continue;
}
}
if (allowAboveRoot) {
if (res.length > 0)
res += "/..";
else
res = "..";
lastSegmentLength = 2;
}
} else {
if (res.length > 0)
res += "/" + path.slice(lastSlash + 1, i);
else
res = path.slice(lastSlash + 1, i);
lastSegmentLength = i - lastSlash - 1;
}
lastSlash = i, dots = 0;
} else if (code === 46 && dots !== -1)
++dots;
else
dots = -1;
}
return res;
}
function _format(sep, pathObject) {
var dir = pathObject.dir || pathObject.root, base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
if (!dir)
return base;
if (dir === pathObject.root)
return dir + base;
return dir + sep + base;
}
function resolve() {
var resolvedPath = "", resolvedAbsolute = false, cwd;
for (var i = arguments.length - 1;i >= -1 && !resolvedAbsolute; i--) {
var path;
if (i >= 0)
path = arguments[i];
else {
if (cwd === undefined)
cwd = process.cwd();
path = cwd;
}
if (assertPath(path), path.length === 0)
continue;
resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = path.charCodeAt(0) === 47;
}
if (resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute), resolvedAbsolute)
if (resolvedPath.length > 0)
return "/" + resolvedPath;
else
return "/";
else if (resolvedPath.length > 0)
return resolvedPath;
else
return ".";
}
function normalize(path) {
if (assertPath(path), path.length === 0)
return ".";
var isAbsolute = path.charCodeAt(0) === 47, trailingSeparator = path.charCodeAt(path.length - 1) === 47;
if (path = normalizeStringPosix(path, !isAbsolute), path.length === 0 && !isAbsolute)
path = ".";
if (path.length > 0 && trailingSeparator)
path += "/";
if (isAbsolute)
return "/" + path;
return path;
}
function isAbsolute(path) {
return assertPath(path), path.length > 0 && path.charCodeAt(0) === 47;
}
function join() {
if (arguments.length === 0)
return ".";
var joined;
for (var i = 0;i < arguments.length; ++i) {
var arg = arguments[i];
if (assertPath(arg), arg.length > 0)
if (joined === undefined)
joined = arg;
else
joined += "/" + arg;
}
if (joined === undefined)
return ".";
return normalize(joined);
}
function relative(from, to) {
if (assertPath(from), assertPath(to), from === to)
return "";
if (from = resolve(from), to = resolve(to), from === to)
return "";
var fromStart = 1;
for (;fromStart < from.length; ++fromStart)
if (from.charCodeAt(fromStart) !== 47)
break;
var fromEnd = from.length, fromLen = fromEnd - fromStart, toStart = 1;
for (;toStart < to.length; ++toStart)
if (to.charCodeAt(toStart) !== 47)
break;
var toEnd = to.length, toLen = toEnd - toStart, length = fromLen < toLen ? fromLen : toLen, lastCommonSep = -1, i = 0;
for (;i <= length; ++i) {
if (i === length) {
if (toLen > length) {
if (to.charCodeAt(toStart + i) === 47)
return to.slice(toStart + i + 1);
else if (i === 0)
return to.slice(toStart + i);
} else if (fromLen > length) {
if (from.charCodeAt(fromStart + i) === 47)
lastCommonSep = i;
else if (i === 0)
lastCommonSep = 0;
}
break;
}
var fromCode = from.charCodeAt(fromStart + i), toCode = to.charCodeAt(toStart + i);
if (fromCode !== toCode)
break;
else if (fromCode === 47)
lastCommonSep = i;
}
var out = "";
for (i = fromStart + lastCommonSep + 1;i <= fromEnd; ++i)
if (i === fromEnd || from.charCodeAt(i) === 47)
if (out.length === 0)
out += "..";
else
out += "/..";
if (out.length > 0)
return out + to.slice(toStart + lastCommonSep);
else {
if (toStart += lastCommonSep, to.charCodeAt(toStart) === 47)
++toStart;
return to.slice(toStart);
}
}
function _makeLong(path) {
return path;
}
function dirname(path) {
if (assertPath(path), path.length === 0)
return ".";
var code = path.charCodeAt(0), hasRoot = code === 47, end = -1, matchedSlash = true;
for (var i = path.length - 1;i >= 1; --i)
if (code = path.charCodeAt(i), code === 47) {
if (!matchedSlash) {
end = i;
break;
}
} else
matchedSlash = false;
if (end === -1)
return hasRoot ? "/" : ".";
if (hasRoot && end === 1)
return "//";
return path.slice(0, end);
}
function basename(path, ext) {
if (ext !== undefined && typeof ext !== "string")
throw TypeError('"ext" argument must be a string');
assertPath(path);
var start = 0, end = -1, matchedSlash = true, i;
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
if (ext.length === path.length && ext === path)
return "";
var extIdx = ext.length - 1, firstNonSlashEnd = -1;
for (i = path.length - 1;i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else {
if (firstNonSlashEnd === -1)
matchedSlash = false, firstNonSlashEnd = i + 1;
if (extIdx >= 0)
if (code === ext.charCodeAt(extIdx)) {
if (--extIdx === -1)
end = i;
} else
extIdx = -1, end = firstNonSlashEnd;
}
}
if (start === end)
end = firstNonSlashEnd;
else if (end === -1)
end = path.length;
return path.slice(start, end);
} else {
for (i = path.length - 1;i >= 0; --i)
if (path.charCodeAt(i) === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else if (end === -1)
matchedSlash = false, end = i + 1;
if (end === -1)
return "";
return path.slice(start, end);
}
}
function extname(path) {
assertPath(path);
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, preDotState = 0;
for (var i = path.length - 1;i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1)
matchedSlash = false, end = i + 1;
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1)
preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
return "";
return path.slice(startDot, end);
}
function format(pathObject) {
if (pathObject === null || typeof pathObject !== "object")
throw TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
return _format("/", pathObject);
}
function parse(path) {
assertPath(path);
var ret = { root: "", dir: "", base: "", ext: "", name: "" };
if (path.length === 0)
return ret;
var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
if (isAbsolute2)
ret.root = "/", start = 1;
else
start = 0;
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, i = path.length - 1, preDotState = 0;
for (;i >= start; --i) {
if (code = path.charCodeAt(i), code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1)
matchedSlash = false, end = i + 1;
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1)
preDotState = -1;
}
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
if (end !== -1)
if (startPart === 0 && isAbsolute2)
ret.base = ret.name = path.slice(1, end);
else
ret.base = ret.name = path.slice(startPart, end);
} else {
if (startPart === 0 && isAbsolute2)
ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
else
ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
ret.ext = path.slice(startDot, end);
}
if (startPart > 0)
ret.dir = path.slice(0, startPart - 1);
else if (isAbsolute2)
ret.dir = "/";
return ret;
}
var sep = "/";
var delimiter = ":";
var posix = ((p) => (p.posix = p, p))({ resolve, normalize, isAbsolute, join, relative, _makeLong, dirname, basename, extname, format, parse, sep, delimiter, win32: null, posix: null });
// src/router.js
var router = (routes) => {
const getHash = () => window.location.hash.slice(1) || "/";
const path = $(getHash());
@@ -500,26 +832,9 @@
router.to = (p) => window.location.hash = p.replace(/^#?\/?/, "#/");
router.back = () => window.history.back();
router.path = () => window.location.hash.replace(/^#/, "") || "/";
var Fragment = (props) => props.children;
var mount = (comp, target) => {
const t = typeof target === "string" ? doc.querySelector(target) : target;
if (!t)
return;
if (MOUNTED_NODES.has(t))
MOUNTED_NODES.get(t).destroy();
const inst = render(isFunc(comp) ? comp : () => comp);
t.replaceChildren(inst.container);
MOUNTED_NODES.set(t, inst);
return inst;
};
if (typeof window !== "undefined") {
"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
}
// src/build_umd.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 });
}
})();

View File

@@ -1,146 +0,0 @@
# Vite Plugin: File-based Routing
The `sigproRouter` plugin for Vite automates route generation by scanning your `pages` directory. It creates a **virtual module** that you can import directly into your code, eliminating the need to maintain a manual routes array.
## 1. Project Structure
To use the plugin, organize your files within the `src/pages` directory. The folder hierarchy directly determines your application's URL structure. SigPro uses brackets `[param]` for dynamic segments.
<div class="mockup-code bg-base-300 text-base-content shadow-xl my-8">
<pre><code>my-sigpro-app/
├── src/
│ ├── pages/
│ │ ├── index.js → #/
│ │ ├── about.js → #/about
│ │ ├── users/
│ │ │ └── [id].js → #/users/:id
│ │ └── blog/
│ │ ├── index.js → #/blog
│ │ └── [slug].js → #/blog/:slug
│ ├── App.js (Main Layout)
│ └── main.js (Entry Point)
├── vite.config.js
└── package.json</code></pre>
</div>
---
## 2. Setup & Configuration
Add the plugin to your `vite.config.js`. It works out of the box with zero configuration.
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import { sigproRouter } from 'sigpro/vite';
export default defineConfig({
plugins: [sigproRouter()]
});
```
---
## 3. Implementation
Thanks to **SigPro's synchronous initialization**, you no longer need to wrap your mounting logic in `.then()` blocks.
<div class="tabs tabs-box w-full mt-8 mb-12 bg-base-200/50 p-2 border border-base-300">
<input type="radio" name="route_impl" class="tab" aria-label="Option A: Direct in main.js" checked />
<div class="tab-content bg-base-100 border-base-300 rounded-lg p-6 mt-2">
```javascript
// src/main.js
import { mount, router } from 'sigpro';
import { routes } from 'virtual:sigpro-routes';
// The Core already has Router ready
mount(router(routes), '#app');
```
</div>
<input type="radio" name="route_impl" class="tab" aria-label="Option B: Inside App.js (Persistent Layout)" />
<div class="tab-content bg-base-100 border-base-300 rounded-lg p-6 mt-2">
```javascript
// src/App.js
import { routes } from 'virtual:sigpro-routes';
export default () => div({ class: 'layout' }, [
header([
h1("SigPro App"),
nav([
button({ onclick: () => Router.go('/') }, "Home"),
button({ onclick: () => Router.go('/blog') }, "Blog")
])
]),
// Only the content inside <main> will be swapped reactively
main(Router(routes))
]);
```
</div>
</div>
---
## 4. Route Mapping Reference
The plugin follows a simple convention to transform your file system into a routing map.
<div class="overflow-x-auto my-8">
<table class="table table-zebra w-full">
<thead class="bg-base-200">
<tr>
<th>File Path</th>
<th>Generated Path</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>index.js</code></td>
<td class="font-mono text-primary font-bold">/</td>
<td>The application root.</td>
</tr>
<tr>
<td><code>about.js</code></td>
<td class="font-mono text-primary font-bold">/about</td>
<td>A static page.</td>
</tr>
<tr>
<td><code>[id].js</code></td>
<td class="font-mono text-primary font-bold">/:id</td>
<td>Dynamic parameter (passed to the component).</td>
</tr>
<tr>
<td><code>blog/index.js</code></td>
<td class="font-mono text-primary font-bold">/blog</td>
<td>Folder index page.</td>
</tr>
<tr>
<td><code>_utils.js</code></td>
<td class="italic opacity-50 text-error">Ignored</td>
<td>Files starting with <code>_</code> are excluded from routing.</td>
</tr>
</tbody>
</table>
</div>
---
## 5. How it Works (Vite Virtual Module)
The plugin generates a virtual module named `virtual:sigpro-routes`. This module exports an array of objects compatible with `Router()`:
```javascript
// Internal representation generated by the plugin
export const routes = [
{ path: '/', component: () => import('/src/pages/index.js') },
{ path: '/users/:id', component: () => import('/src/pages/users/[id].js') },
// ...
];
```
Because it uses dynamic `import()`, Vite automatically performs **Code Splitting**, meaning each page is its own small JS file that only loads when the user navigates to it.

View File

@@ -19,8 +19,8 @@
"script": "./dist/sigpro.min.js",
"types": "./sigpro.d.ts"
},
"./vite": {
"import": "./dist/vite.js"
"./router": {
"import": "./dist/router.js"
}
},
"files": [
@@ -46,8 +46,9 @@
"build:iife:min": "bun build ./src/build_umd.js --bundle --outfile=./dist/sigpro.min.js --format=iife --global-name=sp --minify",
"build:esm": "bun build ./src/sigpro.js --bundle --outfile=./dist/sigpro.esm.js --format=esm",
"build:esm:min": "bun build ./src/sigpro.js --bundle --outfile=./dist/sigpro.esm.min.js --format=esm --minify",
"build:copy": "cp ./src/vite.js ./dist/vite.js && cp ./dist/sigpro.js ./docs/sigpro.js",
"build": "bun run build:iife && bun run build:iife:min && bun run build:esm && bun run build:esm:min && bun run build:copy",
"build:router": "bun build ./src/router.js --bundle --outfile=./dist/router.js --format=esm --external:fs --external:path",
"build:copy": "cp ./dist/sigpro.js ./docs/sigpro.js",
"build": "bun run build:iife && bun run build:iife:min && bun run build:esm && bun run build:esm:min && bun run build:router && bun run build:copy",
"docs": "bun x serve docs"
},
"keywords": [

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