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

@ankabit/micro-framework

v1.1.0

Published

A lightweight, framework-agnostic micro-frontend framework with routing, modules, and event filtering

Readme

@ankabit/micro-framework

A lightweight, framework-agnostic micro-frontend framework with built-in routing, module loading, and event filtering.

🚀 Features

  • Framework Agnostic - Works with vanilla JS, React, Vue, or any other framework
  • Auto-Binding Navigation - Zero-config link binding with data-route attributes
  • SPA Router - History mode, hash mode, and hash-bang mode support
  • Module System - Dynamic module loading with lifecycle management
  • Event Filtering - Pipeline-based event system for data transformation
  • Robust Container Handling - Automatic stale container detection and recovery
  • Clean Route Definition - Object-based routes with shorthand syntax
  • Plugin Architecture - Easy to extend and customize
  • TypeScript Support - Full type definitions included

📦 Installation

NPM Install

npm install @ankabit/micro-framework

CDN (Browser)

<!-- Framework JavaScript (required) -->
<script src="https://unpkg.com/@ankabit/[email protected]/dist/micro-framework.min.js"></script>

<!-- CSS (optional - only needed for default 404 pages and examples) -->
<link
	rel="stylesheet"
	href="https://unpkg.com/@ankabit/[email protected]/src/micro-framework.css"
/>

Note: The CSS is completely optional. It's only used for:

  • Default 404 error pages (if no custom notFoundHandler is provided)
  • Example applications and demos

If you provide a custom 404 handler or use your own styling, you don't need to include the CSS.

🚀 Quick Start

NPM Usage

import MicroFramework from "@ankabit/micro-framework";
// Or CommonJS
const MicroFramework = require("@ankabit/micro-framework");

const app = new MicroFramework({
	container: "#app",
	router: {mode: "history"},
});

app.start();

Browser Usage

<!DOCTYPE html>
<html>
	<head>
		<title>My App</title>
		<!-- CSS optional - only for default 404 pages -->
		<link rel="stylesheet" href="src/micro-framework.css" />
	</head>
	<body>
		<!-- Create your own UI structure -->
		<nav>
			<button data-route="/">Home</button>
			<button data-route="/dashboard">Dashboard</button>
		</nav>

		<!-- Framework only manages this container -->
		<div id="app"></div>

		<!-- Optional loading spinner -->
		<div id="loading" style="display: none;">Loading...</div>

		<script src="src/micro-framework.js"></script>
		<script>
			const app = new MicroFramework({
				container: "#app", // Required: where modules render
				loadingSpinner: "#loading", // Optional: loading indicator
				router: {
					mode: "history", // 'history', 'hash', or 'hashbang'
				},
			});

			app.start();
		</script>
	</body>
</html>

🔗 Auto-Binding Navigation

The framework automatically binds navigation links - no JavaScript required! Just add data-route attributes to any element.

Basic Usage

<!-- Any element can be a navigation link -->
<a data-route="/dashboard">Dashboard</a>
<button data-route="/users">Users</button>
<div data-route="/settings">Settings</div>

<!-- Works with traditional links too (fallback to href) -->
<a href="/profile" data-route>Profile</a>

Configuration

const app = new MicroFramework({
    container: '#app',
    
    // Auto-binding options (all optional)
    autoBindLinks: true,           // Enable/disable auto-binding (default: true)
    linkSelector: '[data-route]'   // CSS selector to match (default: '[data-route]')
});

Custom Selectors

// Use regular links
const app = new MicroFramework({
    linkSelector: 'a[href]' // Bind all links with href
});

// Use custom attribute
const app = new MicroFramework({
    linkSelector: '[data-navigate]' // <div data-navigate="/page">Page</div>
});

// Use CSS class
const app = new MicroFramework({
    linkSelector: '.nav-link' // <span class="nav-link" data-route="/page">Page</span>
});

How It Works

  • Event Delegation: Single click listener on document handles all navigation
  • Dynamic Content: Works automatically with AJAX-loaded content
  • Performance: Ultra-fast with minimal runtime logic (4 lines of code)
  • Flexible: Works with any HTML element, not just <a> tags

2. Create Your First Module

// modules/dashboard.js
export default {
	name: "dashboard",
	render(container, params) {
		container.innerHTML = `
            <h1>Dashboard</h1>
            <p>Welcome to your dashboard!</p>
        `;
	},
	destroy() {
		// Cleanup logic
	},
};

3. Register Routes and Modules

// Register a module
app.registerModule("dashboard", dashboardModule);

// Route with module only - just loads and renders the module
app.registerRoute("/", {
	module: "dashboard", // No handler needed - uses module.render automatically
});

// Route with function handler only
app.registerRoute("/api/users", {
	handler: async (params, context, route) => {
		const users = await fetchUsers();
		context.render(`
            <h1>Users API</h1>
            <pre>${JSON.stringify(users, null, 2)}</pre>
        `);
	},
});

// Route with template handler
app.registerRoute("/hello/:name", {
	handler: "<h1>Hello {{name}}!</h1><p>Welcome to our site.</p>",
});

// Route with module + custom handler
app.registerRoute("/dashboard/settings", {
	module: "dashboard",
	handler: (params, context, route) => {
		// Custom logic after module loads
		console.log("Dashboard settings loaded");
	},
});

// Start the application
app.start();

📚 Documentation

Configuration Options

const app = new MicroFramework({
	// Container element - where modules will be rendered
	container: "#app", // CSS selector or DOM element

	// Optional loading spinner element
	loadingSpinner: "#loading-spinner", // CSS selector or DOM element (optional)

	// Router configuration
	router: {
		mode: "history", // 'history', 'hash', 'hashbang'
		base: "", // Base path for history mode
		hashbang: false, // Use #! instead of # for hash mode
		beforeEnter: null, // Global beforeEnter guard
		afterEnter: null, // Global afterEnter hook
		notFoundHandler: null, // Custom 404 handler (function, string, or module)
	},

	// Legacy router options (for backward compatibility)
	mode: "history", // Use router.mode instead
	base: "", // Use router.base instead
	hashbang: false, // Use router.hashbang instead

	// Module configuration
	moduleBase: "./modules/", // Base path for dynamic imports
	lazy: true, // Enable lazy loading

	// Event system configuration
	enableEventLogging: false, // Enable event logging for debugging
	eventLogPrefix: "[MyApp]", // Custom log prefix

	// Auto-binding configuration
	autoBindLinks: true, // Enable automatic link binding (default: true)
	linkSelector: '[data-route]', // CSS selector for navigation links (default: '[data-route]')

	// Hooks
	onBeforeRouteChange: null,
	onAfterRouteChange: null,
	onModuleLoad: null,
	onModuleError: null,
});

Module Structure

export default {
	name: "module-name",

	// Required: Render function
	render(container, params, context) {
		// Render your module content
		container.innerHTML = "<h1>Hello World</h1>";
	},

	// Optional: Lifecycle hooks
	beforeMount(params, context) {
		// Called before render
	},

	afterMount(container, params, context) {
		// Called after render
	},

	destroy() {
		// Cleanup when module is unmounted
	},

	// Optional: Route guards
	beforeEnter(to, from) {
		// Return false to cancel navigation
		return true;
	},

	afterEnter(to, from) {
		// Called after successful navigation
	},
};

API Reference

Core Methods

// Module management
app.registerModule(name, module);
app.unregisterModule(name);
app.loadModule(name, params);

// Routing - Clean, consistent API
app.registerRoute(path, { module: 'name' });                         // Module only (uses module.render)
app.registerRoute(path, { handler: function });                      // Function handler
app.registerRoute(path, { handler: 'template string' });             // Template handler
app.registerRoute(path, { module: 'name', handler: function });      // Module + custom handler

app.navigate(path);
app.getCurrentRoute();

// Event system
app.on(event, callback);
app.off(event, callback);
app.emit(event, data);                    // Fire and forget
app.filter(event, data);                  // Transform data through listeners

// Plugin system
app.use(plugin);

Events

The framework provides a centralized event system with predefined event constants:

// Import event constants
const {EVENTS} = MicroFramework;

// Listen to framework events using constants
app.on(EVENTS.ROUTE_CHANGE, (route) => {
	console.log("Route changed to:", route.path);
});

app.on(EVENTS.MODULE_LOAD, (moduleData) => {
	console.log("Module loaded:", moduleData.name);
});

app.on(EVENTS.MODULE_ERROR, (error) => {
	console.error("Module error:", error);
});

// Available events
app.on(EVENTS.FRAMEWORK_READY, () => console.log("Framework ready"));
app.on(EVENTS.FRAMEWORK_DESTROYED, () => console.log("Framework destroyed"));
app.on(EVENTS.MODULE_REGISTERED, (data) =>
	console.log("Module registered", data)
);
app.on(EVENTS.MODULE_UNREGISTERED, (data) =>
	console.log("Module unregistered", data)
);
app.on(EVENTS.ROUTE_REGISTERED, (route) =>
	console.log("Route registered", route)
);
app.on(EVENTS.ROUTE_WILL_CHANGE, (data) => 
	console.log("Route will change", data.to.path)
);
app.on(EVENTS.ROUTE_ERROR, (error) => console.error("Route error", error));
app.on(EVENTS.ROUTE_404, (data) => console.log("404 error", data));
app.on(EVENTS.LOADING_CHANGE, (isLoading) =>
	console.log("Loading:", isLoading)
);
app.on(EVENTS.ERROR, (error) => console.error("Error", error));
app.on(EVENTS.CONTAINER_REINITIALIZED, () => 
	console.log("Container reinitialized")
);
app.on(EVENTS.CONTAINER_REMOVED, () => 
	console.log("Container removed")
);
app.on(EVENTS.CONTAINER_RECOVERED, () => 
	console.log("Container recovered")
);
app.on(EVENTS.CONTAINER_RECOVERY_FAILED, (error) => 
	console.error("Container recovery failed", error)
);
app.on(EVENTS.PLUGIN_INSTALLED, (plugin) =>
	console.log("Plugin installed", plugin)
);

Event Debugging

Enable event logging for debugging:

const app = new MicroFramework({
	container: "#app",
	enableEventLogging: true, // Enable event logging
	eventLogPrefix: "[MyApp Event]", // Custom log prefix
});

// Get event history for debugging
const history = app.getEventHistory();
console.log("Event history:", history);

// Clear event history
app.clearEventHistory();

// Get all available event names
const eventNames = app.getEventNames();
console.log("Available events:", eventNames);

🎨 Theming

The framework includes a flexible theming system:

/* Custom theme */
:root {
	--primary-color: #007bff;
	--secondary-color: #6c757d;
	--success-color: #28a745;
	--danger-color: #dc3545;
	--warning-color: #ffc107;
	--info-color: #17a2b8;
	--light-color: #f8f9fa;
	--dark-color: #343a40;
}

🎨 CSS Framework (Optional)

The framework includes an optional CSS file that provides:

  • Default 404 error page styling (only used if no custom notFoundHandler is provided)
  • UI components for examples (buttons, cards, forms, etc.)
  • CSS custom properties for theming

When CSS is NOT needed:

  • Custom 404 handlers - You control error page styling
  • Custom UI framework - Using Bootstrap, Tailwind, etc.
  • Headless usage - API-only or custom rendering

When CSS is helpful:

  • 📝 Quick prototypes - Use provided UI components
  • 🚫 Default 404 pages - Framework handles error styling
  • 🎯 Learning/examples - Consistent styling across demos
<!-- Include only if needed -->
<link
	rel="stylesheet"
	href="https://unpkg.com/@ankabit/[email protected]/src/micro-framework.css"
/>

📁 Project Structure

your-project/
├── index.html
├── modules/
│   ├── dashboard.js
│   ├── users.js
│   └── settings.js
├── assets/
│   ├── css/
│   └── js/
└── src/
    ├── micro-framework.js
    └── micro-framework.css

🔧 Advanced Usage

Dynamic Module Loading

// Load module from URL
app.loadModuleFromUrl("./modules/advanced-module.js");

// Load module with parameters
app.loadModule("dashboard", {userId: 123});

// Conditional loading
if (user.isAdmin) {
	app.loadModule("admin-panel");
}

Route Guards

Global Route Guards

const app = new MicroFramework({
	router: {
		// Global beforeEnter guard (runs for all routes)
		beforeEnter: async (to, from) => {
			console.log(
				`Navigating from ${from?.path || "initial"} to ${to.path}`
			);

			// Global authentication check
			if (!user.isAuthenticated && to.path !== "/login") {
				app.navigate("/login");
				return false; // Cancel navigation
			}

			// Global permission check
			if (to.path.startsWith("/admin") && !user.isAdmin) {
				app.navigate("/unauthorized");
				return false;
			}

			return true; // Allow navigation
		},

		// Global afterEnter hook (runs after successful navigation)
		afterEnter: (to, from) => {
			// Global analytics tracking
			analytics.track("page_view", to.path);

			// Update page title
			document.title = `MyApp - ${to.moduleName}`;

			// Scroll to top
			window.scrollTo(0, 0);
		},
	},
});

Route-Specific Guards

app.registerRoute("/admin", {
	module: "admin-panel",
	beforeEnter: (to, from) => {
		// Route-specific check (runs after global beforeEnter)
		if (!user.hasAdminAccess()) {
			app.navigate("/dashboard");
			return false;
		}
		return true;
	},

	afterEnter: (to, from) => {
		// Route-specific action (runs before global afterEnter)
		console.log("Admin panel loaded");
	},
});

Route Lifecycle & Event Order

  1. ROUTE_WILL_CHANGE event - Fires immediately when navigation starts
  2. Global beforeEnter - Runs first for all routes
  3. Route-specific beforeEnter - Runs if global guard allows navigation
  4. Module loading and rendering - Core navigation logic
  5. Route-specific afterEnter - Runs first after successful navigation
  6. Global afterEnter - Runs last after successful navigation
  7. ROUTE_CHANGE event - Fires after everything completes successfully
// Example: Track navigation lifecycle
app.on('route:will_change', ({ to, from, path }) => {
    console.log(`Starting navigation from ${from?.path} to ${path}`);
    showLoadingIndicator();
    analytics.track('navigation_start', { to: path, from: from?.path });
});

app.on('route:change', (route) => {
    console.log(`Navigation completed to ${route.path}`);
    hideLoadingIndicator();
    analytics.track('navigation_complete', { path: route.path });
});

Custom 404 Handlers

The framework supports customizable 404 error pages when routes are not found:

Note: When using custom 404 handlers, you don't need the framework CSS since you control the error page styling.

const app = new MicroFramework({
    container: '#app',
    router: {
        // Function handler - full control over 404 rendering
        notFoundHandler: (path, context) => {
            context.render(`
                <div class="custom-404">
                    <h1>🔍 Page Not Found</h1>
                    <p>The route <strong>${path}</strong> doesn't exist.</p>
                    <button onclick="app.navigate('/')">Go Home</button>
                </div>
            `;
        }

        // OR: Template string handler with {{path}} placeholder
        notFoundHandler: `
            <div class="error-page">
                <h1>404 - Not Found</h1>
                <p>Route {{path}} was not found.</p>
            </div>
        `

        // OR: Module handler - load a specific module for 404s
        notFoundHandler: {
            module: 'error-page' // Will receive { path } as params
        }
    }
});

If no custom handler is provided, the framework falls back to the default 404 page.

🛤️ Simplified Routing

The framework uses a clean, consistent API for route definition:

Route Options

Every route is defined with app.registerRoute(path, options):

// Module only - loads and renders the module
app.registerRoute("/dashboard", {
	module: "dashboard",
});

// Function handler - executes custom logic
app.registerRoute("/api/status", {
	handler: (params, context) => {
		context.framework.moduleContainer.innerHTML = `
            <div class="status">
                <h1>System Status</h1>
                <p>All systems operational ✅</p>
            </div>
        `;
	},
});

// Template handler - renders string with variable substitution
app.registerRoute("/hello/:name", {
	handler: "<h1>Hello {{name}}!</h1><p>Welcome to our site.</p>",
});

// Module + handler - loads module, then runs custom logic
app.registerRoute("/dashboard/analytics", {
	module: "dashboard",
	handler: (params, context) => {
		// Module loaded, now add specific behavior
		console.log("Analytics dashboard ready");
	},
});

Auto-Registration with Modules

Modules can define their own routes for automatic registration:

// Module with built-in routes - clean object syntax
const shopModule = {
	routes: {
		"/shop": {}, // Empty object - uses module.render
		"/shop/cart": {}, // Another simple route
		"/shop/product/:id": async (params, context) => {
			// Function shorthand
			const product = await fetchProduct(params.id);
			document.getElementById("product-detail").innerHTML = product.name;
		},
		"/shop/settings": {
			// Full options object
			beforeEnter: (to, from) => checkAdminAccess(), // No handler - uses module.render
		},
	},

	render(container, params, context) {
		container.innerHTML = `
            <div id="shop-header"><h1>Shop</h1></div>
            <div id="shop-content">Default shop content</div>
            <div id="product-detail"></div>
        `;
	},
};

// Register module - routes are automatically registered too!
app.registerModule("shop", shopModule);
// This automatically calls:
// app.registerRoute('/shop', { module: 'shop' });
// app.registerRoute('/shop/cart', { module: 'shop' });
// app.registerRoute('/shop/product/:id', { module: 'shop', handler: function });
// app.registerRoute('/shop/settings', { module: 'shop', beforeEnter: ... });

Shorthand Syntax

The object-based routes support convenient shorthand notation:

const myModule = {
	routes: {
		// Empty object shorthand - uses module.render
		"/simple": {}, // → { module: 'myModule' }

		// String shorthand - template handler
		"/template/:name": "Hello {{name}}!", // → { handler: 'Hello {{name}}!' }

		// Function shorthand
		"/api/users": async (params, context) => {
			// → { handler: function }
			const users = await fetchUsers();
			context.framework.moduleContainer.innerHTML = renderUsers(users);
		},

		// Full options object
		"/admin": {
			beforeEnter: (to, from) => checkAdminAccess(), // Uses module.render
			afterEnter: (to, from) => logAdminAccess(),
		},
	},
};

Template Variables

String handlers support simple variable substitution:

app.registerRoute("/user/:id/profile/:tab", {
	handler: `
        <div class="profile">
            <h1>User {{id}}</h1>
            <div class="active-tab">{{tab}}</div>
        </div>
    `,
});
// /user/123/profile/settings → renders with id=123, tab=settings

Module Communication

// Module A
app.emit("user:selected", {id: 123});

// Module B
app.on("user:selected", (user) => {
	// Handle user selection
});

🎨 Container Rendering

The framework provides a robust container handling system with automatic stale container detection and recovery.

Render API

Route handlers and modules can use the simplified render() method from the context:

// String content with auto-bound navigation
app.registerRoute('/hello', {
    handler: (params, context) => {
        context.render(`
            <h1>Hello World!</h1>
            <a data-route="/dashboard">Go to Dashboard</a>
        `);
    }
});

// HTML Element
app.registerRoute('/component', {
    handler: (params, context) => {
        const div = document.createElement('div');
        div.textContent = 'Dynamic component';
        context.render(div);
    }
});

// Function for complex rendering with navigation
app.registerRoute('/complex', {
    handler: (params, context) => {
        context.render((container) => {
            // Custom rendering logic with full container access
            const header = document.createElement('h1');
            header.textContent = 'Complex Layout';
            container.appendChild(header);
            
            // Auto-bound navigation links work in dynamic content
            const nav = document.createElement('nav');
            nav.innerHTML = `
                <button data-route="/users">Users</button>
                <button data-route="/settings">Settings</button>
            `;
            container.appendChild(nav);
        });
    }
});

Automatic Container Recovery

The framework automatically handles scenarios where the DOM container becomes stale:

  • Detection: Monitors container availability using MutationObserver
  • Re-initialization: Automatically finds and re-initializes containers when needed
  • Recovery: Re-renders current module after container recovery
  • Events: Emits container lifecycle events for monitoring
// Listen for container events
app.on('container:removed', () => {
    console.log('Container was removed from DOM');
});

app.on('container:recovered', () => {
    console.log('Container successfully recovered');
});

app.on('container:recovery_failed', (error) => {
    console.error('Container recovery failed:', error);
});

Direct Container Access

For advanced use cases, you can still access the container directly:

// Get the current container (automatically validates and re-initializes if needed)
const container = app.getContainer();

// Check if container is valid without re-initialization
const isValid = app.isContainerValid();

🔌 Plugins

Extend the framework with plugins:

const analyticsPlugin = {
	install(framework) {
		framework.on("route:change", (route) => {
			analytics.track("page_view", route.path);
		});
	},
};

app.use(analyticsPlugin);

🛠️ Development

# Clone the repository
git clone <repo-url>

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Run tests
npm test

📋 Examples

Check the examples/ directory for:

  • Basic usage examples
  • Module examples
  • Advanced routing scenarios
  • Plugin development

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Commit your changes
  4. Push to the branch
  5. Create a Pull Request

📄 License

MIT License - see LICENSE file for details