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

v0.15.6

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.

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: generates a resolve.alias entry for every installed @sigx/* package and every one of its exports subpaths, each pinned to that package's built entry, so the whole family resolves to one physical copy. Subpath entries are emitted before bare ones — Vite matches aliases by prefix, so a bare @sigx/resume ahead of @sigx/resume/client would rewrite the subpath into a nonexistent path. 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.

    If your config already aliases a package, the plugin leaves that package entirely alone (all of its entries or none — a partially overridden set is worse than none, since a bare key ahead of its own subpaths breaks them). 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.
  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: resolve manifest entries into DocumentOptions.assets
import { collectAssets } from '@sigx/vite/assets';
const assets = collectAssets(manifest, ['index.html']);

@sigx/vite/assets imports nothing — no node: builtins — and its one process.env read is typeof-guarded, so a workerd/Deno/Bun entry (where process may not exist at all) can use it directly. Import it from /assets, not /ssr: the latter also carries the dev request handler, which does import node:fs/promises and node:path, and pulling that into an edge graph is not possible. @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,
  handler: async (rq, id: string) => 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,
    resolve: (symbol) => serverFns[symbol]?.() ?? null
  });
}

Options

| Option | Type | Default | What it does | |---|---|---|---| | include | string \| string[] | ['**/*.server.ts', '**/*.server.tsx'] | 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, baked with stable symbols, and no registry is emitted — there is no server in this build. | | 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.

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).

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), manifest, islandsManifest, and resumeManifest as inlined literals — no filesystem 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, islandsManifest, resumeManifest } = await import(
    new URL('./dist/server/sigx-app.js', import.meta.url).href
);

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/