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

@sv-kit/keep-route

v1.0.0

Published

Route keep-alive for SvelteKit: per-route state snapshots, scroll restoration, include/exclude and LRU.

Readme

@sv-kit/keep-route

SvelteKit 的“路由级保活(Keep-Alive)”插件:缓存每个路由的组件状态快照滚动位置,切路由来回不丢表单、不丢分页、不丢滚动。支持 include/excludeLRU 上限本地持久化,零第三方依赖。🧠💾

  • 🔁 状态保活:给需要保活的组件打上 use:keepState,自动保存/恢复其快照
  • 🧭 滚动恢复:同一路由返回时自动回到离开前的滚动位置
  • 🎯 精确控制include / exclude 按路由 glob 匹配
  • 🧳 LRU 缓存:只保留最近使用的 N 个路由,防止内存炸掉
  • 🗂️ 可持久化:可选写入 localStorage,刷新后仍可恢复
  • 🧩 零侵入:不改编译产物、不克隆 DOM,组件按原样运行

这不是 Vue 的 vnode 级 <KeepAlive>,而是数据层的“状态快照”方案,更贴合 Svelte 的编译模型,稳定、可控、易调试。


安装

pnpm add @sv-kit/keep-route
# or npm i / yarn add

快速开始(两步走)

1) 在 +layout.svelte 里接线 SvelteKit 导航事件

<script lang="ts">
  import { createKeepRoute, wireSvelteKit, makeKeepState } from '@sv-kit/keep-route';
  import { page } from '$app/stores';
  import { beforeNavigate, afterNavigate } from '$app/navigation';

  // 1) 创建 keep 实例
  const keep = createKeepRoute({
    include: ['/dashboard/**', '/search/**', '/detail/**'], // 需要保活的路由
    exclude: ['/login/**'],                                  // 不保活的路由
    max: 12,                  // LRU 上限
    scroll: true,             // 是否恢复滚动
    persistKey: 'kr'          // 可选:持久化键前缀
  });

  // 2) 接线(挂到 SvelteKit 的导航事件)
  wireSvelteKit(keep, { page, beforeNavigate, afterNavigate });

  // 3) 导出一个 action,给页面/组件使用
  export const keepState = makeKeepState(keep);
</script>

<slot />

SvelteKit v2 兼容说明 wireSvelteKit 内部会自动从事件对象的 e.from?.url / e.to?.url 提取 URL,同时兼容旧形态(直接传 URL)。你无需自己做类型转换。

2) 在页面或组件上声明“我需要被保活的状态”

<script lang="ts">
  import { keepState } from '../+layout.svelte'; // 从布局导出的 action
  let q = '';          // 搜索词
  let pageNo = 1;      // 分页页码

  function snap() {         // 序列化自身状态(必须可 JSON 化)
    return { q, pageNo };
  }
  function hydrate(s: any) {// 反序列化恢复
    if (!s) return;
    q = s.q ?? q;
    pageNo = s.pageNo ?? pageNo;
  }
</script>

<!-- 你可以把 action 绑在任意一个“承载该状态”的顶层元素上 -->
<div use:keepState={{ id: 'searchForm', get: snap, set: hydrate }}>
  <input bind:value={q} placeholder="关键词" />
  <button on:click={() => pageNo = 1}>搜索</button>
  <!-- ...列表与分页... -->
</div>

切到别的路由再返回,q/pageNo自动复活;回退也会回到原滚动位置。🎯


核心概念

RouteKey(路由键)

默认用 pathname + search,例如 /search?q=abc&page=2。 需要自定义可在 wireSvelteKit(keep, { key(url){...} }) 里覆盖。

Keeper(保活单元)

use:keepState({ id, get, set }) 会注册一个 keeper:

  • id:同一路由内应唯一(不传会自动分配)
  • get():返回可 JSON 化的快照
  • set(v):把快照恢复回组件状态

LRU

只缓存最近 max 个路由(默认 10)。超过就淘汰“最久未使用”的路由缓存。

滚动恢复

默认启用。导航返回时在下一帧 window.scrollTo(x,y),尽量与 SvelteKit 内置滚动行为协调。

持久化(可选)

传入 persistKey 会把每个路由的 {state, scroll} 存到 localStorage。刷新后仍能恢复。


API

import {
  createKeepRoute, wireSvelteKit, makeKeepState,
  type KeepRouteOptions, type KeepInstance, type KeepStateParam, type RouteKey
} from '@sv-kit/keep-route';

createKeepRoute(options?: KeepRouteOptions): KeepInstance

Options

  • include?: string[]:生效白名单(glob),默认 ['/**']
  • exclude?: string[]:排除名单(glob),默认 []
  • max?: number:LRU 上限,默认 10
  • scroll?: boolean:是否恢复滚动,默认 true
  • persistKey?: string:持久化前缀,例如 'kr'

KeepInstance

  • register(k: { id, get, set }): () => void:注册 keeper(通常通过 makeKeepState 封装)
  • onBeforeNavigate(routeKey: RouteKey) / onAfterNavigate(routeKey: RouteKey):导航钩子(由 wireSvelteKit 代调用)
  • save(routeKey) / restore(routeKey):手动保存/恢复
  • current: Readable<RouteKey | null>:当前命中的路由键(调试用)

wireSvelteKit(keep, wiring)

把 keep 实例接到 SvelteKit(v2 事件形态):

wireSvelteKit(keep, {
  page,                  // $app/stores -> page
  beforeNavigate,        // $app/navigation
  afterNavigate,         // $app/navigation
  key?: (url: URL) => string // 可自定义路由键(默认 pathname+search)
});

类型提示(说明用)

type Wiring = {
  page: import('svelte/store').Readable<{ url: URL }>;
  // SvelteKit v2 的事件;库内会从 e.from?.url / e.to?.url 提取 URL
  beforeNavigate: (cb: (e: import('@sveltejs/kit').BeforeNavigate) => void) => void;
  afterNavigate:  (cb: (e: import('@sveltejs/kit').AfterNavigate)  => void) => void;
  key?: (url: URL) => RouteKey;
};

makeKeepState(keep): Action<HTMLElement, KeepStateParam>

返回一个 Svelte Action(use:keepState={...}

  • KeepStateParam
    • id?: string:唯一标识,不传自动生成
    • get: () => any:序列化快照(必须可 JSON 化)
    • set: (v:any) => void:恢复快照

路由匹配:glob 规则

  • /**:任意多层目录
  • *:一层内任意字符(不含 /

示例:

include: ['/search/**', '/detail/*']
exclude: ['/login/**', '/admin/**']

进阶用法

自定义路由键(忽略查询参数)

wireSvelteKit(keep, {
  page, beforeNavigate, afterNavigate,
  key: (u) => u.pathname // 仅按 path 缓存
});

多个 keeper(复杂页面拆分)

<div use:keepState={{ id:'filters', get: getFilters, set: setFilters }} />
<div use:keepState={{ id:'table',   get: getTable,   set: setTable   }} />

同一路由下各 keeper 互不影响。

手动保存/恢复(很少需要)

keep.save('/search?q=abc');    // 强制保存
keep.restore('/search?q=abc'); // 强制恢复

与 SSR 的关系

本插件只在浏览器运行(依赖导航事件与 window.scrollTo)。SSR 无需特殊处理;如启用 persistKey,刷新后也能恢复。


实战建议

  • 快照要小:只存 UI 状态(表单、分页、选中项、折叠状态…),避免把大数组整段写入本地持久化。
  • 表单组件最受益:搜索页、筛选页、表格分页;回退立即还原,用户好感度+++。
  • 滚动冲突:若你自定义了 SvelteKit 的 handleScroll,可把 scroll:false 交由你统一管理。
  • LRU 调大有代价:注意内存占用;建议 10–20 之间权衡。

FAQ

Q:为啥不做成 Vue 那种 <KeepAlive> A:Svelte 的编译模型不暴露 vnode 层拦截点,DOM 克隆/劫持不稳定,且副作用多。数据层快照更稳、更透明、可测试。

Q:刷新页面还能恢复吗? A:给 createKeepRoutepersistKey,状态与滚动会写入 localStorage。刷新后同一路由会恢复。

Q:内存会不会爆? A:有 LRU。超过 max 就自动淘汰最久未使用的路由缓存。

Q:同一路由的不同查询要不要区分? A:默认 区分pathname+search);可自定义 key 只按 pathname

Q:导航事件类型不匹配,TS 报错? A:升级到本版(内置 v2 兼容),并按本文的 wireSvelteKit 用法接线即可;库内会自动从 e.from?.url / e.to?.url 提取 URL,无需你转型。

Q:组件卸载/重新挂载时如何避免冲突? A:use:keepState 在组件销毁时自动注销;同一路由 id 要保持唯一。


兼容性 & 许可

  • Svelte 4 / 5
  • 浏览器支持:现代浏览器(需要 MutationObserver / localStorage 若启用持久化)
  • License:MIT

变更日志 & 路线图

  • v0.1.0:首发,状态快照 + 滚动恢复 + include/exclude + LRU + 持久化
  • v0.1.1:SvelteKit v2 导航事件类型兼容(自动识别 e.from?.url / e.to?.url),初始订阅时自动做一次 restore
  • 计划:按模块导出 Dev 面板(可视化当前缓存路由/快照大小)、每路由限速持久化、跨标签同步

示例样式(可选)

/* 仅示意:让恢复滚动时更顺滑 */
html { scroll-behavior: smooth; }