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

@xcwds/sveltekit

v0.5.1

Published

Binds the @xcwds kernel to SvelteKit: a Vite plugin that sets up SvelteKit, server hooks, the <App> component, Svelte 5 persist() and reactive settings, and a route registry.

Downloads

250

Readme

@xcwds/sveltekit

Binds the @xcwds kernel to SvelteKit: config loading, plugin entries for the page and the service worker, the pre-paint script, the manifest and icons, and Svelte 5 helpers. Design: RFC 0001, decisions 1 to 4 and 9.

Setup

SvelteKit 3 only (no svelte.config.js): xcwds() takes SvelteKit's config and adds SvelteKit's own Vite plugin, so don't add sveltekit() as well.

// vite.config.ts
import { xcwds } from '@xcwds/sveltekit/vite';
import { defineConfig } from 'vite';

export default defineConfig({ plugins: [xcwds({ paths: { base: '' } })] });
// tsconfig.json: .xcwds/types.d.ts tells TypeScript what the plugins add
{
	"extends": "$app/tsconfig",
	"include": ["src", "*", ".xcwds/types.d.ts"],
	"exclude": ["src/service-worker.ts"]
}
// src/hooks.server.ts
export { handle, init } from '@xcwds/sveltekit/hooks';
// src/hooks.client.ts
export { init } from '@xcwds/sveltekit/hooks';
// src/service-worker.ts
import '../.xcwds/worker.js';
// src/routes/+layout.ts
export const prerender = true;
<!-- src/routes/+layout.svelte -->
<script>
	import { App } from '@xcwds/sveltekit';
	let { children } = $props();
</script>

<App>{@render children()}</App>

src/app.html needs %xcwds.head% right after %sveltekit.head% (see decision 4). Add .xcwds to .gitignore if your tools don't read the one generated inside it.

From SvelteKit 2 (0.4 and earlier)

Move the kit options from svelte.config.js into xcwds() and delete the file: withXcwds({ kit: { paths } }, { adapter }) becomes xcwds({ paths }, { adapter }), and plugins: [xcwds(), sveltekit()] becomes plugins: [xcwds({ paths })]. Then follow SvelteKit 3's own migration: the tsconfig extends $app/tsconfig (add .xcwds/types.d.ts to its include, which withXcwds() used to add for you), $app/environment is $app/env, and $lib is gone (add "imports": { "#lib/*": "./src/lib/*" } to package.json and import #lib/name.js, with the extension).

What each part does

  • xcwds(config, { adapter, root }) loads xcwds.config.ts (or .js) with Vite's runnerImport, validates it, loads each plugin's build export and runs its build hooks (onConfig, onManifest, onHead, onWorker, onPrerender), and writes .xcwds/worker.js and .xcwds/types.d.ts (a type-only import of each plugin's ./client entry). Then it creates SvelteKit's plugin with your config, adding adapter-static with a 404.html fallback (unless config.adapter is set), prerender entries for every registered route and onPrerender path, and a hash-mode CSP that allows only the app's origin plus the origins plugins declare (network metadata) and privacy.allowOrigins lists; your csp directives are added to it. Its adapter wrapper scans the built site and fails the build on any other origin it finds (docs/privacy.md). root defaults to the working directory.
  • The xcwds Vite plugin (the first one xcwds() returns) serves virtual:xcwds/client (each plugin's ./client entry, routes and head data) to the page bundle, and emits manifest.webmanifest and the icons rendered from brand.icon (with @xcwds/core/build, which needs @resvg/resvg-js). It adds the page entries of plugins installed in node_modules (and @xcwds/core, when the app depends on it) to optimizeDeps.include: only the generated module imports them, so Vite's dependency scan can't find them up front.
  • handle puts the manifest, icon and iOS tags and one pre-paint script (plugins' onHead snippets and settings' prePaint fields) in place of %xcwds.head%, and adds the script's hash to the page's CSP. init loads the plugins before the first render, so pages render and hydrate with their decorators, routes and settings fields.
  • .xcwds/worker.js first imports .xcwds/worker-guard.js, which limits the worker's fetch, WebSocket, EventSource, WebTransport, Cache#add/addAll and background fetches to the origins the page's CSP allows (@xcwds/sveltekit/guard), since that <meta> policy doesn't reach the worker. It then starts the worker from @xcwds/sveltekit/worker: plugins' onFetch hooks answer first; the build, static files, prerendered pages, manifest and icons are precached on install and served from this version's cache first; anything else goes to the network, with 404.html for offline navigations. The strategy reads app.worker.policy (extra precache paths, exclusions, the fallback page, runtime caching) once every worker plugin has registered; @xcwds/plugin-offline sets it. Nothing is cached at runtime unless a plugin turns it on. A new version waits until the old one's tabs have closed or a plugin calls app.worker.skipWaiting() (@xcwds/plugin-update does when the user taps Update). A static file with the same path as a generated one (manifest.webmanifest, icons/*, favicon.ico) fails the build, since it would replace the generated file.
  • <App> provides the app, boots it after mount (onBoot, then onReady), then marks <html data-hydrated> (what gotoHydrated in @xcwds/testing waits for), and turns navigations into onNavigate and afterNavigate, and page visibility into onHidden / onVisible. Vite HMR closes the old app.
  • @xcwds/sveltekit/routes is the framework-free core (no Svelte, SvelteKit or Vite imports) that <App>, the service worker and @xcwds/testing share, so they can't drift apart: the route registry (decorateRoutes), setupApp (create the app, add the routes, register the plugins), routeOf (the route hooks see for a URL), askGuards and decide (what guards' answers mean: cancel, redirect, the 5-redirect limit, the first page) and workerPath (the requests the worker handles).

Navigation guards

onNavigate(to, from) hooks run before every in-app navigation (links, goto, Back and Forward, and links to app paths SvelteKit has no route for). Returning false cancels it, a path (without the base) redirects there, and nothing lets it go ahead. Hooks that answer synchronously decide on the spot. If one returns a promise, the navigation is held and repeated once the hooks allow it: Back and Forward with history.go() (after SvelteKit has undone the held one), everything else with goto(url), so a guarded link loses goto options and data-sveltekit-* link options such as replaceState. Only the latest held navigation counts. A redirect runs the target's own guards, up to 5 redirects in a row; after that the navigation stops and the error goes to onError.

The first page is loaded rather than navigated to, so its guards run once the app has booted (with from null): a redirect replaces it in history, and false does nothing, since the page is already showing. Both are dropped if the user has navigated away by the time the guards answer.

Everything that builds a URL honours paths.base. Route paths, hook paths and the route registry never include it.

In components

import { appPath, brand, persist, routeInfo, settings, useApp } from '@xcwds/sveltekit';

const app = useApp(); // the kernel app, with every plugin's decorators
persist(
	app.timers.presets,
	() => presets,
	(v) => (presets = v)
); // loads after mount, saves on change
settings.current.theme; // defaults until `settings.ready`, then the saved values, reactively
routeInfo(page.url.pathname); // { title, emoji, parent, width } of the current page
appPath(page.url.pathname); // '/hello' under any base path (never relative), or null outside
brand.name; // and brand.tagline, from xcwds.config

Plugin packages

A plugin package exports up to three entries, each a kernel plugin:

// index.js: the config factory, and build hooks run in Node
export default descriptor('xcwds-plugin-hello');
export const build = definePlugin((app, options) => {
	app.route({ path: '/', title: 'Hello', emoji: '👋', parent: '/' }); // under its `prefix`
	app.addHook('onHead', () => 'document.documentElement.dataset.hello="1";');
});
// client.js (the page, and Node at prerender) and worker.js (the service worker)
export default definePlugin((app, options) => { ... });

Its pages are thin route files in the app that render a component the plugin exports (decision 2). A route is { path, title, emoji?, parent?, width?, private? }; private: true marks a personal page that plugins keep out of what they share or list. examples/plugin-hello has all three.

Dev, preview and static hosting

  • vite build writes a static site; serve it like GitHub Pages does (unknown paths get 404.html, which boots the app and shows +error.svelte).
  • vite dev renders pages on request: the CSP is a header, the manifest and icons come from memory, and there is no 404.html, so offline not-found pages only work in a build. SvelteKit registers the service worker in dev too, with empty precache lists.
  • vite preview serves the build output but renders unknown paths on the server instead of serving 404.html.