hannoeru / vite-plugin-pages

File system based route generator for ⚡️Vite
MIT License
1.84k stars 127 forks source link

[Feature] Typescript support for route path and route path params(Auto complete) #434

Closed niku98 closed 10 months ago

niku98 commented 1 year ago

Description

It's so good to have Typescript support for route path an path params, just like Tanstack Router 🔥

With this, everyone will have a better DX.

Suggested solution

  1. Instead of using a static declaration file, we should generate one.
  2. Wrapping react-router-dom/vue-router API to using types from generated declaration file. Then exports them.

Example:

// Generated declaration file

declare module '~react-pages' {
  import type { RouteObject } from 'react-router'
  import type { NavigateOptions, LinkProps } from 'react-router-dom'

  // Generated route paths
  export type RoutePaths = "/posts" | "/posts/:id";

  // Declare function generatePath
  type Split<T extends string> = T extends `${infer P}/`
    ? Split<P>
      : T extends `/${infer P}`
    ? Split<P>
      : T extends `${infer PL}/${infer PR}`
    ? Split<PL> | Split<PR>
      : T;

  type PathParamsKey<T extends string, K = Split<T>> = K extends `:${infer P}`
    ? P
      : K extends `...${infer P}`
    ? P
      : // eslint-disable-next-line @typescript-eslint/no-unused-vars
      K extends `${infer _P}*`
    ? "slug"
      : never;

  type PathParams<T extends string> = {
    [P in PathParamsKey<T>]: string | number;
  };

  export type GeneratePathOptions<
    T extends string,
    Params extends object = PathParams<T>
  > = {
    path: T;
  } & (keyof Params extends never ? { params?: Params } : { params: Params });

  export function generatePath<T extends string = RoutePaths>({
    path,
    params,
  }: GeneratePathOptions<T>): string;

  // Declare hook useNavigate
  export function useNavigate(): <T extends string = RoutePaths>(
    pathOptions: GeneratePathOptions<T>,
    options?: NavigateOptions
  ) => void

  // Declare component Link
  export function Link<T extends string = RoutePaths>(
    props: GeneratePathOptions<T> &
  Omit<LinkProps, "to">
  ): void

  // Export routes
  const routes: RouteObject[]
  export default routes
}

declare module 'virtual:generated-pages-react' {
  import type { RouteObject } from 'react-router'
  const routes: RouteObject[]
  export default routes
}
// Function generatePath

export default function generatePath<T extends string>({
  path,
  params,
}: GeneratePathOptions<T>): string {
  let result: string = path;

  if (params) {
    for (const key in params) {
      if (Object.prototype.hasOwnProperty.call(params, key)) {
        const value = params[key as keyof typeof params] as string;
        result = result.replace(`:${key}`, value);
      }
    }

    if ("slug" in params && result.endsWith("*")) {
      result = result.replace(/\*$/, params.slug as string);
    }
  }

  return result;
}

Then, we should exports generatePath, useNavigate and Link in virtual module, which currently only exports routes.

Alternative

No response

Additional context

No response

Validations

hannoeru commented 10 months ago

Out of scope and not planning to do.

Maybe someone can make a react version of unplugin-vue-router.