🚀 Núcleo Ultracompacto
Todo el sistema reactivo, enrutamiento y componentes en menos de 9kb. Sin bloatware ni dependencias ocultas.
Desarrolla aplicaciones web modernas con un enfoque minimalista: toda la potencia de un framework en solo 9kb
Todo el sistema reactivo, enrutamiento y componentes en menos de 9kb. Sin bloatware ni dependencias ocultas.
Sistema de actualización granular que solo modifica lo necesario en el DOM usando Proxy nativo.
Cada componente maneja su propio estado reactivo, ciclo de vida y template scoped.
Vincula datos con atributos HTML simples (data-bind
, data-model
) sin necesidad de JSX.
Sistema de navegación SPA con menos de 200 bytes de código central. Simple pero potente.
Ejecución inmediata en navegador sin pasos de build. Perfecto para prototipado rápido.
Principio de "haz una cosa y hazla bien". Métodos concisos y predecibles.
Comienza con un script tradicional y escala a PWA completa según tus necesidades.
Usa solo las partes que necesites e intégralo con cualquier stack existente.
Simplemente incluye el script en tu archivo HTML:
<script src="lila.js"></script>
<script src="https://seip25.github.io/Lila_js/lila.js"></script>
Aquí tienes una configuración mínima para una aplicación Lila JS:
<body>
<nav>
<a href="/" data-link>Inicio</a>
<a href="/about" data-link>Acerca de</a>
</nav>
<main id="app-lila"></main>
<template data-template="home-template">
<h1>Bienvenido</h1>
<p data-bind="message"></p>
</template>
<template data-template="about-template">
<h1>Acerca de</h1>
<p>Esta es la página acerca de nosotros.</p>
</template>
<script>
App.createComponent('Home', {
template: 'home-template',
state: () => ({ message: '¡Hola Mundo!' })
});
App.addRoute('/', 'Home');
App.createComponent('About', {
template: 'about-template'
});
App.addRoute('/about', 'About');
handleRouting();
</script>
</body>
Un contador simple con funcionalidad de incrementar, decrementar y reiniciar, más auto-incremento cada segundo.
<template data-template="counter-template">
<h1>Contador</h1>
<p>Conteo actual: <span data-bind="count"></span></p>
<button data-action="increment">Incrementar</button>
<button data-action="decrement">Decrementar</button>
<button data-action="reset">Reiniciar</button>
</template>
const incrementCounter = ({ state }) => { state.count++ };
App.createComponent('Counter', {
template: 'counter-template',
state: () => ({
count: 0,
interval: null
}),
actions: {
increment: incrementCounter,
decrement: ({ state }) => { state.count-- },
reset: ({ state }) => { state.count = 0 }
},
onMount: (state) => {
console.log('Contador montado');
state.interval = setInterval(() => {
console.log('Intervalo tick', state.count);
state.count++;
}, 1000);
},
onDestroy: (state) => {
console.log('Contador destruido');
if (state.interval) {
clearInterval(state.interval);
state.interval = null;
}
}
});
App.addRoute('/counter', 'Counter');
onMount
inicia el auto-incremento,
onDestroy
limpia/counter
Un componente de página estática simple.
<template data-template="about-template">
<h1>Acerca de</h1>
<p>Esta es la página acerca de nosotros.</p>
</template>
App.createComponent('About', {
template: 'about-template'
});
App.addRoute('/about', 'About');
Un componente de respaldo para rutas no coincidentes con un botón para volver al inicio.
<template data-template="not-found-template">
<h1>404 No Encontrado</h1>
<p>La página que buscas no existe.</p>
<button data-action="goHome">Ir al Inicio</button>
</template>
App.createComponent('NotFound', {
template: 'not-found-template',
actions: {
goHome: () => {
App.navigateTo('#/');
}
}
});
App.addRoute('*', 'NotFound');
*
)goHome
redirige a la raízUna página de inicio completa con manejo de formularios, enlace de datos y componentes anidados.
<template data-template="home-template">
<h1>¡Bienvenido a la página de inicio!</h1>
<form data-action="submit">
<input type="text" name="greet" data-bind="greet" placeholder="Ingresa tu saludo" class="w-full" />
<button type="submit" class="contrast">
<i class="icon-send"></i>Enviar
</button>
</form>
<br />
<div>
<p>Tu saludo es: <span data-bind="greet"></span></p>
</div>
<div class="input-icon">
<i class="icon-edit"></i>
<input type="text" data-model="greet" placeholder="Cambiar saludo" value="">
</div>
<button data-action="changeGreeting" class="w-full">
<i class="icon-check-circle"></i>Cambiar Saludo
</button>
<br />
<div data-component="Counter"></div>
</template>
App.createComponent('Home', {
template: 'home-template',
state: () => ({
greet: null
}),
actions: {
changeGreeting: ({ state }) => {
state.greet = state.greet === "Hello World!"
? "Hola Mundo!"
: "Hello World!";
console.log(state.greet);
},
submit: ({ state, event }) => {
event.preventDefault();
const formData = Object.fromEntries(new FormData(event.target));
console.log('Formulario enviado:', formData);
alert(JSON.stringify(formData));
}
}
});
App.addRoute('/', 'Home');
El atributo data-action="submit"
enlaza el envío del formulario con la acción
submit del componente.
data-model="greet"
crea un enlace bidireccional entre el input y la propiedad
del estado.
data-bind="greet"
muestra el valor del estado y se actualiza cuando cambia.
data-component="Counter"
incrusta el componente Counter dentro del componente
Home.
data-action="changeGreeting"
enlaza el clic del botón con la acción del
componente.
La acción submit usa FormData
para procesar fácilmente los inputs del
formulario.
Este ejemplo muestra cómo todos los componentes trabajan juntos en una aplicación completa.
<header class="container">
<nav>
<a href="/" data-link>Inicio</a>
<a href="/about" data-link>Acerca de</a>
<a href="/counter" data-link>Contador</a>
</nav>
</header>
<main id="app-lila" class="container"></main>
<footer></footer>
<!-- Todas las plantillas se incluirían aquí -->
// Definir todos los componentes primero
App.createComponent('Home', { /* ... */ });
App.createComponent('About', { /* ... */ });
App.createComponent('Counter', { /* ... */ });
App.createComponent('NotFound', { /* ... */ });
// Configurar rutas
App.addRoute('/', 'Home');
App.addRoute('/about', 'About');
App.addRoute('/counter', 'Counter');
App.addRoute('*', 'NotFound'); // Ruta comodín
// Inicializar el enrutamiento
handleRouting();
Muestra u oculta elementos basado en condiciones del estado de tu componente.
<template data-template="about-template">
<h1>Acerca de</h1>
<div data-if="items.length === 0">
<p>No hay items.</p>
</div>
<div data-if="items.length > 3">
<p>Hay más de 3 items.</p>
</div>
<div data-if="items.length > 0">
<span data-bind="items"></span>
</div>
</template>
App.createComponent('About', {
template: 'about-template',
state: () => ({
items: []
}),
actions: {
addItem: ({ state }) => {
state.items = [...state.items, `Item ${state.items.length + 1}`];
},
removeItem: ({ state }) => {
state.items.pop();
}
},
onMount: (state) => {
state.items = ['Item 1', 'Item 2', 'Item 3'];
}
});
display: none
para ocultar elementos (mantiene la posición en el DOM)Directiva | Descripción | Ejemplo |
---|---|---|
data-if |
Muestra/oculta elementos condicionalmente basado en una expresión | <div data-if="items.length > 0">...</div> |
data-bind |
Vincula el contenido del elemento a una propiedad del estado | <span data-bind="count"></span> |
data-action |
Vincula eventos del DOM a acciones del componente | <button data-action="increment"></button> |
data-link |
Navegación mediante enrutador | <a href="/about" data-link></a> |
Usa expresiones booleanas simples en data-if
para mejor rendimiento.
Recomendado: data-if="isActive"
Evitar: data-if="user.roles.includes('admin') && items.length > 5"
Usa data-bind
dentro de bloques condicionales para mostrar contenido dinámico.
<div data-if="hasItems">
<span data-bind="itemCount"></span> items
</div>
Para alternar entre vistas, usa condiciones complementarias.
<div data-if="isEditing">Modo Edición</div>
<div data-if="!isEditing">Modo Visualización</div>
Renderiza listas dinámicas desde el estado de tu componente con actualizaciones automáticas.
<template data-template="about-template">
<h1>Acerca de</h1>
<div class="mt-4" data-if="items.length === 0">
<p>No hay items.</p>
</div>
<div data-repeat="items">
<article class="mt-4 flex between gap-2" data-repeated-item="" secondary>
<span class="flex between gap-1">
<span>Índice:</span>
<span data-repeat-bind="index"></span>
</span>
<span data-repeat-bind="item.id"></span>
<span data-repeat-bind="item.name"></span>
<button data-repeat-action="removeItemId" class="btn-error">
<i class="icon-delete"></i>
<span data-repeat-bind="'Eliminar ' + item.name"></span>
</button>
</article>
</div>
</template>
App.createComponent('About', {
template: 'about-template',
state: () => ({
items: []
}),
actions: {
addItem: ({ state }) => {
let item = {
name: 'Item ' + (state.items.length + 1),
id: state.items.length + 1
};
state.items = [...state.items, item];
},
removeItem: ({ state }, index) => {
let items = state.items;
items.splice(index, 1);
state.items = [...items];
},
removeItemId: ({ state, event, item, index }) => {
event.preventDefault();
state.items = state.items.filter((i) => i.id !== item.id);
}
},
onMount: (state) => {
state.items = [
{ name: 'Item 1', id: 1 },
{ name: 'Item 2', id: 2 },
{ name: 'Item 3', id: 3 }
];
}
});
item
y al índice con index
Directiva | Descripción | Ejemplo |
---|---|---|
data-repeat |
Repite el elemento por cada item en el array | <div data-repeat="items">...</div> |
data-repeat-bind |
Vincula contenido a propiedades del item actual | <span data-repeat-bind="item.name"></span> |
data-repeat-action |
Vincula una acción con contexto del item | <button data-repeat-action="removeItem"></button> |
data-repeated-item |
Atributo marcador para items repetidos (se añade automáticamente) | <div data-repeated-item></div> |
Incluye identificadores únicos en tus items para mejor rendimiento con listas dinámicas.
state.items = [
{ id: 1, name: 'Item 1' }, // Bien - tiene ID único
{ id: 2, name: 'Item 2' }
];
Usa data-if
para mostrar mensajes cuando la lista está vacía.
<div data-if="items.length === 0">
<p>No se encontraron items</p>
</div>
<div data-repeat="items">...</div>
Verifica la existencia de propiedades al vincular para evitar errores.
<span data-repeat-bind="item.name || 'Sin nombre'"></span>
Variable | Disponible En | Descripción |
---|---|---|
item |
data-repeat-bind , data-repeat-action |
Item actual en la iteración |
index |
data-repeat-bind , data-repeat-action |
Índice actual (base 0) en la iteración |
Cuando usas data-repeat-action
en items repetidos, tu manejador de acción recibe propiedades adicionales de contexto:
// Firma de acción para items repetidos
actions: {
handleItem: ({
state, // Estado del componente
event, // Evento DOM original
item, // Objeto item actual del array
index // Índice actual en el array
}) => {
// Lógica de la acción aquí
}
}
Propiedad | Tipo | Descripción | Ejemplo de Uso |
---|---|---|---|
state |
Objeto | Objeto de estado del componente (lectura/escritura) | state.items.push(newItem) |
event |
Evento DOM | Evento original que disparó la acción | event.preventDefault() |
item |
Objeto | Item actual del array repetido | console.log(item.id) |
index |
Número | Índice actual en el array (base 0) | state.items.splice(index, 1) |
removeItem: ({ state, item }) => {
state.items = state.items.filter(i => i.id !== item.id);
}
updateItem: ({ state, item, index }) => {
state.items[index] = { ...item, updated: true };
state.items = [...state.items]; // Activar reactividad
}
handleClick: ({ event, item }) => {
event.stopPropagation();
console.log('Item clickeado:', item.name);
}
event.preventDefault()
para envíos de formularios o clicks en enlaces
// Bien - activa actualización
state.items = state.items.filter(...);
// Mal - no activa actualización reactiva
state.items.splice(index, 1);
item
es el mismo objeto que en el array (no es una copia profunda)
// Crea un objeto de estado reactivo
const state = App.reactive({
count: 0
});
// Crea un componente
App.createComponent('NombreComponente', {
template: 'id-plantilla', // ID del elemento template
state: () => ({ ... }), // Función de estado inicial
actions: { // Métodos del componente
nombreAccion: ({ state, event }, ...args) => { ... }
},
onMount: (state) => { // Se llama cuando el componente se monta
console.log('Montado');
},
onDestroy: (state) => { // Se llama cuando el componente se desmonta
console.log('Desmontado');
}
});
// Enrutamiento
App.addRoute('/ruta', 'NombreComponente');
App.navigateTo('/ruta'); // Navegación programática
handleRouting();// Enlazar las rutas
Directiva | Descripción | Ejemplo |
---|---|---|
data-bind |
Enlace de datos unidireccional | <span data-bind="mensaje"></span> |
data-model |
Enlace de datos bidireccional para inputs de formulario | <input data-model="usuario"> |
data-action |
Vincula un método del componente a eventos del DOM | <button data-action="enviar"></button> |
data-component |
Componentes anidados | <div data-component="Hijo"></div> |
data-link |
Enlace del enrutador | <a href="/about" data-link></a> |
data-if |
Condicional | <div data-if="items.length >0"></div> |