npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@connextjs/runtime

v0.0.1

Published

> Runtime y sistema de componentes para ConnextJS

Readme

@connext/runtime

Runtime y sistema de componentes para ConnextJS

📦 Instalación

npm install @connext/runtime
# o
pnpm add @connext/runtime
# o
yarn add @connext/runtime

🚀 Uso Básico

Inicialización de Componente

import { ConnextComponent } from '@connext/runtime';
import { render } from './App.cnx';

// Crear instancia del componente
const app = new ConnextComponent({
  target: document.getElementById('app'),
  render: render
});

Router

import { ConnextRouter } from '@connext/runtime';
import { render as Home } from './pages/Home.cnx';
import { render as About } from './pages/About.cnx';

const router = new ConnextRouter();

// Definir rutas
router.route('/', () => {
  const component = new ConnextComponent({
    target: document.getElementById('app'),
    render: Home
  });
});

router.route('/about', () => {
  const component = new ConnextComponent({
    target: document.getElementById('app'),
    render: About
  });
});

// Iniciar router
router.start();

🏗️ Arquitectura

ConnextComponent

La clase principal para manejar componentes:

class ConnextComponent {
  constructor(options: ComponentOptions)
  destroy(): void
  update(props?: any): void
}

interface ComponentOptions {
  target: HTMLElement;     // Elemento DOM donde renderizar
  render: RenderFunction;  // Función de renderizado del componente
  props?: any;            // Props iniciales
}

ConnextRouter

Sistema de enrutamiento SPA:

class ConnextRouter {
  route(path: string, handler: RouteHandler): void
  navigate(path: string): void
  start(): void
  stop(): void
}

type RouteHandler = (params?: RouteParams) => void;

🔄 Sistema Reactivo

Variables Reactivas

Las variables en ConnextJS son automáticamente reactivas:

// En el componente .cnx
<script>
  let count = 0;        // Variable reactiva
  let name = 'World';   // Variable reactiva
  
  // Cuando cambian, la UI se actualiza automáticamente
  function increment() {
    count++; // Trigger re-render
  }
</script>

Computed Values

<script>
  let firstName = 'John';
  let lastName = 'Doe';
  
  // Computed value usando $:
  $: fullName = `${firstName} ${lastName}`;
  
  // Computed con lógica compleja
  $: greeting = {
    if (fullName.length > 10) {
      return `Hello, ${firstName}!`;
    }
    return `Hello, ${fullName}!`;
  };
</script>

Watchers

<script>
  let count = 0;
  
  // Watcher que se ejecuta cuando count cambia
  $: {
    if (count > 10) {
      console.log('Count is getting high!');
    }
  }
  
  // Watcher con dependencias específicas
  $: console.log(`Count changed to: ${count}`);
</script>

🎯 Event Handling

Event Listeners

<template>
  <!-- Click events -->
  <button on:click="{handleClick}">Click me</button>
  <button on:click="{() => count++}">Increment</button>
  
  <!-- Input events -->
  <input on:input="{handleInput}" value="{text}">
  <input on:change="{handleChange}">
  
  <!-- Form events -->
  <form on:submit="{handleSubmit}">
    <input type="text" required>
    <button type="submit">Submit</button>
  </form>
  
  <!-- Keyboard events -->
  <input on:keydown="{handleKeyDown}">
  <input on:keyup="{handleKeyUp}">
  
  <!-- Mouse events -->
  <div on:mouseenter="{handleMouseEnter}">
    <div on:mouseleave="{handleMouseLeave}">
      Hover me
    </div>
  </div>
</template>

<script>
  let text = '';
  
  function handleClick(event) {
    console.log('Button clicked!', event);
  }
  
  function handleInput(event) {
    text = event.target.value;
  }
  
  function handleSubmit(event) {
    event.preventDefault();
    console.log('Form submitted!');
  }
  
  function handleKeyDown(event) {
    if (event.key === 'Enter') {
      console.log('Enter pressed!');
    }
  }
</script>

Event Modifiers

<template>
  <!-- Prevent default -->
  <form on:submit|preventDefault="{handleSubmit}">
    <!-- contenido -->
  </form>
  
  <!-- Stop propagation -->
  <div on:click="{handleOuterClick}">
    <button on:click|stopPropagation="{handleInnerClick}">
      Click me
    </button>
  </div>
  
  <!-- Once -->
  <button on:click|once="{handleOnce}">Click once</button>
  
  <!-- Passive -->
  <div on:scroll|passive="{handleScroll}">Scrollable content</div>
</template>

🎨 Lifecycle

Component Lifecycle

<script>
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from '@connext/runtime';
  
  // Se ejecuta después de que el componente se monta
  onMount(() => {
    console.log('Component mounted');
    
    // Cleanup function
    return () => {
      console.log('Cleanup on unmount');
    };
  });
  
  // Se ejecuta antes de que el componente se destruya
  onDestroy(() => {
    console.log('Component will be destroyed');
  });
  
  // Se ejecuta antes de cada actualización
  beforeUpdate(() => {
    console.log('Component will update');
  });
  
  // Se ejecuta después de cada actualización
  afterUpdate(() => {
    console.log('Component updated');
  });
</script>

🔗 Component Communication

Props

// Parent.cnx
<template>
  <ChildComponent name="{userName}" age="{userAge}" />
</template>

<script>
  import ChildComponent from './ChildComponent.cnx';
  
  let userName = 'John';
  let userAge = 25;
</script>
// ChildComponent.cnx
<template>
  <div>
    <h2>Hello {name}!</h2>
    <p>You are {age} years old.</p>
  </div>
</template>

<script>
  // Props se declaran como variables
  export let name;
  export let age;
  
  // Props con valores por defecto
  export let theme = 'light';
  export let showAge = true;
</script>

Events (Custom Events)

// Child.cnx
<template>
  <button on:click="{sendMessage}">Send Message</button>
</template>

<script>
  import { createEventDispatcher } from '@connext/runtime';
  
  const dispatch = createEventDispatcher();
  
  function sendMessage() {
    dispatch('message', {
      text: 'Hello from child!',
      timestamp: Date.now()
    });
  }
</script>
// Parent.cnx
<template>
  <Child on:message="{handleMessage}" />
</template>

<script>
  import Child from './Child.cnx';
  
  function handleMessage(event) {
    console.log('Received:', event.detail);
  }
</script>

Stores (Global State)

// stores.ts
import { writable, readable, derived } from '@connext/runtime';

// Writable store
export const count = writable(0);

// Readable store
export const time = readable(new Date(), (set) => {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);
  
  return () => clearInterval(interval);
});

// Derived store
export const doubled = derived(count, $count => $count * 2);

// Custom store
function createCounter() {
  const { subscribe, set, update } = writable(0);
  
  return {
    subscribe,
    increment: () => update(n => n + 1),
    decrement: () => update(n => n - 1),
    reset: () => set(0)
  };
}

export const counter = createCounter();
// Component.cnx
<template>
  <div>
    <p>Count: {$count}</p>
    <p>Doubled: {$doubled}</p>
    <p>Time: {$time}</p>
    
    <button on:click="{counter.increment}">+</button>
    <button on:click="{counter.decrement}">-</button>
    <button on:click="{counter.reset}">Reset</button>
  </div>
</template>

<script>
  import { count, doubled, time, counter } from './stores.js';
  
  // Los stores se auto-suscriben con el prefijo $
</script>

🎭 Animations

Transitions

<template>
  {#if visible}
    <div transition:fade="{{duration: 300}}">
      Fade in/out
    </div>
  {/if}
  
  {#if showSlide}
    <div transition:slide="{{duration: 500, easing: 'ease-out'}}">
      Slide in/out
    </div>
  {/if}
</template>

<script>
  import { fade, slide } from '@connext/runtime/transitions';
  
  let visible = false;
  let showSlide = false;
</script>

Custom Transitions

// transitions.ts
import { TransitionConfig } from '@connext/runtime';

export function customFade(node: Element, params: any): TransitionConfig {
  return {
    duration: params.duration || 300,
    css: (t: number) => `opacity: ${t}`
  };
}

export function bounce(node: Element, params: any): TransitionConfig {
  return {
    duration: params.duration || 600,
    css: (t: number) => {
      const eased = elasticOut(t);
      return `transform: scale(${eased})`;
    }
  };
}

function elasticOut(t: number): number {
  return Math.sin(-13 * (t + 1) * Math.PI / 2) * Math.pow(2, -10 * t) + 1;
}

🔧 API Reference

ConnextComponent

class ConnextComponent {
  constructor(options: ComponentOptions)
  
  // Métodos públicos
  destroy(): void
  update(props?: Record<string, any>): void
  
  // Propiedades
  readonly target: HTMLElement
  readonly destroyed: boolean
}

interface ComponentOptions {
  target: HTMLElement;
  render: RenderFunction;
  props?: Record<string, any>;
  hydrate?: boolean;
}

type RenderFunction = (target: HTMLElement) => void;

ConnextRouter

class ConnextRouter {
  constructor(options?: RouterOptions)
  
  // Métodos de enrutamiento
  route(path: string, handler: RouteHandler): void
  navigate(path: string, options?: NavigateOptions): void
  back(): void
  forward(): void
  
  // Control del router
  start(): void
  stop(): void
  
  // Estado
  readonly currentPath: string
  readonly isActive: boolean
}

interface RouterOptions {
  base?: string;
  hash?: boolean;
  fallback?: string;
}

interface NavigateOptions {
  replace?: boolean;
  state?: any;
}

type RouteHandler = (params: RouteParams) => void;
type RouteParams = Record<string, string>;

Stores

// Writable Store
interface Writable<T> {
  subscribe(subscriber: Subscriber<T>): Unsubscriber;
  set(value: T): void;
  update(updater: Updater<T>): void;
}

// Readable Store
interface Readable<T> {
  subscribe(subscriber: Subscriber<T>): Unsubscriber;
}

// Derived Store
function derived<T, S>(
  stores: Readable<T> | Readable<T>[],
  fn: (values: T) => S,
  initial?: S
): Readable<S>;

type Subscriber<T> = (value: T) => void;
type Unsubscriber = () => void;
type Updater<T> = (value: T) => T;

Lifecycle Functions

function onMount(fn: () => void | (() => void)): void;
function onDestroy(fn: () => void): void;
function beforeUpdate(fn: () => void): void;
function afterUpdate(fn: () => void): void;

// Context
function setContext<T>(key: any, context: T): void;
function getContext<T>(key: any): T;
function hasContext(key: any): boolean;

🎯 Ejemplos Avanzados

Todo App

// TodoApp.cnx
<template>
  <div class="todo-app">
    <h1>Todo List</h1>
    
    <form on:submit|preventDefault="{addTodo}">
      <input 
        bind:value="{newTodo}" 
        placeholder="Add a todo..."
        required
      >
      <button type="submit">Add</button>
    </form>
    
    <ul class="todo-list">
      {#each todos as todo (todo.id)}
        <li class="todo-item {todo.completed ? 'completed' : ''}">
          <input 
            type="checkbox" 
            bind:checked="{todo.completed}"
          >
          <span class="todo-text">{todo.text}</span>
          <button on:click="{() => removeTodo(todo.id)}">×</button>
        </li>
      {/each}
    </ul>
    
    <div class="todo-stats">
      <span>{remainingCount} remaining</span>
      <button on:click="{clearCompleted}">Clear completed</button>
    </div>
  </div>
</template>

<script>
  let newTodo = '';
  let todos = [];
  let nextId = 1;
  
  $: remainingCount = todos.filter(t => !t.completed).length;
  
  function addTodo() {
    if (newTodo.trim()) {
      todos = [...todos, {
        id: nextId++,
        text: newTodo.trim(),
        completed: false
      }];
      newTodo = '';
    }
  }
  
  function removeTodo(id) {
    todos = todos.filter(t => t.id !== id);
  }
  
  function clearCompleted() {
    todos = todos.filter(t => !t.completed);
  }
</script>

<style>
  .todo-app {
    max-width: 500px;
    margin: 0 auto;
    padding: 2rem;
  }
  
  .todo-list {
    list-style: none;
    padding: 0;
  }
  
  .todo-item {
    display: flex;
    align-items: center;
    padding: 0.5rem;
    border-bottom: 1px solid #eee;
  }
  
  .todo-item.completed .todo-text {
    text-decoration: line-through;
    opacity: 0.6;
  }
  
  .todo-stats {
    display: flex;
    justify-content: space-between;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid #eee;
  }
</style>

🔗 Enlaces

📄 Licencia

MIT - ver LICENSE para más detalles.