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

@vue-spark/router-plugins

v2.0.2

Published

Lightweight Vue Router 4 plugin collection - extend routing capabilities with minimal overhead.

Readme

@vue-spark/router-plugins

轻量级 Vue Router 4 插件集合——以最小开销扩展路由能力。

v1.0.0 起,插件基于 vue-router-plugin-system 开发(弃用旧插件注册方式),详见其文档。

English Document

在线示例

安装

npm i @vue-spark/router-plugins

插件注册

方式一:

import ScrollerPlugin from '@vue-spark/router-plugins/scroller'

// 初始化插件并安装
ScrollerPlugin({
  selectors: ['.scrollable'],
}).install(router)

方式二:

import ScrollerPlugin from '@vue-spark/router-plugins/scroller'

createApp(App)
  // 先注册路由
  .use(router)
  // 再注册插件
  .use(
    ScrollerPlugin({
      selectors: ['.scrollable'],
    }),
  )

方式三:

import ScrollerPlugin from '@vue-spark/router-plugins/scroller'
import { createWebHistory } from 'vue-router'
import { createRouter } from 'vue-router-plugin-system'

const router = createRouter({
  history: createWebHistory(),
  routes: [],
  plugins: [
    // 初始化插件
    ScrollerPlugin({
      selectors: ['.scrollable'],
    }),
  ],
})

插件列表

HistoryStatePlugin

用于在浏览器导航(前进/后退)时传递和恢复状态数据。

<!-- list.vue -->
<script
  setup
  lang="ts"
>
  import { shallowRef, onActivated } from 'vue'
  import { useRouter } from 'vue-router'
  import type { PageState as ListDetailPageState } from './detail.vue'

  interface Item {}

  const list = shallowRef<Item[]>([])
  const fetchList = async () => {
    fetch('/api/list').then((res) => {
      list.value = res.json()
    })
  }

  // 立即请求
  fetchList()

  const router = useRouter()
  const listDetailPageState = router.historyState<ListDetailPageState>('/list/detail')

  onActivated(() => {
    listDetailPageState.withTake(({ outgoing }) => {
      // 在需要时重新请求列表
      outgoing?.refreshList && fetchList()
    })
  })
</script>

<template>
  <button @click="$router.push('/list/detail')">新增</button>
  <ul>
    <li v-for="item in list">{{ item }}</li>
  </ul>
</template>
<!-- detail.vue -->
<script
  setup
  lang="ts"
>
  import { useRouter } from 'vue-router'

  export interface PageState {
    outgoing?: {
      refreshList?: boolean
    }
  }

  const router = useRouter()
  const pageState = router.historyState<PageState>('/list/detail')

  function handleBack() {
    // 设置延迟状态数据
    pageState.setDeferred({ outgoing: { refreshList: true } })

    router.back()
  }
</script>

<template>
  <button @click="handleBack">返回</button>
</template>

类型定义

interface HistoryStateManager<State extends {} = {}> {
  /**
   * 等同于 `router.options.history.state`
   */
  readonly raw: VueRouter.HistoryState
  /**
   * 状态所属的命名空间名称
   */
  readonly namespace: string

  /**
   * 设置状态数据,会立即同步到 `router.options.history.state`,数据仅支持浅拷贝
   *
   * **注意:当设置的数据无法被 `history.state` 结构化克隆({@link structuredClone})时,`vue-router` 会自动重置页面!**
   */
  set: (state: NoInfer<State>) => void

  /**
   * 仅设置内存里的状态数据,不会被更新到 `router.options.history.state`
   *
   * **注意:虽然该函数不会更新 `history.state`,但是仍然不推荐设置无法被其结构化克隆({@link structuredClone})的数据!**
   */
  setMemory: HistoryStateManager<NoInfer<State>>['set']

  /**
   * 延迟设置状态数据,会等待 `router` 下次导航成功时再同步到 `router.options.history.state`,
   * 可以在导航前多次调用,延迟设置的状态数据会放入缓冲区,无论下次导航成功或失败都会重置缓冲区
   *
   * **注意:当设置的数据无法被 `history.state` 结构化克隆({@link structuredClone})时,`vue-router` 会自动重置页面!**
   */
  setDeferred: HistoryStateManager<NoInfer<State>>['set']
  /**
   * 取消延迟设置的状态数据,直接重置缓冲区
   */
  cancelDeferred: () => void
  /**
   * 立即同步缓冲区中的状态数据到 `router.options.history.state`
   */
  applyDeferred: () => void

  /**
   * 获取状态数据
   */
  get: () => NoInfer<State>
  /**
   * 获取状态数据后执行回调函数
   */
  withGet: <R = void>(cb: (state: NoInfer<State>) => R) => NoInfer<R>

  /**
   * 获取状态数据并删除原始缓存
   */
  take: () => NoInfer<State>
  /**
   * 获取状态数据并删除原始缓存后执行回调函数
   */
  withTake: <R = void>(cb: (state: NoInfer<State>) => R) => NoInfer<R>

  /**
   * 销毁当前命名空间的状态数据
   */
  destroy: () => void
}

interface Router {
  historyState: {
    <State extends {}>(namespace: string): HistoryStateManager<State>
    readonly raw: VueRouter.HistoryState
  }
}

NavigationDirectionPlugin

用于模拟移动端导航方向(前进/后退/刷新),辅助动画与缓存控制。

<!-- App.vue -->
<script
  setup
  lang="ts"
>
  import type { ResolveViewKey } from 'vue-router-better-view'
  import { ref, shallowReactive } from 'vue'
  import { useRouter } from 'vue-router'

  const router = useRouter()
  const transitionName = ref<string>()
  const keepAliveValues = shallowReactive(new Set<string>())

  const resolveViewKey: ResolveViewKey = (route) => {
    return route.meta.title ? route.fullPath : null
  }

  router.navigationDirection.listen((direction, to, from) => {
    switch (direction) {
      case 'forward': {
        transitionName.value = 'page-in'
        keepAliveValues.add(to.fullPath)
        break
      }
      case 'backward': {
        transitionName.value = 'page-out'
        keepAliveValues.delete(from.fullPath)
        break
      }
      default: {
        transitionName.value = undefined
        keepAliveValues.delete(from.fullPath)
        keepAliveValues.add(to.fullPath)
        break
      }
    }
  })
</script>

<template>
  <BetterRouterView
    v-slot="{ Component: viewComponent, route }"
    :resolve-view-key
  >
    <Transition
      :name="transitionName"
      :css="!!transitionName"
    >
      <KeepAlive :include="[...keepAliveValues]">
        <Component
          :is="viewComponent"
          :key="route.fullPath"
        />
      </KeepAlive>
    </Transition>
  </BetterRouterView>
</template>

<style scoped>
  .page-in-enter-active,
  .page-in-leave-active,
  .page-out-enter-active,
  .page-out-leave-active {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: #fff;
    overflow: hidden;
    will-change: transform;
    transition:
      transform 0.3s linear,
      /* fix: 避免离场元素闪烁 */ opacity 0.31s;
  }

  .page-in-leave-to {
    /* fix: 避免离场元素闪烁 */
    opacity: 1;
  }
  .page-in-enter-from {
    z-index: 2;
    transform: translateX(100%);
  }
  .page-in-enter-to {
    z-index: 2;
  }

  .page-out-leave-from {
    z-index: 2;
  }
  .page-out-leave-to {
    z-index: 2;
    transform: translateX(100%);
  }
</style>

MemoryHistory 支持:在使用 createMemoryHistory() 时,由于 MemoryHistory 不提供浏览器历史记录的 delta 信息,插件会将所有导航操作(包括 pushreplace)都识别为前进方向。如果需要在 MemoryHistory 环境下自定义方向识别逻辑,可以通过 directionResolver 选项来实现:

const router = createRouter({
  history: createMemoryHistory(),
  routes,
  plugins: [
    NavigationDirectionPlugin({
      directionResolver: ({ to, from, delta }) => {
        // 自定义逻辑:根据路由变化判断方向
        if (to.path !== from.path) {
          // 可以根据具体业务逻辑判断是前进、后退还是替换
          return NavigationDirection.unchanged // 或其他逻辑
        }
        return delta > 0 ? NavigationDirection.forward : NavigationDirection.backward
      },
    }),
  ],
})

配置项

interface NavigationDirectionOptions {
  /**
   * 导航方向解析器
   */
  directionResolver?: NavigationDirectionResolver
}

类型定义

enum NavigationDirection {
  forward = 'forward',
  backward = 'backward',
  unchanged = 'unchanged',
}

interface INavigationDirection {
  /**
   * 当前导航方向,即最后一次导航方向
   */
  currentDirection: ShallowRef<NavigationDirection>
  /**
   * 设置下次导航方向,将会在下次导航成功时生效,导航失败时需要重新设置
   */
  setNextDirection: (direction: NavigationDirection) => void
  /**
   * 监听导航方向变化,会在 `onScopeDispose` 时自动移除回调
   */
  listen: (callback: NavigationDirectionCallback) => () => void
}

interface Router {
  navigationDirection: INavigationDirection
}

ScrollerPlugin

自动保存并恢复滚动位置,适用于长页面或列表页。

// router/index.ts
import ScrollerPlugin from '@vue-spark/router-plugins/scroller'
import { createWebHistory } from 'vue-router'
import { createRouter } from 'vue-router-plugin-system'

const router = createRouter({
  history: createWebHistory(),
  routes: [],
  plugins: [
    ScrollerPlugin({
      // 设置滚动目标
      selectors: ['.scrollable'],
    }),
  ],
})

在搭配 <Transition> 组件使用时,需要在其 after-enter 事件时执行 router.scroller.trigger() 手动进行当前路由的滚动位置还原。

<template>
  <RouterView v-slot="{ Component: viewComponent }">
    <Transition @after-enter="$router.scroller.trigger()">
      <Component :is="viewComponent" />
    </Transition>
  </RouterView>
</template>

配置项

interface ScrollerOptions {
  /**
   * 滚动行为
   */
  behavior?: ScrollBehavior
  /**
   * 滚动元素选择器,支持特殊选择器 `window` 和 `document`
   */
  selectors: string[]
  /**
   * 滚动位置处理函数
   * - 返回 `true` 时使用记录的滚动位置
   * - 返回假值时时跳过本次滚动
   * - 返回 `ScrollPositionCoordinates` 时使用自定义滚动位置
   */
  scrollHandler?: ScrollHandler
}

类型定义

interface ScrollPositionCoordinates {
  left?: number
  top?: number
}

type ScrollPositionCoordinatesGroup = Record<string, ScrollPositionCoordinates>

interface Scroller {
  /**
   * 滚动位置记录
   */
  positionsMap: ShallowReactive<Map<string, ScrollPositionCoordinatesGroup>>
  /**
   * 手动触发当前路由的滚动位置还原,适用于 `Transition` 组件动画结束后进行调用
   */
  trigger: () => void
}

interface Router {
  scroller: Scroller
}

IsNavigatingPlugin

用于检测当前是否处于导航状态,适用于过渡动画或加载提示。

<template>
  <div :class="{ 'is-navigating': $router.isNavigating.value }">
    <RouterView />
  </div>
</template>

类型定义

interface Router {
  isNavigating: ShallowRef<boolean>
}

PreviousRoutePlugin

记录上一个访问的路由信息,适合需要根据来源做逻辑判断的场景。

const router = createRouter({...})

const originalBack = router.back
router.back = () => {
  const currentRoute = router.currentRoute.value
  const previousRoute = router.previousRoute.value
  // 遇到前置路由为根路由且当前路由不是 TabBar 页面时,返回 TabBar 页面
  if (previousRoute && previousRoute.fullPath === '/' && !currentRoute.meta.isTabBar) {
    // 设置下次导航方向,用于变更导航动画
    router.navigationDirection.setNextDirection(NavigationDirection.backward)
    router.replace('/tab-bar')
    return
  }
  originalBack()
}

类型定义

interface PreviousRoute
  extends Readonly<
    Pick<VueRouter.RouteLocationNormalizedLoaded, 'name' | 'path' | 'fullPath' | 'hash'>
  > {}

interface Router {
  previousRoute: ShallowRef<PreviousRoute | undefined>
}

迁移指南

v1.x 迁移至 v2.x

  • ScrollerPlugin
    • scrollOnlyBackward 选项移除,使用 scrollHandler 处理滚动恢复。
    • selectors 选项改为 string[]
  • HistoryStatePlugin
    • HistoryStateManager 类型变更为 HistoryStateManager<State extends {} = {}>
    • router.historyState 不再支持默认命名空间。
    • setsetMemorysetDeferredgettake 函数不再支持通过 key 设置/获取单个属性。
    • setsetMemorysetDeferredgettake 函数类型变更,详见类型定义。

v0.x 迁移至 v1.x

  • 移除全部插件注册快捷方式,仅支持按需导入需要注册的插件。
  • 插件基于 vue-router-plugin-system 开发(弃用旧插件注册方式),详见其文档。
  • 单一插件导入路径改为 @vue-spark/router-plugins/[plugin-name]
  • ScrollerPlugin 配置项 selectors 移除默认值并改为必填项。