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

@deijose/nix-ionic

v0.5.0

Published

Ionic lifecycle & router bridge for Nix.js

Readme

@deijose/nix-ionic

npm version License: MIT

Ionic bridge for Nix.js — routing, lifecycle hooks, and navigation powered by the official ion-router API.


How it works

@deijose/nix-ionic bridges Nix.js components with Ionic Core's official vanilla JS routing system:

  1. Each route is registered as a Custom Element (nix-page-home, nix-page-detail, etc.)
  2. ion-router activates the correct custom element based on the URL
  3. ion-router-outlet manages: view cache, page transitions, back button, iOS swipe back — all native, zero custom code
  4. connectedCallback mounts the Nix component inside the custom element
  5. ionRouteWillChange / ionRouteDidChange drive the Nix lifecycle hooks

This gives you the same integration depth as @ionic/angular and @ionic/react, using only the public ion-router API.


Installation

npm install @deijose/nix-ionic @deijose/nix-js @ionic/core

Modular component loading (v0.3.0+)

Starting with v0.3.0, setupNixIonic() only registers 6 minimal core components needed for routing (ion-app, ion-router, ion-route, ion-router-outlet, ion-back-button, ion-icon). All other components are loaded on demand.

This means you only pay for what you use, reducing your initial bundle size dramatically.

Three ways to load components

1. Individual components (maximum tree-shaking) ✅

Import only the exact components you need:

import { setupNixIonic } from "@deijose/nix-ionic";
import {
  defineIonHeader,
  defineIonToolbar,
  defineIonTitle,
  defineIonContent,
  defineIonButton,
} from "@deijose/nix-ionic/components";

setupNixIonic({
  components: [
    defineIonHeader,
    defineIonToolbar,
    defineIonTitle,
    defineIonContent,
    defineIonButton,
  ],
});

2. Category bundles (balanced approach)

Load components by category:

import { setupNixIonic } from "@deijose/nix-ionic";
import { layoutComponents } from "@deijose/nix-ionic/bundles/layout";
import { buttonComponents } from "@deijose/nix-ionic/bundles/buttons";
import { listComponents } from "@deijose/nix-ionic/bundles/lists";

setupNixIonic({
  components: [...layoutComponents, ...buttonComponents, ...listComponents],
});

Available bundles:

| Bundle | Import path | Components | |---|---|---| | Layout | @deijose/nix-ionic/bundles/layout | header, toolbar, title, content, footer, buttons | | Navigation | @deijose/nix-ionic/bundles/navigation | menu, menu-button | | Forms | @deijose/nix-ionic/bundles/forms | input, textarea, checkbox, toggle, select, select-option, radio, radio-group, range, searchbar | | Lists | @deijose/nix-ionic/bundles/lists | list, list-header, item, item-divider, item-sliding, item-options, item-option, label, note, card, card-header, card-title, card-subtitle, card-content | | Feedback | @deijose/nix-ionic/bundles/feedback | spinner, progress-bar, skeleton-text, badge, avatar, thumbnail | | Buttons | @deijose/nix-ionic/bundles/buttons | button, fab, fab-button, fab-list, ripple-effect | | Overlays | @deijose/nix-ionic/bundles/overlays | modal, popover, toast, alert | | All | @deijose/nix-ionic/bundles/all | All of the above |

3. All components (same as v0.2.x)

If you want backward-compatible behavior with all components loaded at once:

import { setupNixIonic } from "@deijose/nix-ionic";
import { allComponents } from "@deijose/nix-ionic/bundles/all";

setupNixIonic({ components: allComponents });

Migration from v0.2.x

  import { setupNixIonic } from "@deijose/nix-ionic";
+ import { allComponents } from "@deijose/nix-ionic/bundles/all";

- setupNixIonic();
+ setupNixIonic({ components: allComponents });

Or better yet, import only what you actually use for a smaller bundle.


Quick start

1. Initialize and Mount in main.ts

// 1. Core Styles (order matters)
import "@ionic/core/css/core.css";
import "@ionic/core/css/normalize.css";
import "@ionic/core/css/structure.css";
import "@ionic/core/css/typography.css";
import "@ionic/core/css/padding.css";
import "@ionic/core/css/flex-utils.css";
import "@ionic/core/css/display.css";
import "./style.css";

// 2. Framework Imports
import { NixComponent, html, mount } from "@deijose/nix-js";
import { setupNixIonic, IonRouterOutlet } from "@deijose/nix-ionic";
import { layoutComponents } from "@deijose/nix-ionic/bundles/layout";
import { defineIonButton } from "@deijose/nix-ionic/components";

// 3. Pages
import { HomePage }   from "./pages/HomePage";
import { DetailPage } from "./pages/DetailPage";

// Configure and inject Ionic Core (only the components you use)
setupNixIonic({
  components: [...layoutComponents, defineIonButton],
});

// 4. Router Configuration
const outlet = new IonRouterOutlet([
  { path: "/",           component: (ctx) => new HomePage(ctx)   },
  { path: "/detail/:id", component: (ctx) => new DetailPage(ctx) }
]);

// 5. App Component
class App extends NixComponent {
  override render() {
    return html`<ion-app>${outlet}</ion-app>`;
  }
}

// 6. Bootstrap
mount(new App(), "#app");

Pages

Class component — IonPage

Use IonPage when you need navigation lifecycle hooks.

import { html, signal } from "@deijose/nix-js";
import { IonPage, IonBackButton, useRouter } from "@deijose/nix-ionic";
import type { NixTemplate } from "@deijose/nix-js";
import type { PageContext } from "@deijose/nix-ionic";

export class DetailPage extends IonPage {
  private post = signal<Post | null>(null);
  private _id: string;

  constructor({ lc, params }: PageContext) {
    super(lc);
    this._id = params["id"] ?? "1";
  }

  // Called on EVERY activation — even when returning from cached stack
  override ionViewWillEnter(): void {
    this._loadPost(this._id);
  }

  // Called when leaving the view (still in cache)
  override ionViewWillLeave(): void {
    // pause timers, subscriptions, etc.
  }

  override render(): NixTemplate {
    return html`
      <ion-header>
        <ion-toolbar>
          <ion-buttons slot="start">
            ${IonBackButton()}
          </ion-buttons>
          <ion-title>Detail</ion-title>
        </ion-toolbar>
      </ion-header>
      <ion-content class="ion-padding">
        <p>${() => this.post.value?.title ?? ""}</p>
      </ion-content>
    `;
  }
}

Function component — composables

import { html, signal } from "@deijose/nix-js";
import { useIonViewWillEnter, useIonViewWillLeave, IonBackButton } from "@deijose/nix-ionic";
import type { NixTemplate } from "@deijose/nix-js";
import type { PageContext } from "@deijose/nix-ionic";

export function ProfilePage({ lc }: PageContext): NixTemplate {
  const visits = signal(0);

  useIonViewWillEnter(lc, () => {
    visits.update((n) => n + 1);
  });

  useIonViewWillLeave(lc, () => {
    console.log("leaving profile");
  });

  return html`
    <ion-header>
      <ion-toolbar>
        <ion-buttons slot="start">
          ${IonBackButton()}
        </ion-buttons>
        <ion-title>Profile</ion-title>
      </ion-toolbar>
    </ion-header>
    <ion-content class="ion-padding">
      <p>Visits: ${() => visits.value}</p>
    </ion-content>
  `;
}

Navigation — useRouter()

Access the router singleton from anywhere without prop drilling:

import { useRouter } from "@deijose/nix-ionic";

const router = useRouter();

// Navigate forward
router.navigate("/detail/42");

// Navigate back
router.back();

// Replace current view (no history entry)
router.replace("/home");

// Reactive signals
router.canGoBack.value   // boolean — true when back stack exists
router.params.value      // { id: "42" } for /detail/:id
router.path.value        // current pathname

In a class component

override render(): NixTemplate {
  const router = useRouter(); // safe to call inside render()

  return html`
    <ion-button @click=${() => router.navigate("/profile")}>
      Go to Profile
    </ion-button>
  `;
}

IonBackButton()

A wrapper around <ion-back-button> that intercepts the click before Ionic's internal router processes it, calling router.back() directly. Automatically hidden on the root page.

import { IonBackButton } from "@deijose/nix-ionic";

// In any template — no arguments needed
html`
  <ion-buttons slot="start">
    ${IonBackButton()}
  </ion-buttons>
`

// Optional default href (navigates here if no back stack)
${IonBackButton("/")}

Lifecycle hooks

All hooks are optional. For class components, implement the methods directly. For function components, use the composables.

| Hook | When it fires | |---|---| | ionViewWillEnter | Before the view becomes visible (every activation) | | ionViewDidEnter | After the view is fully visible | | ionViewWillLeave | Before the view is hidden (stays in cache) | | ionViewDidLeave | After the view is hidden |

Key difference from onMount / onInit

onMount and onInit (from Nix.js) only fire once when the component is first created. Ionic caches views in the stack — when the user returns to a cached view, onMount does NOT run again.

Use ionViewWillEnter for anything that needs to refresh on every visit (data fetching, resetting state, restarting timers):

// ❌ Only runs once — misses subsequent visits
override onMount() {
  this._fetchData();
}

// ✅ Runs on every activation
override ionViewWillEnter() {
  this._fetchData();
}

Route guards

new IonRouterOutlet([
  { path: "/", component: (ctx) => new HomePage(ctx) },
  {
    path: "/admin",
    component: (ctx) => new AdminPage(ctx),
    beforeEnter: (to, from) => {
      if (!isLoggedIn()) return "/login"; // redirect
      if (!isAdmin())    return false;    // cancel navigation
      // return void or undefined to allow
    },
  },
]);

| Return value | Effect | |---|---| | void / undefined | Allow navigation | | false | Cancel — stay on current view | | "string" | Redirect to that path |


PageContext

Every route factory receives a PageContext:

interface PageContext {
  lc:     PageLifecycle;        // navigation lifecycle signals
  params: Record<string,string>; // /detail/:id → { id: "42" }
}

API Reference

setupNixIonic(options?)

setupNixIonic(options?: {
  iconAssetPath?: string;
  components?: ComponentDefiner[];
}): void

Initializes Ionic Core and registers the minimal routing components. Pass additional components via options.components.

IonRouterOutlet

new IonRouterOutlet(routes: RouteDefinition[])

Mounts ion-router + ion-router-outlet in the DOM. Registers a custom element per route. Initialize once in your app entry point.

useRouter()

useRouter(): RouterStore

Returns the active router store. Must be called after IonRouterOutlet is instantiated.

IonBackButton(defaultHref?)

IonBackButton(defaultHref?: string): NixTemplate

Back button that works with the Nix router. Hidden when canGoBack is false.

IonPage

Abstract class. Extend for pages that need lifecycle hooks.

abstract class IonPage extends NixComponent {
  constructor(lc: PageLifecycle)
  ionViewWillEnter?(): void
  ionViewDidEnter?():  void
  ionViewWillLeave?(): void
  ionViewDidLeave?():  void
  abstract render(): NixTemplate
}

Composables

useIonViewWillEnter(lc: PageLifecycle, fn: () => void): void
useIonViewDidEnter(lc:  PageLifecycle, fn: () => void): void
useIonViewWillLeave(lc: PageLifecycle, fn: () => void): void
useIonViewDidLeave(lc:  PageLifecycle, fn: () => void): void

Comparison with other frameworks

| Feature | @ionic/angular | @ionic/react | @deijose/nix-ionic | |---|---|---|---| | Router integration | Angular Router | React Router | ion-router (vanilla) | | View cache | ✅ | ✅ | ✅ native | | Page transitions | ✅ | ✅ | ✅ native | | iOS swipe back | ✅ | ✅ | ✅ native | | ion-back-button | native | wrapper | wrapper | | Lifecycle hooks | directive | hooks | IonPage / composables | | Navigation API | NavController | useHistory | useRouter() | | Modular loading | ❌ | ❌ | ✅ tree-shakeable |

Project setup

Prerequisites

| Tool | Version | Install | |---|---|---| | Node.js | ≥ 18 | nodejs.org | | npm | ≥ 9 | included with Node | | Capacitor CLI | latest | npm i -g @capacitor/cli | | Android Studio | latest | developer.android.com | | Xcode | ≥ 14 | Mac App Store |


Create a new project

# 1. Scaffold a Vite + TypeScript project
npm create vite@latest my-app -- --template vanilla-ts
cd my-app

# 2. Install dependencies
npm install

# 3. Install Nix.js + Ionic + nix-ionic
npm install @deijose/nix-js @ionic/core @deijose/nix-ionic

# 4. Install Capacitor (for native iOS / Android)
npm install @capacitor/core @capacitor/cli
npm install @capacitor/android @capacitor/ios

Recommended folder structure

my-app/
├── android/                      ← generated by Capacitor (do not edit manually)
├── ios/                          ← generated by Capacitor (do not edit manually)
├── public/
│   └── favicon.ico
├── src/
│   ├── ionic-nix/                ← copy from @deijose/nix-ionic if customizing
│   │   ├── IonRouterOutlet.ts
│   │   ├── lifecycle.ts
│   │   └── index.ts
│   │
│   ├── pages/                    ← one file per screen
│   │   ├── HomePage.ts
│   │   ├── DetailPage.ts
│   │   └── ProfilePage.ts
│   │
│   ├── components/               ← reusable UI components (not pages)
│   │   ├── PostCard.ts
│   │   └── Avatar.ts
│   │
│   ├── stores/                   ← global state via Nix.js createStore()
│   │   ├── auth.ts
│   │   └── cart.ts
│   │
│   ├── services/                 ← API calls, business logic
│   │   ├── api.ts
│   │   └── storage.ts
│   │
│   ├── style.css                 ← global styles + Ionic CSS imports
│   └── main.ts                   ← app entry point
│
├── index.html
├── capacitor.config.ts
├── package.json
├── tsconfig.json
└── vite.config.ts

main.ts — entry point

// 1. Core Styles (order matters)
import "@ionic/core/css/core.css";
import "@ionic/core/css/normalize.css";
import "@ionic/core/css/structure.css";
import "@ionic/core/css/typography.css";
import "@ionic/core/css/padding.css";
import "@ionic/core/css/flex-utils.css";
import "@ionic/core/css/display.css";
import "./style.css";

// 2. Framework Imports
import { NixComponent, html, mount } from "@deijose/nix-js";
import { setupNixIonic, IonRouterOutlet } from "@deijose/nix-ionic";
import { layoutComponents } from "@deijose/nix-ionic/bundles/layout";
import { buttonComponents } from "@deijose/nix-ionic/bundles/buttons";

// 3. Pages
import { HomePage }   from "./pages/HomePage";
import { DetailPage } from "./pages/DetailPage";

// Configure and inject Ionic Core
setupNixIonic({
  components: [...layoutComponents, ...buttonComponents],
});

// 4. Router Configuration
const outlet = new IonRouterOutlet([
  { path: "/",           component: (ctx) => new HomePage(ctx)   },
  { path: "/detail/:id", component: (ctx) => new DetailPage(ctx) }
]);

// 5. App Component
class App extends NixComponent {
  override render() {
    return html`<ion-app>${outlet}</ion-app>`;
  }
}

// 6. Bootstrap
mount(new App(), "#app");

index.html — root HTML

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="viewport-fit=cover, width=device-width, initial-scale=1.0,
               minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <title>My App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

capacitor.config.ts

import type { CapacitorConfig } from "@capacitor/cli";

const config: CapacitorConfig = {
  appId: "com.example.myapp",
  appName: "My App",
  webDir: "dist",
};

export default config;

vite.config.ts

import { defineConfig } from "vite";

export default defineConfig({
  optimizeDeps: {
    exclude: ["@ionic/core"],
  },
  build: {
    rollupOptions: {
      output: { manualChunks: undefined },
    },
  },
});

Development commands

Web

# Start dev server (hot reload)
npm run dev

# Type check
npx tsc --noEmit

# Production build
npm run build

# Preview production build
npm run preview

Android

First-time setup

# 1. Build the web app
npm run build

# 2. Initialize Capacitor (only once)
npx cap init "My App" "com.example.myapp" --web-dir dist

# 3. Add Android platform
npx cap add android

# 4. Sync web assets to native project
npx cap sync android

Daily workflow

# After any change to web code:
npm run build
npx cap sync android

# Open in Android Studio (run / debug from there)
npx cap open android

# Or run directly on a connected device / emulator
npx cap run android

Live reload on Android (dev)

# Start dev server first
npm run dev

# In a second terminal — live reload on device
npx cap run android --livereload --external

Note: device and computer must be on the same Wi-Fi network for live reload.


iOS

Requires macOS + Xcode.

First-time setup

# 1. Build the web app
npm run build

# 2. Add iOS platform
npx cap add ios

# 3. Sync
npx cap sync ios

Daily workflow

npm run build
npx cap sync ios

# Open in Xcode (run / debug from there)
npx cap open ios

# Or run on simulator
npx cap run ios

Live reload on iOS (dev)

npm run dev
npx cap run ios --livereload --external

Capacitor plugins

Add any official Capacitor plugin the same way:

# Camera
npm install @capacitor/camera
npx cap sync

# Filesystem
npm install @capacitor/filesystem
npx cap sync

# Push notifications
npm install @capacitor/push-notifications
npx cap sync

Then use them in your services:

// src/services/camera.ts
import { Camera, CameraResultType } from "@capacitor/camera";

export async function takePhoto(): Promise<string> {
  const photo = await Camera.getPhoto({
    quality:      90,
    allowEditing: false,
    resultType:   CameraResultType.DataUrl,
  });
  return photo.dataUrl ?? "";
}

Page template

Copy this as a starting point for any new page:

// src/pages/MyPage.ts
import { html, signal } from "@deijose/nix-js";
import type { NixTemplate } from "@deijose/nix-js";
import { IonPage, IonBackButton, useRouter } from "@deijose/nix-ionic";
import type { PageContext } from "@deijose/nix-ionic";

export class MyPage extends IonPage {
  private data = signal<string | null>(null);

  constructor({ lc, params }: PageContext) {
    super(lc);
    // params contains dynamic route segments
    // e.g. for /my/:id → params["id"]
  }

  // Runs on EVERY visit (initial + returning from stack)
  override ionViewWillEnter(): void {
    this._load();
  }

  // Runs when navigating away (view stays cached)
  override ionViewWillLeave(): void {
    // pause subscriptions, timers, etc.
  }

  private async _load(): Promise<void> {
    // fetch data
  }

  override render(): NixTemplate {
    const router = useRouter();

    return html`
      <ion-header>
        <ion-toolbar>
          <ion-buttons slot="start">
            ${IonBackButton()}
          </ion-buttons>
          <ion-title>My Page</ion-title>
        </ion-toolbar>
      </ion-header>

      <ion-content class="ion-padding">
        <p>${() => this.data.value ?? "Loading..."}</p>

        <ion-button @click=${() => router.navigate("/other")}>
          Go somewhere
        </ion-button>
      </ion-content>
    `;
  }
}

Register it in main.ts:

{ path: "/my/:id", component: (ctx) => new MyPage(ctx) },

Build for production

# Web PWA
npm run build
# Output in dist/ — deploy to any static host (Vercel, Netlify, etc.)

# Android APK / AAB
npm run build
npx cap sync android
npx cap open android
# In Android Studio: Build → Generate Signed Bundle/APK

# iOS IPA
npm run build
npx cap sync ios
npx cap open ios
# In Xcode: Product → Archive

License

MIT