This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { $, watch, batch, h, Fragment, mount, when, each, onUnmount, isArr, isFunc, isObj } from "./sigpro.js"
|
||||
import { router } from "./router.js"
|
||||
import { db } from "./utils.js"
|
||||
import { $, watch, batch, h, Fragment, mount, when, each, isArr, isFunc, isObj } from "./sigpro.js"
|
||||
import { router, addLang, t } from "./sigpro.plus.js"
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
Object.assign(window, { $, watch, h, Fragment, when, each, router, mount, batch, onUnmount, isArr, isFunc, isObj, db })
|
||||
Object.assign(window, { $, watch, h, Fragment, when, each, router, addLang, t, mount, batch, isArr, isFunc, isObj })
|
||||
}
|
||||
101
src/router.js
101
src/router.js
@@ -1,101 +0,0 @@
|
||||
// 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 };
|
||||
89
src/sigpro.plus.js
Normal file
89
src/sigpro.plus.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// plus
|
||||
import { h, watch, $, render, isFunc } from './sigpro';
|
||||
|
||||
// router
|
||||
export const router = routes => {
|
||||
const getHash = () => window.location.hash.slice(1) || "/";
|
||||
const path = $(getHash());
|
||||
const handler = () => path(getHash());
|
||||
window.addEventListener("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);
|
||||
}
|
||||
});
|
||||
|
||||
hook.destroy = () => {
|
||||
window.removeEventListener("hashchange", handler);
|
||||
currentView?.destroy();
|
||||
};
|
||||
|
||||
return hook;
|
||||
};
|
||||
|
||||
router.params = $({});
|
||||
router.to = p => window.location.hash = p.replace(/^#?\/?/, "#/");
|
||||
router.back = () => window.history.back();
|
||||
router.path = () => window.location.hash.replace(/^#/, "") || "/";
|
||||
|
||||
// db
|
||||
export const db = async (url, data = {}, loading = null) => {
|
||||
if (loading) loading(true);
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Error ${res.status}: ${errorText}`);
|
||||
}
|
||||
return await res.json();
|
||||
} finally {
|
||||
if (loading) loading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const currentLocale = $('en');
|
||||
|
||||
const translations = {};
|
||||
|
||||
// addLang
|
||||
export const addLang = (obj) => {
|
||||
for (const locale of Object.keys(obj)) {
|
||||
if (!translations[locale]) translations[locale] = {};
|
||||
Object.assign(translations[locale], obj[locale]);
|
||||
}
|
||||
};
|
||||
|
||||
// setLocale
|
||||
export const setLocale = (locale) => {
|
||||
if (locale && translations[locale]) {
|
||||
currentLocale(locale);
|
||||
}
|
||||
};
|
||||
|
||||
// t
|
||||
export const t = (key) => {
|
||||
return () => translations[currentLocale()]?.[key] ?? key;
|
||||
};
|
||||
@@ -1,15 +1,8 @@
|
||||
/**
|
||||
* SigPro Vite Plugin - File-based Routing
|
||||
* @module sigpro/vite
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export default function sigproRouter() {
|
||||
// sigproRouter for Vite
|
||||
export 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 })
|
||||
@@ -17,14 +10,12 @@ export default function sigproRouter() {
|
||||
.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, '*')
|
||||
@@ -34,18 +25,13 @@ export default function sigproRouter() {
|
||||
|
||||
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);
|
||||
@@ -55,23 +41,15 @@ export default function sigproRouter() {
|
||||
});
|
||||
|
||||
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`;
|
||||
routeEntries += ` { path: '${urlPath}', component: () => import('/${relativeImport}') },\n`;
|
||||
});
|
||||
|
||||
// Fallback 404 si no existe una ruta comodín
|
||||
if (!routeEntries.includes("path: '*'")) {
|
||||
routeEntries += ` { path: '*', component: () => document.createTextNode('404 - Not Found') },\n`;
|
||||
routeEntries += ` { path: '*', component: () => ({ default: () => document.createTextNode('404 - Not Found') }) },\n`;
|
||||
}
|
||||
|
||||
return `export const routes = [\n${routeEntries}];`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { sigproRouter };
|
||||
24
src/utils.js
24
src/utils.js
@@ -1,24 +0,0 @@
|
||||
// utils.js
|
||||
|
||||
export const db = async (url, data = {}, loading = null) => {
|
||||
if (loading) loading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Error ${res.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
|
||||
} finally {
|
||||
if (loading) loading(false);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user