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

@piyushagade/flexirouter

v1.0.2

Published

A lightweight, feature-rich SPA router supporting Hash/Path modes, dynamic params, and an event bus.

Readme

@piyushagade/flexirouter 🚀

A lightweight, feature-loaded Single Page Application (SPA) router for jQuery-powered projects. FlexiRouter transforms your traditional multi-page website into a modern, fluid experience with zero-config hash routing or clean Path URLs.


✨ Features

  • Dual Mode: Switch between clean Path URLs (History API) and Zero-Config Hash URLs (#/path).
  • Dynamic Parameters: Pattern matching to extract variables from URLs (e.g., /user/:id).
  • Lifecycle Hooks: Granular control with onLoad, onClose, and onRefresh.
  • Lazy Loading: Dynamic imports for modules, loading code only when the route is active.
  • Route Guards: Global authGuard and per-route beforeEnter middleware.
  • Event Bus: A built-in Pub/Sub system for decoupled module communication.
  • Headless Breadcrumbs: Returns an array of "Steps" on every route change for custom UI rendering.
  • Loading States: Built-in management for async loading overlays.

✨ New in v1.0.1

  • Auto-Queueing: Call router.navigate() inside onLoad without setTimeout. The router now queues nested calls automatically.
  • 🎣 Lifecycle Fixes: onClose and onRefresh now fire reliably with standardized arguments.
  • 🛡️ MIME Safety: Documentation updated to address nested path MIME type errors via absolute paths.

📦 Installation

npm install @piyushagade/flexirouter
<script type="module" src="https://cdn.jsdelivr.net/npm/@piyushagade/flexirouter@latest/FlexiRouter.min.js"></script>

🚯 Quick Start

1. Define your HTML

Mark your views with a specific class or data attribute (default is [data-route-view]). The dafault data attribute for navigation links is [data-link]. Both attribute names can be configured.

<!-- Navigation -->
<nav>
  <a href="/dashboard" data-link>Dashboard</a>
  <a href="/settings" data-link>Settings</a>
</nav>

<!-- Views -->
<div id="view-dashboard" data-route-view class="hidden">Dashboard Content</div>
<div id="view-settings" data-route-view class="hidden">Settings Content</div>

<!-- Optional Loader -->
<div id="router-loader" style="display:none;">Loading...</div>

2. Initialize the Router

import FlexiRouter from '@piyushagade/flexirouter';

new FlexiRouter({
    useHash: true,
    routes: {
        '/': { redirectTo: '/dashboard' },
        '/dashboard': { 
            target: 'view-dashboard',
            steps: [{ name: 'Home', url: '/' }, { name: 'Dashboard', url: '/dashboard' }]
        },
        '/user/:username': {
            target: 'view-profile',
            module: './ProfileManager.js',
            initMethod: 'init'
        },
        '/printer/:id': {
            onLoad: ({ params }) => {
                console.log("Entering printer", params.id);
            },
            onClose: () => console.log("Exiting printer")
        }
    }
});

Another example:

const router = new FlexiRouter({
    routes: {
        '/': {
            onLoad: (router) => router.navigate('/dashboard')
        },
        '/profile/:id': {
            onLoad: (router, { params }) => console.log("Entering", params.id),
            onClose: () => console.log("Cleaning up")
        }
    }
});

🛡️ Route Middleware & Guards

const router = new FlexiRouter({
    authGuard: async () => {
        return !!localStorage.getItem('token');
    },
    routes: {
        '/admin': {
            target: 'admin-view',
            protected: true,
            beforeEnter: async (params) => confirm("Enter restricted area?")
        }
    }
});

🎣 Lifecycle Hooks

If you do not wish to use FlexiRouter as described in Define your HTML section above, you can manually control DOM by bypassing the target property and using the following functions instead:

  • onLoad: Triggered when entering a route.
  • onClose: Triggered when leaving a route for a new one.
  • onRefresh: Triggered when navigating to the already active route.
const router = new FlexiRouter({
    authGuard: async () => {
        return !!localStorage.getItem('token');
    },
    routes: {
        '/': { 
            onLoad: (router, { params }) => {
                if (!auth.loggedin) router.navigate('/login');
                else router.navigate('/profile/' + auth.userId)
            },
            onClose: (router, { params }) => {
                console.log('Leaving login view');
            },
        },
        '/profile/:id': {
            onLoad: (router, { params }) => {
                console.log('User profile loaded for: ' + auth.userId);

                // DOM manipulation
                $('#login-overlay').fadeOut(200);
                $('#member-overlay').fadeIn(100);

                loadUserData(auth.userId);
            },

        }
    }
});

📢 Event Bus

router.on('user-login', (user) => console.log(user.name));
router.emit('user-login', { name: 'Piyush' });

🌐 Server Configurations

For Path Mode (useHash: false), your server must serve index.html for all routes to allow FlexiRouter to take over routing.

Express.js

// Serve static files first, then fallback to index.html for non-API routes
app.use(express.static('public'));
app.get(/^(?!\/api).+/, (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

Flask (Python)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    return render_template('index.html')

Nginx

location / {
    try_files $uri $uri/ /index.html;
}

🛠️ Migration Guide

  1. Consolidate: Move body content into index.html within data-route-view divs.
  2. Links: Add data-link to anchor tags.
  3. Logic: Move jQuery logic into ES Modules.
  4. Server: For Path mode, redirect 404s to index.html.

📄 License

MIT © Piyush Agade