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

@sigx/vite

v1.0.1

Published

Vite plugin for SignalX Framework with HMR, library builds, and automatic type generation

Readme

@sigx/vite

Vite plugin for SignalX — wires up dev-mode source aliasing, HMR for component(), and ships a small sigx-types CLI that generates TypeScript definitions for tag-named components.

📚 Full guides, API reference and live examples → https://sigx.dev/vite/

Install

npm install -D @sigx/vite

@sigx/vite peer-depends on vite >= 8 and sigx (a caret on the published version, ^1.0.0 from 1.0 — the app installs the runtime once and owns the copy, docs/rfc-1.0.md §3). The strategy and server packs (@sigx/server-renderer, @sigx/ssr-islands, @sigx/resume, @sigx/server) are optional peers on the same range.

Usage

Add the plugin to your vite.config.ts:

import { defineConfig } from 'vite';
import sigx from '@sigx/vite';

export default defineConfig({
  plugins: [sigx()],
});

That's it — the plugin handles the rest. Its job is keeping @sigx/reactivity a single module instance in every environment (two instances mean signals written through one never trigger effects tracked by the other — silently dead UI):

  • Dev: pins every installed @sigx/* package and every one of its exports subpaths to that package's built entry, so the whole family resolves to one physical copy. The pin is a resolver step (a resolveId hook), not a set of resolve.alias entries: an import such as @sigx/resume/client or @sigx/zero-basic/css?url is split into the specifier and its ?query / #fragment, the specifier is looked up, and Vite's own resolver finishes with the postfix intact — so ?url / ?raw / ?inline / ?worker imports of a subpath export work like any other file (#655), and a subpath with no literal exports entry (a wildcard such as ./themes/*) is resolved per import. It also excludes all @sigx/* packages from optimizeDeps pre-bundling — the core packages plus every @sigx/* dependency found in your package.json (store, router, daisyui, …), so prebundled chunks can't carry a second reactivity copy.

    Your own resolve.alias entries always take precedence, in any form — exact key, a scope or prefix such as '@sigx': '/my/fork', a RegExp, even a key on the queried form ('@sigx/x/css?url'): Vite applies aliases before any plugin's resolveId runs, and whatever your alias rewrites an import to is no longer a sigx specifier when it reaches the pin. Your alias map itself is passed through unchanged, so later plugins see exactly what you wrote. You should not need a hand-written map; if you do, that's a bug worth reporting.

  • SSR: sets ssr.noExternal: ['sigx', /^@sigx\//] so the whole family stays in the SSR module graph instead of splitting between Vite's module runner and Node's resolver.

  • Build: dedupes the core packages and pins the runtime into one shared sigx chunk.

Your own optimizeDeps.exclude / ssr.noExternal entries are merged with the plugin's, never replaced.

Options

sigx({
  // Enable HMR for component() (default: true). Also drives the dev
  // full-reload for server-only pages: a zero-JS / resumable route never
  // loads its components in the browser, so an edit has no client HMR
  // boundary — the plugin reloads the page instead so the change shows.
  // (A server-only module that only server FUNCTIONS depend on is the
  // exception: with sigxServer() installed it refreshes in place — see
  // "Dev HMR — what refreshes in place".)
  hmr: true,

  // Port for Vite's HMR websocket. Only relevant in middleware mode (the
  // standard SSR setup), where Vite's fixed default (24678) collides when
  // two dev servers run on one machine. Unset: the plugin picks a free port
  // automatically. Explicit `server.hmr` settings in your Vite config always
  // take precedence.
  hmrPort: undefined,

  // SSR mode: ONE `vite build --app` produces the client bundle (with its
  // asset manifest) into dist/client AND the server entry into dist/server —
  // shaped by `adapter` (default nodeAdapter(): dependencies external, one
  // module graph with the production request handler; see "Deployment
  // artifacts" below).
  ssr: { entry: 'src/entry-server.tsx' },
})

SSR mode

The dev server is createServer plus one handler; production is static assets plus one handler (@sigx/server-renderer/node). The entry contract: export createApp(url) returning a fresh per-request app (docs/router-ssr-contract.md).

// dev
import { createDevRequestHandler } from '@sigx/vite/ssr';
app.use(vite.middlewares);
app.use(await createDevRequestHandler(vite, { entry: '/src/entry-server.tsx' }));

// prod: the template and the manifest resolver come from the build itself
const { template, assets, assetsFor } = await import('./dist/server/sigx-app.js');
assets;                                          // every entry, precomputed
assetsFor(['src/sections/TechDetails.tsx']);     // per route (docs/router-ssr-contract.md §2)

@sigx/vite is a build tool and a devDependency — the running production server never imports it. A server that did await import('@sigx/vite/ssr') in production booted only while dev dependencies happened to be installed; npm ci --omit=dev or a slim Docker layer breaks it (#501). Everything the document side needs is emitted by the build as virtual:sigx-app / dist/server/sigx-app.js — template, assets, manifest, the pack manifests, and assetsFor(entries, base?), which is collectAssets with the resolver body inlined, so the module imports nothing on any platform.

@sigx/vite/assets remains the lower-level escape hatch — collectAssets over a manifest you hold yourself, for build-time and dev tooling. It imports nothing — no node: builtins — and its one process.env read is typeof-guarded. Import it from /assets, not /ssr: the latter also carries the dev request handler, which does import node:fs/promises and node:path. @sigx/vite/ssr still re-exports it, so existing imports keep working.

Styles in dev

There is no manifest in dev, and Vite serves JS-imported CSS (import './styles.css') as a module that injects a <style> at runtime — so a server-rendered document would carry no styles in its head and paint unstyled until the client entry executes.

createDevRequestHandler closes that gap: it walks the SSR module graph and inlines the reachable CSS into <head> as <style data-vite-dev-id="…">, the shape Vite's client adopts on boot and rewrites in place on HMR — so there is no flash, no duplicated rules, and CSS HMR is unaffected. Nothing to configure; production is untouched (the built template carries real <link> tags).

Pass devStyles: false to opt out if your template already ships its own stylesheet link.

Server functions — sigxServer()

sigxServer() (from @sigx/vite/server) is the build half of @sigx/server. A *.server.ts module is server-only wholesale: the SSR build keeps its body, the client build replaces the entire module with generated RPC stubs, so a server-only import can never reach the browser.

// vite.config.ts
import sigx from '@sigx/vite';
import { sigxServer } from '@sigx/vite/server';

export default defineConfig({
  plugins: [sigx({ ssr: { entry: 'src/entry-server.tsx' } }), sigxServer()]
});
// src/api.server.ts — never shipped to the browser
export const getProduct = serverFn({
  allowAnonymous: true,
  input: z.string(),
  handler: async ({ input: id }) => db.get(id)
});
// Product.tsx — a normal import; the client gets a stub that POSTs
import { getProduct } from './api.server';
const product = await getProduct('sku-1');

Dev needs no wiring: the plugin serves the endpoint from vite.middlewares, which every example mounts already. Production reads the build's registry — virtual:sigx-server-fns, emitted as dist/server/sigx-server-fns.js (or inlined in a bundled build) — and your entry passes it explicitly, never ambiently:

import { handleServerFnRequest, matchesServerFn } from '@sigx/server/server';
import { serverFns, serverFnBase } from 'virtual:sigx-server-fns';

if (matchesServerFn(request, serverFnBase)) {
  return handleServerFnRequest(request, {
    base: serverFnBase,
    functions: serverFns
  });
}

Options

| Option | Type | Default | What it does | |---|---|---|---| | include | string \| string[] | ['**/*.server.ts', '**/*.server.tsx', '**/*.server.mts', '**/*.server.js', '**/*.server.mjs'] | Which modules are server modules. | | exclude | string \| string[] | ['**/node_modules/**', '**/dist/**'] | Excluded from matching. | | base | string | '/_sigx/fn' | The server mount path — the dev middleware's and createServerFnHandler's prefix. Exported back to your entry as serverFnBase; pass it to matchesServerFn and the handler so all three agree. | | endpoint | string | base | The fetch target baked into stubs; an absolute URL for a build that calls a remote server. Call-time precedence: configureServerFn > this > base. | | role | 'auto' \| 'client' | 'auto' | 'auto' swaps stubs in the Vite client environment only. 'client' declares the whole build a remote-server client (lynx, terminal): every environment gets stubs and no registry is emitted — there is no server in this build. (Stub identity is the same stable key under every role.) | | scan | string[] | [] | Extra directories scanned for server modules — shared workspace packages outside the Vite root. | | serverApp | string | — | The app's server-app module ('/src/server-app.ts') — it calls createServerApp(...) at module scope (rfc-server-v4 §3.4). Dev loads it eagerly through the SSR module runner and re-evaluates it after edits, so middleware/authentication/authorization/posture apply to the dev endpoint AND in-process SSR calls without a restart; a production build injects one side-effect import of it at the top of virtual:sigx-server-fns. Without it the fail-closed runtime denies rather than opens. | | requireAuthorization | boolean \| 'warn' | true | The access gate (rfc-server-v4 §5): every extracted serverFn/serverStream must have a decided access policy — declare authorize: [...], declare the literal allowAnonymous: true, or inherit the app default via a configured serverApp. A bare one with no app is a build error naming its file, line and the remedies. 'warn' lists them without failing; false opts out deliberately. | | renderBoundaries | string | — | A Vite-root-relative module exporting renderBoundaries — the value createBoundaryRefresh (@sigx/resume/server) builds for production entries — forwarded to the dev endpoint so single-flight boundary refresh behaves identically in dev. | | origin | 'same-origin' \| 'verify-when-present' \| string[] \| false | 'same-origin' | Origin policy forwarded to the dev endpoint. | | maxBodyBytes | number | 1_048_576 | Body cap forwarded to the dev endpoint. | | maxUrlBytes | number | 8_192 | A GET read's query-string cap, forwarded to the dev endpoint. | | timeoutMs | number | — | Forwarded to the dev endpoint. | | onError | (error, info, ctx) => void | — | Forwarded to the dev endpoint. |

The last five come from ServerFnRequestOptions by inheritance rather than by being copied, so an option added to the endpoint is reachable in dev the day it ships. serverApp and renderBoundaries differ from their production twins by necessity: they are module specifiers here, loaded through the SSR module runner so edits apply without a restart, where a production entry imports the modules itself. Everything about what those values mean lives in @sigx/server.

Inline server functions

A serverFn declared at module scope of any client-reachable file is extracted in place: the client build replaces the initializer with a stub and strips imports used only inside extracted bodies, so a server-only dependency never loads in the browser (dev included — there is no tree-shaking there). Captured free variables are a hard build error: the rule is imports-only, and the message tells you to pass the value as an argument.

Dev HMR — what refreshes in place

The backend half of a server function is live in vite dev by construction: every call resolves its module through the SSR module runner at request time, so the next RPC after a save runs the new body — no restart. Since #716 the browser half is live too:

| You save… | An open page that reads it (useData(fn)) | An open page that does not | |---|---|---| | a *.server.ts module, or an inline serverFn carrier | refetches the affected useData cells in place — no reload, client state kept; the old stubs it holds send the new version tag, so nothing 409s | reloads, unless it never loaded the stub and has nothing to show for it | | a server-only module they import (db.ts behind api.server.ts) | same — the edit is routed to the stubs behind it by walking the SSR graph | reloads (the pre-#716 behaviour, the sigx() server-only full reload) | | a server-only module a rendered component imports too | reloads — the document may have changed | reloads |

Mechanically: every client stub module self-accepts in dev and, when Vite re-evaluates it, hands its function keys to serverFnHotUpdate (@sigx/vite/hmr), which sweeps the SSR data blob and refreshes every mounted cell on those keys — through the @sigx/cache seam when that pack is installed, so its entries drop too. A page with no live reader for the keys hands the update back to Vite (import.meta.hot.invalidate()): an accepting component module re-runs, a non-accepting importer (a resume handler chunk) means the full reload the page got before. And because Vite's module graph is per server while its HMR reaches only pages that hold the module, the plugin also broadcasts sigx:server-fn-update to every connected page; a small listener it injects into each dev document reloads unless a loaded stub for the changed source claims the event.

Known limits, on purpose: server-rendered HTML that used a function with no live reader on the page (static islands content, an in-process call in the SSR entry) stays until a reload; a serverApp middleware edit keeps the full reload (the server side re-evaluates either way); and module-level state in a server module (let votes = 3) resets when the module re-evaluates — exactly as a restart would.

Extraction outside Vite — @sigx/vite/server-extract

Non-Vite bundlers (a lynx app on Rspack, say) reach the same analysis directly: @sigx/vite/server-extract exports extractServerFns, extractInlineServerFns, mintSymbols and computeStableId with no Vite plugin around them, so every client of one solution mints identical stable symbols.

Resumability — sigxResume()

sigxResume() (from @sigx/vite/resume) completes the @sigx/resume story: event handlers in resume modules (*.resume.tsx, or anything under resume/) are extracted at build time into lazily-imported QRL chunks, signal state is keyed from its declaration (const hits = ctx.signal(0) is keyed "hits" — named = transferred), and the client build emits .vite/sigx-resume-manifest.json for resumePlugin({ manifest }) on the server. With sigxServer() present it also stamps real action/method attributes onto a <form> whose submit handler calls a form: true server function — the zero-JS transport (rfc-server §6.4).

Three transform-time contract violations are build errors (rfc-1.0 §4.5), not warnings: a component reachable only as export default (resume components must be named exports), the same component name exported from two resume modules (the error names both files), and a handler that binds or references $scope / $el. The full contract is in @sigx/resume's README, "Writing resumable components".

Under vite dev the ladder is the same as prod: the transform runs for the dev SSR render, the handlers modules are served as virtual:sigx-resume:… modules, the registry lazily imports the component modules, and no manifest is needed. The original on* prop is kept in every environment — the hydrated listener is what dispatches after an upgrade. An edit that changes a handler symbol (they are content-hashed) reloads the page, since the rendered attributes and the evaluated registry hold the old names; a markup-only edit keeps in-place HMR.

Deployment artifacts — ssr.adapter and virtual:sigx-app

The build seam of the deployment RFC (docs/rfc-deploy.md §3). ssr.adapter is a plain SigxAdapter object — default nodeAdapter(), which keeps today's externalized Node output byte-identical:

sigx({ ssr: { entry: 'src/entry-server.tsx', adapter: nodeAdapter() } })

serverBuild: 'external' resolves deps from node_modules at runtime (Node / Bun hosts); serverBuild: 'bundled' produces a fully self-contained server build (resolve.noExternal: true, platform conditions — a REPLACEMENT array, so node is present only if the adapter lists it — target esnext, runtimeExternal for platform-scheme imports). Binary on purpose: partially-external is the dangerous middle ground for DI-token identity. Build ordering is explicit: client → ssr → remaining environments → adapter.generate(ctx) (which sees both finished output trees). Adapters may also hook the dev server via dev(server) — dev stays createDevRequestHandler on every platform.

The document-side artifacts become code: virtual:sigx-app exports template, assets (precomputed collectAssets over every entry), manifest, islandsManifest, and resumeManifest as inlined literals, plus assetsFor(entries, base?) — the same resolver over the inlined manifest, with its body baked in — for per-route preloads (#501). No filesystem and no @sigx/vite in the output. External builds also materialize it as dist/server/sigx-app.js (imports of the virtual resolve to that emitted sibling), so a Node server.mjs collapses from four readFiles to one import:

const { template, assets, assetsFor, islandsManifest, resumeManifest } = await import(
    new URL('./dist/server/sigx-app.js', import.meta.url).href
);
document: (url) => ({ assets: assetsFor(ROUTE_MODULES[url] ?? []) })

Bundled builds inline the module instead — one self-contained file is the deliverable. In dev the virtual throws (dev has no manifests; createDevRequestHandler resolves template/assets live). Combining ssr.adapter with sigxServer({ role: 'client' }) is a config-time error — a client-role build has no server for an adapter to shape.

The narrow sibling virtual:sigx-manifests exports just islandsManifest and resumeManifest, and — unlike virtual:sigx-app — resolves in EVERY mode: real inlined literals in the SSR build, undefined under dev (the packs run manifest-less there). It exists for the entry-server's app factory, the pack install site (#413):

// src/entry-server.tsx
import { islandsManifest } from 'virtual:sigx-manifests';
export const createApp = (url) =>
    defineApp(<App />).use(islandsPlugin({ manifest: islandsManifest }));

Typing the virtual modules — @sigx/vite/client

One reference line, next to vite/client, types every virtual:* module the sigx plugins generate — virtual:sigx-app, virtual:sigx-manifests, virtual:sigx-server-fns, virtual:sigx-islands, virtual:sigx-resume/entry:

// src/env.d.ts
/// <reference types="vite/client" />
/// <reference types="@sigx/vite/client" />

The two pack manifests type themselves from the packs you actually installed: importing @sigx/ssr-islands gives islandsManifest its IslandsManifestV2, importing @sigx/resume gives resumeManifest its ResumeManifest, and a manifest whose pack is absent stays unknown — which is what the value is anyway, since a pack that is not installed contributes no manifest. That is why this file never imports the packs: both are optional peers, and an app with only one of them installed still has to type-check. Registration rides the pack's own import, exactly like the client:* and use: attribute types.

Islands

sigxIslands() (from @sigx/vite/islands) completes the @sigx/ssr-islands story: island modules (*.island.tsx or anything under islands/) get stable __islandId identities and automatic signal state keys (const state = ctx.signal(…) is keyed "state" from the declaration — named = transferred, per island instance), virtual:sigx-islands registers a lazy code-split loader per island in the client entry, and the client build emits .vite/sigx-islands-manifest.json for islandsPlugin({ manifest }) on the server.

📚 Documentation

Plugin options, HMR, the sigx-types CLI, TSX setup and subpath exports — full guides, the complete reference and live examples → https://sigx.dev/vite/