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

@shift-stack/core

v0.1.0

Published

SHiFT: Svelte + Hypermedia + islands + eFfect + TypeScript. Server-rendered Svelte on Effect, swapped by HTMX, with hydrated islands.

Readme

SHiFT

Svelte + Hypermedia + islands + eFfect + TypeScript.

Pages are Svelte components rendered on the server by an Effect HTTP app. HTMX swaps them in without full reloads. The interactive parts are Svelte islands: rendered on the server with the page, then hydrated in place in the browser, so they show from the first paint and work before any JavaScript arrives.

npm install @shift-stack/core effect @effect/platform @effect/platform-node svelte vite

Four entry points

The code runs in three places that don't share modules, plus the build:

| Import | Runs | What it gives you | | ------------------------------- | ------------------------------------------ | ----------------------------------------------------------- | | @shift-stack/core/server | Node (Effect) | responses for pages and fragments, HTMX headers, error pages, serve | | @shift-stack/core/views | inside Vite, on the server | createViews, Island | | @shift-stack/core/client | the browser | start: HTMX and island hydration | | @shift-stack/core/vite | vite.config.ts | the shift plugin |

@shift-stack/core itself holds the constants all four agree on (contentTarget, layoutHeader, refreshEvent) and the types that describe an app to SHiFT (ViewContext, FrontendConfig, …). It has no side effects, so islands can import it. @shift-stack/core/attributes is types only: import it once from a .d.ts of the app, and views may use hx-* attributes on any element.

Setting up an app

1. Say where the frontend lives. One object, shared by Vite and the server:

// src/frontend/config.ts
export const frontend = {
  views: "src/frontend/views/index.ts",
  client: "src/frontend/islands/entry.ts",
  styles: "src/frontend/styles/app.css"
}
// vite.config.ts
import { svelte } from "@sveltejs/vite-plugin-svelte"
import { shift } from "@shift-stack/core/vite"
import { frontend } from "./src/frontend/config.ts"

export default { plugins: [svelte(), shift(frontend)] }

2. The views. The pages, the layouts they render in (each with an element of id contentTarget), and the islands:

// src/frontend/views/index.ts
import { createViews } from "@shift-stack/core/views"
import Home from "./pages/Home.svelte"
import NotFound from "./pages/NotFound.svelte"
import Row from "./components/Row.svelte"
import Layout from "./Layout.svelte"
import TopBar from "./TopBar.svelte"

const pages = { home: Home, notFound: NotFound }
const fragments = { row: Row }
export type Pages = typeof pages
export type Fragments = typeof fragments

export default createViews({
  pages,
  fragments,
  islands: import.meta.glob("../islands/*.svelte", { eager: true }),
  layouts: {
    main: {
      component: Layout,                     // gets the props below, plus `page` and `pageProps`
      props: (page, context) => ({ title: page.name }),
      outOfBand: [TopBar]                    // sent along with every page swap, with `oob: true`
    },
    bare: { component: Bare }                // no bars: just the content target and the page
  },
  defaultLayout: "main",
  pageLayouts: { notFound: "bare" }
})
<!-- Layout.svelte -->
<script lang="ts">
  import { contentTarget, Island } from "@shift-stack/core/views"
  let { title, page: Page, pageProps } = $props()
</script>

<TopBar {title} />
<main id={contentTarget}><Page {...pageProps} /></main>
<Island name="Toaster" props={{ position: "top" }} />

3. The browser entry.

// src/frontend/islands/entry.ts
import { start } from "@shift-stack/core/client"

start({ islands: import.meta.glob("./*.svelte", { eager: true }) })

4. The server. Routes are a plain HttpRouter; SHiFT wraps it, from the inside out:

import { HttpRouter } from "@effect/platform"
import { clientAssets, errorPages, responders } from "@shift-stack/core/server"
import type { Fragments, Pages } from "../frontend/views/index.ts"
import { frontend } from "../frontend/config.ts"

const { page, fragment } = responders<Pages, Fragments>()

const routes = HttpRouter.empty.pipe(HttpRouter.get("/", page("home", { greeting: "Hi" })))

export const app = routes.pipe(
  errorPages({ notFound: page("notFound", {}, 404), serverError: page("notFound", {}, 500) }),
  clientAssets(frontend)
)
// dev.ts: Vite serves the browser code and reloads the views on every request
import { serve } from "@shift-stack/core/server"
import { frontendDev } from "@shift-stack/core/server/dev"
serve(app, frontendDev(frontend))

// prod.ts: after `vite build && vite build --ssr prod.ts --outDir dist/server`
import { Layer } from "effect"
import { assetsFromManifest, serve, viewsBundled } from "@shift-stack/core/server"
serve(app, Layer.merge(viewsBundled(() => import("../frontend/views/index.ts")), assetsFromManifest(frontend)))

page answers a boosted navigation with the page alone, plus the layout's out-of-band parts. Anything else (a first load, a reload, a history restore) gets the whole document. fragment renders a piece HTMX swaps on its own.

Layouts

Pages can render in different layouts, for example an error page without the app's bars. The browser tells the server which layout it shows (the Shift-Layout header, from <body data-layout>). A navigation within one layout swaps only the page. A navigation into another layout gets the whole document, and the client swaps it in for the whole body (HX-Retarget: body), so the bars leave and come back as they should. The islands in the old body are unmounted and the new ones hydrated. Every layout must render the content target, which links keep aiming at.

Responding to HTMX

  • htmlResponse(html, status?): HTML that varies on the HTMX headers.
  • trigger({ event: data }): raise events in the browser (response.pipe(trigger({ toast }))).
  • navigateTo(path): navigate like a boosted link would.
  • refresh: fetch the current page again, in place.
  • href(path): a link as the current request should see it (a plugin may add a locale prefix).

Plugins

Server code and views run in different module graphs (in development the views run inside Vite), so a plugin comes in two halves:

  • ServerPlugin: a middleware, (app) => app, piped in after errorPages so the error pages get what it provides. It can answer on its own, rewrite the request, set cookies, add view data (withViewData) and change links (Links).
  • ViewPlugin: { around, htmlAttributes, head }. around wraps every render for per-request state, and the other two add to the document. It reads what the server half put into context.data.

@shift-stack/paraglide is one: localized URLs with Paraglide JS.

Islands

An island is a Svelte component in the app's islands folder; SHiFT ships none. Place one with <Island name="File" props={…} />. The props travel as JSON, so they must be JSON-safe. Islands render on the server too, so nothing may touch document or HTMX at import time.

An island is not a small app. It owns what happens in the browser while the user is doing it (a sheet sliding open, a drag, a gesture, a filter while typing). What the island shows and what it sends are the server's HTML: its slots. Props are only for how it behaves, not for the application's data.

Slots

Snippets passed to <Island> are its slots, the children and any named ones:

<!-- A view (rendered on the server) -->
<Island name="Sheet" props={{ title: "New List" }}>
  {#snippet trigger()}<Plus />New List{/snippet}
  <form hx-post="/lists" hx-target="this" hx-swap="outerHTML">…</form>
</Island>
<!-- islands/Sheet.svelte: places the slots, knows nothing of what is in them -->
<script lang="ts">
  let { title, trigger, children } = $props()
</script>
<Drawer.Root><Drawer.Trigger>{@render trigger()}</Drawer.Trigger><Drawer.Content>{@render children()}</Drawer.Content></Drawer.Root>
  • The island gets each slot as a snippet that renders the server's HTML, wrapped in one <shift-slot> laid out as if it weren't there. It can put the slot anywhere, show it later or several times (a sheet renders it each time it opens).
  • Svelte owns the island, HTMX owns inside its slots. Whenever a slot is put into the page, HTMX processes it and the islands inside it are mounted; when the island takes it out, those are unmounted. Target elements inside the slot, never the <shift-slot> itself.
  • The client keeps each slot as a <template> in the placeholder. What HTMX swaps into a slot is copied back into it, so the server's answer (a saved setting, a form with its errors) is what the slot shows the next time.
  • To send something, an island doesn't build requests: the slot holds a form, and the island fills in a field and calls form.requestSubmit().

Lifecycle

start hydrates every island HTMX loads, and unmounts it exactly once: when HTMX removes it, or when the slot it sits in is taken out.

One page at a time: a new request for a page (a link, back, a form that navigates, refresh) cancels the page request still on its way, so a slow answer never lands on top of a newer page. Requests that swap a piece of the page are left alone.

Tests in a browser

shift/e2e is a small app built on SHiFT, served as in production on port 4310, with Playwright tests of what only a browser shows: hydration, navigation and back, crossing layouts, slots and the islands in them, swaps, overlapping requests. Every island there records when it mounts and unmounts, so a test sees a leak as one island too many. pnpm e2e from the root.