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

@r2abreu/path-tree

v2.0.3

Published

Build hierarchical trees from flat arrays of path-based objects. Zero dependencies, TypeScript-first, framework-agnostic.

Readme

@r2abreu/path-tree

Build hierarchical trees from flat arrays of path-based objects. TypeScript-first, framework-agnostic.

Why?

You have flat data with paths:

[
  { path: '/docs/api/users' },
  { path: '/docs/guides/intro' },
  { path: '/blog/2024/post-1' }
]

You need a tree:

[
  {
    item: { path: '/docs' },
    children: [
      {
        item: { path: '/docs/api' },
        children: [
          { item: { path: '/docs/api/users' }, children: [] }
        ]
      },
      {
        item: { path: '/docs/guides' },
        children: [
          { item: { path: '/docs/guides/intro' }, children: [] }
        ]
      }
    ]
  },
  {
    item: { path: '/blog' },
    children: [
      {
        item: { path: '/blog/2024' },
        children: [
          { item: { path: '/blog/2024/post-1' }, children: [] }
        ]
      }
    ]
  }
]

Features

  • Zero dependencies - Lightweight and fast
  • TypeScript-first - Full type safety with generics
  • Framework-agnostic - Works with any JavaScript framework
  • Automatic parent creation - Missing intermediate paths are created automatically
  • Synthetic node marking - Clearly identifies generated vs. original nodes
  • Vue Router adapter - First-class support for Vue Router
  • Tree-shakeable - Only bundle what you use

Installation

npm install @r2abreu/path-tree

Quick Start

Generic Usage

import { buildPathTree } from '@r2abreu/path-tree';

const items = [
  { path: '/a/b/c', data: 1 },
  { path: '/a/b/d', data: 2 },
  { path: '/x/y', data: 3 }
];

const tree = buildPathTree(items);

Vue Router

import { useRouter } from 'vue-router';
import { buildRouteTree } from '@r2abreu/path-tree/vue-router';

const router = useRouter();
const tree = buildRouteTree(router.getRoutes());

Use Cases

1. Navigation Menus

<script setup lang="ts">
import { useRouter } from 'vue-router';
import { buildRouteTree, isRealRoute } from '@r2abreu/path-tree/vue-router';

const router = useRouter();
const routeTree = buildRouteTree(router.getRoutes());
</script>

<template>
  <nav>
    <ul>
      <li v-for="node in routeTree" :key="node.item.path">
        <router-link v-if="isRealRoute(node)" :to="node.item.path">
          {{ node.item.path }}
        </router-link>
        <span v-else class="parent">{{ node.item.path }}</span>
      </li>
    </ul>
  </nav>
</template>

2. File System Trees

import { buildPathTree } from '@r2abreu/path-tree';

const files = [
  { path: '/src/components/Button.vue', size: 1024 },
  { path: '/src/utils/helpers.ts', size: 512 },
  { path: '/tests/unit/Button.test.ts', size: 2048 }
];

const fileTree = buildPathTree(files);

3. API Endpoint Documentation

import { buildPathTree } from '@r2abreu/path-tree';

const endpoints = [
  { path: '/api/v1/users', method: 'GET' },
  { path: '/api/v1/users/:id', method: 'GET' },
  { path: '/api/v1/posts', method: 'POST' }
];

const apiTree = buildPathTree(endpoints);

4. Breadcrumbs

import { buildPathTree } from '@r2abreu/path-tree';

const pages = [
  { path: '/', title: 'Home' },
  { path: '/docs', title: 'Documentation' },
  { path: '/docs/api', title: 'API Reference' },
  { path: '/docs/api/users', title: 'Users API' }
];

const tree = buildPathTree(pages);
// Use tree to generate breadcrumb trail

API

Core

buildPathTree<T>(items, options?)

Build a hierarchical tree from a flat array of path-based items.

Parameters:

  • items: T[] - Array of items with path property
  • options?: BuildPathTreeOptions<T> - Optional configuration

Returns: TreeNode<T>[] - Array of root nodes

Example:

const tree = buildPathTree(items, {
  getPath: (item) => item.url,           // Custom path extractor
  createNode: (path) => ({ ... }),       // Custom node factory
  enrichItem: (item) => ({ ...item }),   // Transform items
  getParentPath: (path) => dirname(path) // Custom parent resolver
});

isRealNode<T>(node)

Check if a node is from the original input (not synthetic).

Example:

if (isRealNode(node)) {
  // This node was in the original input
}

filterRealNodes<T>(tree)

Filter tree to only include real nodes (removes synthetic parents).

Example:

const realOnly = filterRealNodes(tree);

Vue Router Adapter

buildRouteTree(routes, options?)

Build a hierarchical tree from Vue Router routes.

Parameters:

  • routes: RouteRecordNormalized[] - Vue Router routes
  • options?: BuildPathTreeOptions - Optional configuration

Returns: RouteTreeNode[] - Array of root route nodes

useRouteTree(routes, options?)

Vue composable for building route tree.

isRealRoute(node)

Check if a route node is real (not synthetic).

filterRealRoutes(tree)

Filter tree to only real routes.

Synthetic Nodes

The library automatically creates parent nodes for missing intermediate paths:

buildPathTree([{ path: '/a/b/c' }])
// Creates: /, /a, /a/b (all marked with synthetic: true)

When synthetic nodes are useful:

  • ✅ File systems (directories always exist)
  • ✅ Breadcrumbs (need complete path)
  • ✅ Navigation structure (visual hierarchy)

When to filter them out:

  • ❌ API endpoints (only real endpoints should be callable)
  • ❌ Strict routing (only defined routes should be clickable)

Identifying synthetic nodes:

import { isRealNode } from '@r2abreu/path-tree';

tree.forEach(node => {
  if (node.synthetic) {
    console.log('Synthetic:', node.item.path);
  }
  // or use the helper
  if (!isRealNode(node)) {
    console.log('Synthetic:', node.item.path);
  }
});

TypeScript

Fully typed with generics:

interface Page {
  path: string;
  title: string;
  meta?: Record<string, any>;
}

const tree: TreeNode<Page>[] = buildPathTree(pages);

Types

interface TreeNode<T> {
  item: T;
  children: TreeNode<T>[];
  synthetic?: boolean;
}

interface BuildPathTreeOptions<T> {
  getPath?: (item: T) => string;
  createNode?: (path: string) => TreeNode<T>;
  enrichItem?: (item: T) => T;
  getParentPath?: (path: string) => string;
}

Advanced Examples

Custom Path Property

interface CustomItem {
  path: string;
  url: string;
  title: string;
}

const items: CustomItem[] = [
  { path: '/custom', url: '/custom/path', title: 'Custom' }
];

const tree = buildPathTree(items, {
  getPath: (item) => item.url  // Use 'url' instead of 'path'
});

Enrich Items

const tree = buildPathTree(routes, {
  enrichItem: (route) => ({
    ...route,
    displayName: route.meta?.title || route.path,
    icon: route.meta?.icon
  })
});

Custom Parent Resolution

import { dirname } from 'pathe';

const tree = buildPathTree(items, {
  getParentPath: (path) => {
    // Custom logic for determining parent
    return dirname(path);
  }
});

Framework Examples

Vue 3

<script setup lang="ts">
import { buildRouteTree, isRealRoute } from 'path-tree/vue-router';
import { useRouter } from 'vue-router';

const router = useRouter();
const tree = buildRouteTree(router.getRoutes());
</script>

<template>
  <nav>
    <TreeNode :nodes="tree" />
  </nav>
</template>

React

import { buildPathTree } from '@r2abreu/path-tree';

function Navigation({ routes }) {
  const tree = buildPathTree(routes);
  
  return (
    <nav>
      {tree.map(node => (
        <TreeNode key={node.item.path} node={node} />
      ))}
    </nav>
  );
}

Svelte

<script>
  import { buildPathTree } from '@r2abreu/path-tree';
  
  export let routes;
  const tree = buildPathTree(routes);
</script>

<nav>
  {#each tree as node}
    <TreeNode {node} />
  {/each}
</nav>

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © r2abreu

Links


Glory to God