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 🙏

© 2026 – Pkg Stats / Ryan Hefner

or-fe-fwk

v4.0.4

Published

A minimal, modern JavaScript framework for building reactive UIs with a virtual DOM, component model, and built-in router. Designed for learning and experimentation.

Downloads

23

Readme

Frontend Framework

A minimal, modern JavaScript framework for building reactive UIs with a virtual DOM, component model, and built-in router. Designed for learning and experimentation.

Key Features

  • 🚀 Virtual DOM – Fast UI updates using a diffing algorithm
  • 🧩 Component Model – Encapsulated, reusable components with lifecycle events
  • 🔄 Reactive State – State-driven rendering and updates
  • 💾 State Persistence – Opt-in automatic localStorage saving/restoring of component state
  • 🎯 Event System – Simple event emission and handling between components
  • 🛣️ Routing – Hash-based navigation with guards
  • 📦 Slots – Flexible content projection for composition
  • Scheduler – Batched updates for performance
  • 🖼️ SVG Support – Native handling of SVG elements and attributes for icons and graphics
  • 🎨 Zero Dependencies – Pure JavaScript, no external libraries

Table of Contents

Getting Started

Installation (npm)

npm install or-fe-fwk

Import

import { createApp, defineComponent, h, hString, hFragment } from 'or-fe-fwk';

Example Usage

import { createApp, defineComponent, h } from 'or-fe-fwk';

const HelloWorld = defineComponent({
  state() {
    return { count: 0 };
  },
  render() {
    return h('div', {}, [
      h('h1', {}, ['Hello World!']),
      h('p', {}, [`Count: ${this.state.count}`]),
      h('button', {
        on: { click: () => this.updateState({ count: this.state.count + 1 }) }
      }, ['Increment'])
    ]);
  }
});

createApp(HelloWorld).mount(document.body);

Core Ideas

Virtual DOM

The framework represents the UI as a virtual DOM tree, enabling efficient updates:

const vnode = h('div', { class: 'container', id: 'main' }, [
  h('h1', {}, ['Title']),
  h('p', {}, ['Content'])
]);

Components

Define components with defineComponent(). Components can have local state, lifecycle hooks, and custom methods:

const TodoItem = defineComponent({
  // Component state
  state(props) {
    return {
      completed: false
    };
  },
  
  // Lifecycle hooks
  onMounted() {
    console.log('Component mounted');
  },
  
  onUnmounted() {
    console.log('Component unmounted');
  },
  
  // Custom methods
  toggleComplete() {
    this.updateState({ completed: !this.state.completed });
  },
  
  // Render function
  render() {
    const { text } = this.props;
    const { completed } = this.state;
    
    return h('li', {
      class: completed ? 'completed' : '',
      on: {
        click: () => this.toggleComplete()
      }
    }, [text]);
  }
});

State Management

Component state is local and reactive. Updating state triggers a re-render:

// Update state (triggers re-render)
this.updateState({ count: this.state.count + 1 });

// Update props (from parent component)
this.updateProps({ newProp: 'value' });

State Persistence (localStorage)

You can opt-in to automatic state persistence for any component by providing a persistKey option to defineComponent. When set, the component's state will be saved to localStorage on every update, and restored on initialization. This is useful for features like todo lists, drafts, or any state you want to survive page reloads.

Usage Example:

const App = defineComponent({
  persistKey: "todos-app", // Unique key for localStorage
  state() {
    return {
      todos: [],
      currentTodo: "",
      filter: "filter-all"
    };
  },
  // ...methods and render as usual...
});

How it works:

  • On component creation, if persistKey is set, the framework will merge any saved state from localStorage with the initial state.
  • On every updateState, the new state is saved to localStorage under the given key.
  • No manual localStorage code is needed for persisted state fields.

See the examples/Todo with framework app for a real-world usage.

Event Handling

Components can emit and listen for custom events:

const ChildComponent = defineComponent({
  render() {
    return h('button', {
      on: {
        click: () => this.emit('custom-event', { data: 'hello' })
      }
    }, ['Click me']);
  }
});

const ParentComponent = defineComponent({
  render() {
    return h(ChildComponent, {
      on: {
        'custom-event': (payload) => {
          console.log('Received:', payload.data);
        }
      }
    });
  }
});

Slots (Content Projection)

Slots allow flexible content composition:

const Card = defineComponent({
  render() {
    return h('div', { class: 'card' }, [
      h('div', { class: 'card-header' }, [
        hSlot() // Default slot for projected content
      ])
    ]);
  }
});

// Usage
h(Card, {}, [
  h('h2', {}, ['Card Title']),
  h('p', {}, ['Card content'])
]);

SVG Support

You can use SVG elements and attributes directly in your components. The framework automatically handles SVG namespaces and attributes, so you can create icons, charts, and scalable graphics just like in plain HTML:

h('svg', { width: 24, height: 24, viewBox: '0 0 24 24' }, [
  h('circle', { cx: 12, cy: 12, r: 10, fill: 'blue' }),
  h('text', { x: 12, y: 16, 'text-anchor': 'middle', fill: 'white' }, ['Hi'])
])

Routing

The framework provides a hash-based router with navigation guards:

Basic Routing

import { HashRouter, RouterOutlet, RouterLink } from './framework.js';

// Define routes
const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/users/:id', component: UserProfile },
  { path: '*', component: NotFound } // Catch-all route
];

// Create router
const router = new HashRouter(routes);

// Create app with router
const app = createApp(App, {}, { router });

Route Guards

const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: async (from, to) => {
      // Return false to prevent navigation
      if (!isAuthenticated()) {
        return false;
      }
      
      // Return string to redirect
      if (!hasAdminRole()) {
        return '/unauthorized';
      }
      
      // Return nothing/true to allow navigation
      return true;
    }
  }
];

Router Components

const App = defineComponent({
  render() {
    return h('div', {}, [
      // Navigation
      h('nav', {}, [
        h(RouterLink, { to: '/' }, ['Home']),
        h(RouterLink, { to: '/about' }, ['About'])
      ]),
      
      // Route outlet
      h(RouterOutlet)
    ]);
  }
});

Accessing Route Data

const UserProfile = defineComponent({
  render() {
    // Access route params and query
    const { id } = this.appContext.router.params;
    const { tab } = this.appContext.router.query;
    
    return h('div', {}, [
      h('h1', {}, [`User ${id}`]),
      h('p', {}, [`Current tab: ${tab || 'profile'}`])
    ]);
  }
});

Programmatic Navigation

// Navigate to a route
this.appContext.router.navigateTo('/users/123?tab=settings');

// Go back/forward
this.appContext.router.back();
this.appContext.router.forward();

Additional Features

Fragments

Render multiple sibling elements without a wrapper:

import { hFragment } from './framework.js';

const MultipleElements = defineComponent({
  render() {
    return hFragment([
      h('h1', {}, ['Title']),
      h('p', {}, ['Paragraph 1']),
      h('p', {}, ['Paragraph 2'])
    ]);
  }
});

Conditional Rendering

const ConditionalComponent = defineComponent({
  state() {
    return { showMessage: false };
  },
  
  render() {
    return h('div', {}, [
      h('button', {
        on: {
          click: () => this.updateState({ 
            showMessage: !this.state.showMessage 
          })
        }
      }, ['Toggle']),
      
      // Conditional rendering
      this.state.showMessage ? h('p', {}, ['Hello!']) : null
    ]);
  }
});

Lists and Keys

const TodoList = defineComponent({
  state() {
    return {
      todos: [
        { id: 1, text: 'Learn framework', completed: false },
        { id: 2, text: 'Build app', completed: true }
      ]
    };
  },
  
  render() {
    return h('ul', {}, 
      this.state.todos.map(todo => 
        h(TodoItem, { 
          key: todo.id, // Important for efficient updates
          text: todo.text,
          completed: todo.completed
        })
      )
    );
  }
});

Styling

const StyledComponent = defineComponent({
  render() {
    return h('div', {
      class: ['container', 'active'], // Array of classes
      style: {
        backgroundColor: 'blue',
        padding: '20px',
        margin: '10px'
      }
    }, ['Styled content']);
  }
});

API Reference

Core Functions

h(tag, props, children)

Create a virtual DOM node.

createApp(RootComponent, props, options)

Create an application instance.

defineComponent(options)

Define a reusable component.

Component Options

defineComponent({
  // Initial state factory
  state(props) {
    return { /* initial state */ };
  },
  
  // Lifecycle hooks
  onMounted() { /* after mount */ },
  onUnmounted() { /* before unmount */ },
  
  // Render function
  render() {
    return h(/* virtual DOM */);
  },
  
  // Custom methods
  customMethod() {
    // Available as this.customMethod()
  }
});

Component Instance Methods

  • updateState(newState) - Update component state
  • updateProps(newProps) - Update component props
  • emit(eventName, payload) - Emit custom event

Component Instance Properties

  • this.props - Component props
  • this.state - Component state
  • this.appContext - Application context (router, etc.)

Router API

// Router instance methods
router.navigateTo(path)     // Navigate to path
router.back()               // Go back in history
router.forward()            // Go forward in history
router.subscribe(handler)   // Subscribe to route changes
router.unsubscribe(handler) // Unsubscribe from route changes

// Router instance properties
router.matchedRoute  // Current matched route
router.params        // Route parameters
router.query         // Query parameters

Best Practices

Component Organization

// Keep components focused and single-purpose
const Button = defineComponent({
  render() {
    const { variant = 'primary', disabled = false } = this.props;
    
    return h('button', {
      class: [`btn`, `btn-${variant}`],
      disabled,
      on: {
        click: () => this.emit('click')
      }
    }, [hSlot()]);
  }
});

State Management

// Use computed values in render
const UserCard = defineComponent({
  render() {
    const { user } = this.props;
    const displayName = user.firstName + ' ' + user.lastName;
    
    return h('div', { class: 'user-card' }, [
      h('h3', {}, [displayName]),
      h('p', {}, [user.email])
    ]);
  }
});

Performance

// Use keys for lists
this.state.items.map(item => 
  h(ItemComponent, { key: item.id, ...item })
)

// Minimize unnecessary re-renders by checking props/state changes
// The framework automatically does shallow comparison for optimization

How It Works (Overview)

1. Render Cycle

  • Each component implements a render() function that returns a virtual DOM tree using h().
  • When state or props change, a new tree is generated and diffed against the previous one, producing a minimal set of DOM updates.
  • DOM changes are batched for performance.

2. Reactive State

  • Each component has its own state object.
  • Calling updateState(partial) merges new state and schedules a re-render.
  • State is local to each component instance.

3. Lifecycle

| Hook | When it runs | |-------------|------------------------------| | onMounted | After first DOM patch | | onUnmounted | Before element is removed |

Events emitted by children bubble up to parents via an internal dispatcher.

4. Composition

  • Fragments: Render multiple siblings without extra wrappers.
  • Slots: Placeholders for projected content from parents.

5. Router

  • Hash-based router listens to hashchange and matches routes.
  • Async beforeEnter guards can block, redirect, or allow navigation.

6. Footprint

  • Distributed as a single ES module (~6 kB min+gzip).
  • No build step or external dependencies required.

Examples

See the examples directory for sample apps and usage patterns.