Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 7
version: 8
- uses: actions/setup-node@v3
with:
node-version: 16.x
node-version: 18.x
cache: "pnpm"

- run: pnpm install --frozen-lockfile
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 7
version: 8
- uses: actions/setup-node@v3
with:
node-version: 16.x
node-version: 18.x
cache: "pnpm"

- run: pnpm install --frozen-lockfile
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
},
"scripts": {
"demo": "pnpm run --filter @favy/router-demo dev",
"lint": "pnpm run -r lint",
"build": "pnpm run -r build",
"pub": "tsx ./bin/publish.mts"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@favy/wayfind": "0.0.2",
"@favy/wayfind-react": "0.0.1",
"@favy/wayfind": "workspace:*",
"@favy/wayfind-react": "workspace:*",
"scheduler": "^0.23.0",
"type-fest": "^4.4.0"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/demo/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createRoot } from "react-dom/client";
import { useIsActive, useNavigate, Link } from "./routerComponent";
import { router } from "./router";

// eslint-disable-next-line react-refresh/only-export-components
const Head = () => {
const isActiveRoot = useIsActive("/");
const go = useNavigate();
Expand All @@ -30,6 +31,7 @@ const Head = () => {
);
};

// eslint-disable-next-line react-refresh/only-export-components
function App() {
return (
<Router router={router}>
Expand Down
4 changes: 3 additions & 1 deletion packages/demo/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const root = route({
/>
</div>
),
"/logs/{page}": (ctx) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
"/logs/{page}": (ctx: any) => {
throw `random error ${ctx.page} ` + Date.now();
},
error: ({ error }) => <div style={{ color: "red" }}>Throw error: {`${error}`}</div>,
Expand All @@ -43,4 +44,5 @@ const root = route({
404: () => <div className='text-7xl text-red-400'>Not found</div>,
});

// eslint-disable-next-line react-refresh/only-export-components
export const router = createRouter(root);
4 changes: 3 additions & 1 deletion packages/wayfind-react/src/components/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ export const makeLink = (config: ConfProps["Link"] = { active: {}, default: {} }

const isActive = ctx.router?.isActive(url);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { to: _to, vars: _vars, ...restProps } = props as any;
const linkProps: any = {
...(config.default ?? {}),
...props,
...restProps,
};

if (isActive && config.active) {
Expand Down
2 changes: 1 addition & 1 deletion packages/wayfind-react/src/components/Redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const Redirect = <T extends string, V>({ to, vars }: ToRouteProps<T, V>)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
ctx?.go(to, vars);
});
}, [to, vars]);

return <></>;
};
2 changes: 1 addition & 1 deletion packages/wayfind-react/src/components/getComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface ConfProps {
}

export const getComponents = <T extends RouterInstance<C, any, any>, C extends RouteType<any, any>>(
router: T,
_router: T,
config: ConfProps
) => {
type RedirectProps = { [k in T["paths"]]: ToRouteProps<k, ExtractVars<k, string | number>> }[T["paths"]];
Expand Down
2 changes: 1 addition & 1 deletion packages/wayfind-react/src/hooks/useNavigate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const makeNavigateHook = <P>() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (props: P) => ctx?.go(props.url, props.vars);
return (props: P) => ctx?.go((props as any).to, (props as any).vars);
};

return useNavigate;
Expand Down
2 changes: 1 addition & 1 deletion packages/wayfind/src/Parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const parsers = {
number: (value: string) => Number(value),
bigint: (value: string) => BigInt(value),
string: (value: string) => value,
boolean: (value: string) => Boolean(value),
boolean: (value: string) => value === "true",
};

export type Parsers = {
Expand Down
54 changes: 32 additions & 22 deletions packages/wayfind/src/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ export const createRouter = <const C extends RouteType<any, any>>(config: C) =>
window.addEventListener("popstate", handleChange);
handleChange();

// eslint-disable-next-line @typescript-eslint/no-unused-vars
type RemoveType<S extends string> = S extends `${infer L}{${infer N}:${infer T}}${infer R}`
type RemoveType<S extends string> = S extends `${infer L}{${infer N}:${infer _T}}${infer R}`
? `${L}{${N}}${RemoveType<R>}`
: S;

Expand All @@ -59,6 +58,9 @@ export const createRouter = <const C extends RouteType<any, any>>(config: C) =>
isActive(path: string) {
return findRoute(routes, path)?.route === currentRoute?.route;
},
destroy() {
window.removeEventListener("popstate", handleChange);
},
} as any as RouterInstance<C, PO, Partial<UnionToIntersection<ExtractVars<PATHS>>>>;
};

Expand All @@ -78,27 +80,24 @@ export type RouterInstance<C extends RouteType<any, any>, P extends string, V> =
match(path: string): unknown;
isActive(path: string): boolean;
paths: P;
destroy(): void;
};

export const compileRoute = <C extends RouteType<any, any>>(route: C) => {
const routeMap: any = flatRoute(route, "");
const urls = Object.keys(routeMap);
let errorRender: any;

return urls.map((k) => {
const varTypes = new Map<string, string>();
const regString = k.replace(/[\s!#$()+,.:<=?[\\\]^|]/g, "\\$&").replace(/\{(.+?)\}/g, (_, name) => {
const errorRender = routeMap[k].error ?? findErrorRender(route, k);
const regString = k.replace(/[\s!#$()*+,.\-:<=?[\\\]^|]/g, "\\$&").replace(/\\\{(.+?)\\\}/g, (_, name) => {
const [varName, varType] = name.split("\\:");
varTypes.set(varName, varType);

return `(?<${varName}>.+)`;
return `(?<${varName}>[^/?#]+)`;
});
const regEx = new RegExp("^" + regString + "$", "gi");

if (routeMap[k].error) {
errorRender = routeMap[k].error;
}

return {
parse: (path: string) => {
const res = regEx.exec(path);
Expand All @@ -125,20 +124,26 @@ export const compileRoute = <C extends RouteType<any, any>>(route: C) => {
});
};

const findRoute = <R>(routes: R, path: string) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
for (const r of routes) {
const res = r.parse(path);
if (res) return res;
const findErrorRender = <T>(route: RouteType<T, any>, path: string): any => {
const segments = path.split("/").filter(Boolean);
let current: any = route;

for (const segment of segments) {
const key = "/" + segment;
if (current[key] && typeof current[key] === "object") {
if (current[key].error) return current[key].error;
current = current[key];
}
}

return (route as any).error;
};

export const isActive = <R>(routes: R, path: string) => {
const findRoute = <R>(routes: R, path: string) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
for (const r of routes) {
const res = r.isActive(path);
const res = r.parse(path);
if (res) return res;
}
};
Expand All @@ -149,10 +154,15 @@ const flatRoute = <T>(route: RouteType<T, any>, prefix = "", res: Record<string,
if (!key.startsWith("/")) continue;

const next = `${prefix === "/" ? "" : prefix}${key}`;
res[next] = route[key as any];

if (typeof route[key as any] === "object") {
flatRoute(route[key as any] as any, next, res);
const value = route[key as any];

if (typeof value === "function") {
res[next] = value;
} else if (typeof value === "object" && value !== null) {
if ((value as any).render) {
res[next] = value;
}
flatRoute(value as any, next, res);
}
}

Expand All @@ -163,5 +173,5 @@ export const applyVars = (path: string, vars: any) => {
if (!vars) return path;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return path.replace(/\{(.+?)\}/g, (_, name) => vars[name]);
return path.replace(/\{(.+?)\}/g, (_, name) => vars[name.split(":")[0]]);
};
2 changes: 1 addition & 1 deletion packages/wayfind/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./createRouter";
export * from "./ExtractVars";
export * from "./RouteType";
export * from "./ExtractVars";
export * from "./Path";
export * from "./route";
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2016",
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
Expand Down
Loading