Last active
July 20, 2020 14:31
-
-
Save mikeplus64/29f4ed6564d1a229f4f10593f39e1c7c to your computer and use it in GitHub Desktop.
router5 + typescript + effort
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { UnionToIntersection, DeepWritable } from 'ts-essentials'; | |
import { State as RouteState } from 'router5'; | |
import { Switch, createState, createEffect, createMemo } from 'solid-js'; | |
import useRoute, { MatchRoute } from './context'; | |
import { Routes } from './definition'; | |
export type RenderTreeOf<Tree> = | |
Owned<Tree> | RenderNode & | |
UnionToIntersection< | |
Tree extends readonly (infer Node)[] | |
? Node extends { name: infer Name, children?: infer Children } | |
? Name extends (string | number | symbol) | |
? Children extends {} | |
? { [K in Name]?: RenderTreeOf<Children> } | |
: { [K in Name]?: Owned<Children> | RenderNode } | |
: never | |
: never | |
: never | |
>; | |
interface RenderNode { | |
render?(props: { children: JSX.Element }): JSX.Element, | |
fallback?(props: { children: JSX.Element }): JSX.Element, | |
}; | |
export type OwnedBy<Tree, Props> = | |
UnionToIntersection< | |
Tree extends readonly (infer Node)[] | |
? Node extends { name: infer Name, children?: infer Children } | |
? Name extends (string | number | symbol) | |
? Children extends {} | |
? { [K in Name]?: GetProps<Props> & OwnedBy<Children, Props> } | |
: { [K in Name]?: GetProps<Props> } | |
: never | |
: never | |
: never | |
>; | |
interface OwnedOps<Tree, Props> { | |
render: (props: Props) => JSX.Element, | |
defaultProps: Props, | |
props: OwnedBy<Tree, Props>, | |
} | |
type Owned<Tree> = | |
<R>(cont: <Props>(self: OwnedOps<Tree, Props>) => R) => R; | |
type GetProps<Props> = | |
{ [K in keyof Props]?: () => Props[K] }; | |
export function passthru<T>(props: { children: T }): T { | |
return props.children; | |
} | |
export default function sm<R extends RenderTreeLike>(tree: R): JSX.Element { | |
const route = useRoute(); | |
function traverseHydrate<Props>( | |
path0: string[], | |
node0: GetPropsLike<Props>, | |
render: (props: Props) => JSX.Element, | |
defaultProps: Props, | |
): JSX.Element { | |
const [state, setState] = createState(defaultProps); | |
function populate( | |
path: string[], | |
node: GetPropsLike<Props>, | |
next: Partial<Props>, | |
count: number, | |
): number { | |
for (const key in node) { | |
const gp = (node as GetProps<Props>)[key as keyof Props]; | |
if (typeof gp === 'function') { | |
const value = gp(); | |
next[key as keyof Props] = value; | |
count ++; | |
continue; | |
} | |
if (gp !== undefined) { | |
if (path[0] === key) { | |
return populate(path.slice(1), gp as any, next, count); | |
} | |
} | |
} | |
return count; | |
} | |
const getPathSuffix = createMemo<[string, string[]]>(() => { | |
const name = route().name; | |
const p = name.split('.'); | |
p.splice(0, path0.length); | |
return [name, p]; | |
}, undefined, (a, b) => a && a[0] === b[0]); | |
createEffect(() => { | |
const next: Partial<Props> = {}; | |
if (populate(getPathSuffix()[1], node0, next, 0) > 0) { | |
setState(next); | |
} | |
}); | |
return render(state as Props); | |
} | |
function traverse( | |
path: string[], | |
node: RenderTreeLike, | |
): JSX.Element { | |
if (typeof node === 'function') { | |
return node(function <Props>(owned: OwnedOpsLike<Props>) { | |
const { props, render, defaultProps } = owned; | |
return traverseHydrate(path, props, render, defaultProps); | |
}); | |
} | |
const children: JSX.Element = []; | |
let { render: Render, fallback, ...routes } = node; | |
if (Render === undefined) { Render = passthru; } | |
if (typeof Render !== 'function') { return undefined; } | |
for (const key in routes) { | |
const next = [...path, key]; | |
const child = (routes as any)[key]; | |
children.push(MatchRoute({ | |
prefix: key, | |
children: () => traverse(next, child), | |
})); | |
} | |
return Render({ | |
children: Switch({ | |
fallback: typeof fallback === 'function' ? fallback({ children }) : undefined, | |
children, | |
}), | |
}); | |
} | |
return traverse([], tree); | |
} | |
type GetPropsLike<Props> = | |
{ [k: string]: GetPropsLike<Props> } & GetProps<Props>; | |
interface OwnedOpsLike<Props> { | |
render: (props: Props) => JSX.Element, | |
defaultProps: Props, | |
props: GetPropsLike<Props> | |
} | |
type OwnedLike = | |
(cont: <Props>(self: OwnedOpsLike<Props>) => any) => any; | |
export type RenderTreeLike | |
= OwnedLike | |
| (RenderNode & { [k: string]: RenderTreeLike }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment