Lila JS

El micro-framework JavaScript reactivo más eficiente

Desarrolla aplicaciones web modernas con un enfoque minimalista: toda la potencia de un framework en solo 9kb

Ventajas clave de Lila JS como micro-framework

🚀 Núcleo Ultracompacto

Todo el sistema reactivo, enrutamiento y componentes en menos de 9kb. Sin bloatware ni dependencias ocultas.

💡 Reactividad Precisa

Sistema de actualización granular que solo modifica lo necesario en el DOM usando Proxy nativo.

🧩 Componentes Autónomos

Cada componente maneja su propio estado reactivo, ciclo de vida y template scoped.

🔄 Binding Declarativo

Vincula datos con atributos HTML simples (data-bind, data-model) sin necesidad de JSX.

🧭 Router de Microcódigo

Sistema de navegación SPA con menos de 200 bytes de código central. Simple pero potente.

Boot Instantáneo

Ejecución inmediata en navegador sin pasos de build. Perfecto para prototipado rápido.

🔌 API con Mentalidad UNIX

Principio de "haz una cosa y hazla bien". Métodos concisos y predecibles.

📱 Evolución Progresiva

Comienza con un script tradicional y escala a PWA completa según tus necesidades.

🛠️ Interoperabilidad Total

Usa solo las partes que necesites e intégralo con cualquier stack existente.

¿Micro-framework vs Librería vs Framework Completo?

Librerías Puras

  • ✔️ Solo resuelven problemas específicos
  • ❌ No proveen arquitectura
  • Ejemplo: Lodash, Axios

Lila JS (Micro-Framework)

  • ✔️ Arquitectura definida pero flexible
  • ✔️ Funcionalidades clave sin sobre-ingeniería
  • Ventaja: Balance perfecto

Frameworks Completos

  • ✔️ Soluciones todo-en-uno
  • ❌ Complejidad y overhead
  • Ejemplo: Angular, Ember

Empezando

1. Instalación

Simplemente incluye el script en tu archivo HTML:

index.html

<script src="lila.js"></script>


<script src="https://seip25.github.io/Lila_js/lila.js"></script>
                        

2. Estructura Básica

Aquí tienes una configuración mínima para una aplicación Lila JS:

index.html

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

Ejemplos Prácticos

Componente Contador

Un contador simple con funcionalidad de incrementar, decrementar y reiniciar, más auto-incremento cada segundo.

Plantilla

counter-template.html

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

Definición del Componente

counter-component.js

    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');
                        

Características

  • Estado Reactivo: El conteo se actualiza automáticamente en la UI cuando cambia
  • Métodos de Ciclo de Vida: onMount inicia el auto-incremento, onDestroy limpia
  • Acciones: Tres acciones activables por el usuario (incrementar, decrementar, reiniciar)
  • Enrutamiento: Accesible en la ruta /counter

Componente Acerca de

Un componente de página estática simple.

Plantilla

about-template.html

    <template data-template="about-template">
        <h1>Acerca de</h1>
        <p>Esta es la página acerca de nosotros.</p>
    </template>
                        

Definición del Componente

about-component.js

    App.createComponent('About', {
        template: 'about-template'
    });
    
    App.addRoute('/about', 'About');
                        

Componente No Encontrado (404)

Un componente de respaldo para rutas no coincidentes con un botón para volver al inicio.

Plantilla

not-found-template.html

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

Definición del Componente

not-found-component.js

    App.createComponent('NotFound', {
        template: 'not-found-template',
        actions: {
            goHome: () => {
                App.navigateTo('#/');
            }
        }
    });
    
    App.addRoute('*', 'NotFound');
                        

Características

  • Ruta Comodín: Coincide con cualquier ruta no definida (*)
  • Acción de Navegación: La acción goHome redirige a la raíz
  • UI Simple: Mensaje claro de 404 con llamado a la acción

Ejemplos Avanzados

Componente Home con Formulario y Componentes Anidados

Una página de inicio completa con manejo de formularios, enlace de datos y componentes anidados.

Plantilla

home-template.html

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

Definición del Componente

home-component.js

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');
                        

Características Clave

Manejo de Formularios

El atributo data-action="submit" enlaza el envío del formulario con la acción submit del componente.

Enlace de Datos Bidireccional

data-model="greet" crea un enlace bidireccional entre el input y la propiedad del estado.

Enlace de Datos Unidireccional

data-bind="greet" muestra el valor del estado y se actualiza cuando cambia.

Componentes Anidados

data-component="Counter" incrusta el componente Counter dentro del componente Home.

Manejo de Acciones

data-action="changeGreeting" enlaza el clic del botón con la acción del componente.

Procesamiento de Formularios

La acción submit usa FormData para procesar fácilmente los inputs del formulario.

Estructura Completa de la Aplicación

Este ejemplo muestra cómo todos los componentes trabajan juntos en una aplicación completa.

Estructura Principal

index.html

<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í -->
                        

Configuración de Rutas

app.js

// 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();
                        

Patrones Clave

  • Componentes Modulares: Cada característica está encapsulada en su propio componente
  • Enrutamiento Centralizado: Todas las rutas se definen en un lugar para fácil mantenimiento
  • Manejo de Errores: El componente NotFound captura todas las rutas no coincidentes
  • Estructura Consistente: Todos los componentes siguen el mismo patrón para predictibilidad

Renderizado Condicional con data-if

Muestra u oculta elementos basado en condiciones del estado de tu componente.

Ejemplo de Plantilla

about-template.html

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

Definición del Componente

about-component.js

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'];
    }
});
                        

Características

  • Visualización Condicional: Los elementos se muestran/ocultan basados en expresiones JavaScript
  • Reactivo: Se actualiza automáticamente cuando cambia el estado
  • Condiciones Flexibles: Soporta cualquier expresión JavaScript válida
  • Basado en CSS: Usa display: none para ocultar elementos (mantiene la posición en el DOM)

Referencia de Directivas

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>

Mejores Prácticas

Mantén las condiciones simples

Usa expresiones booleanas simples en data-if para mejor rendimiento.

Recomendado: data-if="isActive"

Evitar: data-if="user.roles.includes('admin') && items.length > 5"

Combina con data-bind

Usa data-bind dentro de bloques condicionales para mostrar contenido dinámico.


            <div data-if="hasItems">
                <span data-bind="itemCount"></span> items
            </div>
                        

Secciones mutuamente excluyentes

Para alternar entre vistas, usa condiciones complementarias.


            <div data-if="isEditing">Modo Edición</div>
            <div data-if="!isEditing">Modo Visualización</div>
                        

Renderizado de Listas con data-repeat

Renderiza listas dinámicas desde el estado de tu componente con actualizaciones automáticas.

Ejemplo de Plantilla

about-template.html

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

Definición del Componente

about-component.js

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

Características

  • Renderizado Automático: Crea elementos DOM por cada item en tu array
  • Actualizaciones Reactivas: Se actualiza automáticamente cuando el array cambia
  • Vinculación de Items: Accede al item actual con item y al índice con index
  • Acciones con Contexto: Vincula acciones a cada item con su contexto
  • Contenido Flexible: Soporta cualquier estructura HTML dentro del elemento repetido

Referencia de Directivas

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>

Mejores Prácticas

Usa Claves Únicas

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

Combina con Renderizado Condicional

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>
                        

Acceso Seguro a Propiedades

Verifica la existencia de propiedades al vincular para evitar errores.


            <span data-repeat-bind="item.name || 'Sin nombre'"></span>
                        

Variables Especiales

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

Contexto de data-repeat-action

Cuando usas data-repeat-action en items repetidos, tu manejador de acción recibe propiedades adicionales de contexto:

repeat-action-context.js

// 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í
    }
}
                        

Referencia de Propiedades de Contexto

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)

Patrones Comunes

Eliminar Item por ID

removeItem: ({ state, item }) => {
    state.items = state.items.filter(i => i.id !== item.id);
}
                        
Actualizar Item Específico

updateItem: ({ state, item, index }) => {
    state.items[index] = { ...item, updated: true };
    state.items = [...state.items]; // Activar reactividad
}
                        
Manejo de Eventos con Contexto

handleClick: ({ event, item }) => {
    event.stopPropagation();
    console.log('Item clickeado:', item.name);
}
                        

Notas Importantes

  • Siempre usa event.preventDefault() para envíos de formularios o clicks en enlaces
  • Para mutaciones de array, siempre crea nuevas referencias para activar la reactividad:
    
    // Bien - activa actualización
    state.items = state.items.filter(...);
    
    // Mal - no activa actualización reactiva
    state.items.splice(index, 1);
                                
  • La referencia item es el mismo objeto que en el array (no es una copia profunda)

Referencia de API

Métodos Principales

Referencia de API

// 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 
                        

Directivas de Plantilla

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>