Skip to content

Instantly share code, notes, and snippets.

@tannerlinsley
Created December 29, 2021 16:11
Show Gist options
  • Save tannerlinsley/b4285e52727aca94b8fd1a44b71fa4b4 to your computer and use it in GitHub Desktop.
Save tannerlinsley/b4285e52727aca94b8fd1a44b71fa4b4 to your computer and use it in GitHub Desktop.

Revisions

  1. tannerlinsley created this gist Dec 29, 2021.
    110 changes: 110 additions & 0 deletions treeFromPathMap.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,110 @@
    import { multiSortBy } from 'src/shared/utils'

    export type PathNode<T> = {
    id: string
    path: string
    parentId: string
    meta: T
    children?: PathNode<T>[]
    }

    export function treeFromPathMap<TMeta>(
    rootMeta: TMeta,
    flatRouteDefs: Record<string, TMeta>
    ): [PathNode<TMeta>, PathNode<TMeta>[], Record<string, PathNode<TMeta>>] {
    // We need to go from a flat pathMap to a tree-like structure:

    // {
    // index: index.js
    // a: a.js
    // b: b.js
    // c: c.js
    // c/index: c/index.js
    // c/d: c/d.js
    // }

    // Becomes

    // {
    // path: '/',
    // meta: 'index.js',
    // children: [{
    // path: 'a',
    // meta: 'a.js'
    // }, {
    // path: 'b',
    // meta: 'b.js'
    // }, {
    // path: 'c',
    // meta: 'c.js',
    // children: [{
    // path: '/',
    // meta: 'c/index.js'
    // }, {
    // path: 'd',
    // meta: 'c/d.js'
    // }]
    // }]
    // }

    // Make sure the paths are in order from root to leaf
    let sortedRouteDefs = multiSortBy(Object.keys(flatRouteDefs), [
    (d) => d.split('/').length,
    (d) => (d.endsWith('index') ? -1 : 1),
    (d) => d,
    ]).map((id) => {
    return {
    id,
    file: flatRouteDefs[id],
    }
    })

    // The root node
    let root: PathNode<TMeta> = {
    id: 'root',
    path: '/',
    parentId: '',
    meta: rootMeta,
    children: [],
    }

    // A flat list of the final node objects
    let flatRoutes: PathNode<TMeta>[] = [root]

    sortedRouteDefs.forEach(({ id, file }) => {
    // Reduce each node's full path to build the sub-tre
    const parts = id === '/' ? ['/'] : id.split('/')

    parts.reduce((parent, pathPart) => {
    const found = parent.children?.find((d) => d.path === pathPart)

    if (found) {
    return found
    }

    parent.children = parent.children ?? []

    const resolvedPath = pathPart === 'index' ? '/' : pathPart

    const pathNode: PathNode<TMeta> = {
    path: resolvedPath,
    id: [parent.id, pathPart].join('/'),
    parentId: parent.id,
    meta: file,
    }

    flatRoutes.push(pathNode)
    parent.children.push(pathNode)

    return pathNode
    }, root)
    })

    //
    const nodesByPath = flatRoutes.filter(Boolean).reduce((obj, node) => {
    obj[node.path] = node
    return obj
    }, {} as Record<string, PathNode<TMeta>>)

    return [root, flatRoutes, nodesByPath]
    }