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

route-ui-sync

v0.1.2

Published

A lightweight, framework-agnostic utility for automatically matching routes to menu keys. Zero dependencies, TypeScript support, for navigation menus.

Downloads

33

Readme

route-ui-sync

一个与 UI 框架无关的工具库,用于根据当前路由信息计算选中 key。

应用场景

适用于菜单选中、面包屑导航、标签页激活、侧边栏高亮等需要根据路由状态决定 UI 状态的场景,处理路由和 UI 状态不匹配的问题。

特性

  • 🎯 框架无关:不依赖 React、Vue 或任何路由库
  • 🚀 极轻量级:零依赖,压缩后仅 ~314 字节(ES 模块)或 ~321 字节(CommonJS)
  • 🔧 类型安全:完整的 TypeScript 类型定义
  • 测试覆盖:完整的单元测试
  • 📦 多格式支持:同时支持 ES 模块和 CommonJS
  • 高度优化:使用 Terser 进行激进压缩,生产环境不包含 sourcemap

安装

npm install route-ui-sync

快速开始

3 步完成菜单自动选中

// 1. 导入函数和类型
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
import { useLocation } from 'react-router-dom';

// 2. 定义规则:哪些路由对应哪些菜单 key
const rules: RouteSelectionRule[] = [
  { key: 'page1', match: (loc) => loc.pathname === '/page1' },
  { key: 'page2', match: (loc) => loc.pathname === '/page2' },
];

// 3. 获取当前路由,自动计算选中的 key
const location = useLocation();
const selectedKey = getSelectedKeyFromLocation(location, rules);

// 使用 selectedKey 来高亮菜单
<Menu selectedKeys={selectedKey ? [selectedKey] : []} />

使用方法

基本用法

import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';

// 定义路由匹配规则
const rules: RouteSelectionRule[] = [
  {
    key: 'page1',
    match: (location) => location.pathname === '/page1',
  },
  {
    key: 'page2',
    match: (location) => location.pathname === '/page2',
  },
  {
    key: 'items',
    match: (location) => location.pathname.startsWith('/items'),
  },
];

// 根据当前 location 计算选中的 key
const location = { pathname: '/page1' };
const selectedKey = getSelectedKeyFromLocation(location, rules);
console.log(selectedKey); // 'page1'

使用优先级

当多个规则同时匹配时,可以使用 priority 属性来控制优先级:

const rules: RouteSelectionRule[] = [
  {
    key: 'item-detail',
    match: (location) => /^\/items\/\d+$/.test(location.pathname),
    priority: 10, // 高优先级
  },
  {
    key: 'items',
    match: (location) => location.pathname.startsWith('/items'),
    priority: 5, // 低优先级
  },
];

const location = { pathname: '/items/123' };
const selectedKey = getSelectedKeyFromLocation(location, rules);
console.log(selectedKey); // 'item-detail' (因为优先级更高)

处理查询参数和哈希

const rules: RouteSelectionRule[] = [
  {
    key: 'search',
    match: (location) => {
      return location.pathname === '/search' && location.search?.includes('q=');
    },
  },
  {
    key: 'section',
    match: (location) => location.hash === '#section1',
  },
];

const location = {
  pathname: '/search',
  search: '?q=test',
  hash: '#section1',
};
const selectedKey = getSelectedKeyFromLocation(location, rules);

与 React Router 集成

基础示例

import { useLocation } from 'react-router-dom';
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
import { Menu } from '@arco-design/web-react';

function NavigationMenu() {
  const location = useLocation();
  
  // 定义菜单选中规则
  const rules: RouteSelectionRule[] = [
    {
      key: 'page1',
      match: (loc) => loc.pathname === '/page1',
    },
    {
      key: 'page2',
      match: (loc) => loc.pathname === '/page2',
    },
    {
      key: 'items',
      match: (loc) => loc.pathname.startsWith('/items'),
    },
  ];
  
  // 根据当前路由自动计算选中的菜单项
  const selectedKey = getSelectedKeyFromLocation(location, rules);
  
  return (
    <Menu selectedKeys={selectedKey ? [selectedKey] : []}>
      <Menu.Item key="page1">页面一</Menu.Item>
      <Menu.Item key="page2">页面二</Menu.Item>
      <Menu.Item key="items">项目列表</Menu.Item>
    </Menu>
  );
}

实际项目示例:动态菜单规则

在实际项目中,你可能需要根据动态数据(如从 API 获取的产品分类)来生成规则:

import { useLocation } from 'react-router-dom';
import { useMemo } from 'react';
import { getSelectedKeyFromLocation, RouteSelectionRule } from 'route-ui-sync';
import { Menu } from '@arco-design/web-react';

interface Category {
  categoryId: string;
  name: string;
}

function NavigationMenu() {
  const location = useLocation();
  const [categories, setCategories] = useState<Category[]>([]);
  
  // 从 API 获取分类
  useEffect(() => {
    fetchCategories().then(setCategories);
  }, []);
  
  // 动态生成菜单选中规则
  const menuSelectionRules: RouteSelectionRule[] = useMemo(() => {
    const rules: RouteSelectionRule[] = [
      {
        key: 'page1',
        match: (loc) => loc.pathname === '/page1',
      },
      {
        key: 'page2',
        match: (loc) => loc.pathname === '/page2',
      },
    ];
    
    // 根据分类动态添加规则
    // 例如:/item?type=category1 对应 item-1
    categories.forEach((cat, index) => {
      const key = `item-${index + 1}`;
      rules.push({
        key,
        match: (loc) => {
          if (loc.pathname !== '/item') return false;
          const params = new URLSearchParams(loc.search ?? '');
          return params.get('type') === cat.categoryId;
        },
      });
    });
    
    return rules;
  }, [categories]);
  
  // 自动计算当前选中的菜单项
  const selectedMenuKey = useMemo(
    () => getSelectedKeyFromLocation(location, menuSelectionRules),
    [location, menuSelectionRules]
  );
  
  return (
    <Menu selectedKeys={selectedMenuKey ? [selectedMenuKey] : []}>
      <Menu.Item key="page1">页面一</Menu.Item>
      <Menu.Item key="page2">页面二</Menu.Item>
      {categories.map((cat, index) => (
        <Menu.Item key={`item-${index + 1}`}>
          {cat.name}
        </Menu.Item>
      ))}
    </Menu>
  );
}

使用优先级处理复杂场景

当路由有嵌套关系时,使用优先级确保精确匹配:

const rules: RouteSelectionRule[] = [
  // 精确匹配:项目详情页
  {
    key: 'item-detail',
    match: (loc) => /^\/items\/\d+$/.test(loc.pathname),
    priority: 10, // 高优先级
  },
  // 模糊匹配:项目列表页
  {
    key: 'items',
    match: (loc) => loc.pathname.startsWith('/items'),
    priority: 5, // 低优先级
  },
];

// 访问 /items/123 时,会选中 'item-detail'(优先级更高)
// 访问 /items 时,会选中 'items'

API

getSelectedKeyFromLocation

根据当前路由信息和一组规则,计算"当前应该选中的 key"。

参数

  • location: 包含 pathnamesearch(可选)、hash(可选)的对象
  • rules: 路由选择规则数组

返回值

  • string | undefined: 匹配的 key,如果没有匹配则返回 undefined

RouteSelectionRule

路由选择规则接口。

interface RouteSelectionRule {
  /**
   * 唯一 key,用于传给 UI 组件
   */
  key: string;
  
  /**
   * 匹配函数:根据当前 location 判断这个规则是否命中
   */
  match: RouteMatchPredicate;
  
  /**
   * 优先级:数字越大优先级越高(可选)
   * 当多个规则同时命中时,用它来决定最终选中哪个
   */
  priority?: number;
}

RouteMatchPredicate

匹配函数类型。

type RouteMatchPredicate = (location: {
  pathname: string;
  search?: string;
  hash?: string;
}) => boolean;

开发

构建

npm run build

测试

项目使用 Vitest 进行单元测试,包括源代码测试和打包后代码测试。

# 运行源代码测试
npm test

# 监听模式运行测试
npm run test:watch

# 运行测试并生成覆盖率报告
npm run test:coverage

# 测试打包后的代码(自动构建并测试)
npm run test:bundle

# 运行所有测试(源代码 + 打包后代码)
npm run test:all

类型检查

npm run type-check

测试覆盖

项目包含完整的单元测试,覆盖了各种使用场景:

  • 测试用例数: 39 个(源代码测试)+ 22 个(打包后代码测试)= 61 个测试用例
  • 代码覆盖率: 100%
  • 测试场景: 包括基础功能、复杂场景、边界情况、实际项目场景等
  • 打包验证: 自动测试打包后的 ES 模块和 CommonJS 模块功能

打包后代码测试说明:

test:bundle 命令会:

  1. 自动构建项目(npm run build
  2. 测试打包后的 ES 模块(dist/index.js
  3. 测试打包后的 CommonJS 模块(dist/index.cjs
  4. 验证打包文件大小和完整性

这确保了打包后的代码功能与源代码完全一致,验证了压缩和优化过程没有破坏功能。

查看 TEST_COVERAGE.md 了解详细的测试覆盖情况。

更多示例

查看 USAGE_EXAMPLES.md 获取更多详细的使用示例,包括:

  • 基础使用场景
  • React Router 集成
  • 动态规则生成
  • 优先级控制
  • 处理查询参数
  • 完整项目示例

许可证

MIT