vikejs / vike

🔨 Flexible, lean, community-driven, dependable, fast Vite-based frontend framework.
https://vike.dev
MIT License
3.91k stars 334 forks source link

Single Route File #341

Open brillout opened 2 years ago

brillout commented 2 years ago

Description

Being able to define the entire routing of the app in a single file.

// vike.config.h.ts

import type { Config } from 'vike/types'
import LandingPage from './pages/LandingPage.js'
import ProductListPage from './pages/ProductListPage.js'
import ProductPage from './pages/ProductPage.js'

export default {
  pages: [
    '/': {
      Page: LandingPage
    },
    '/product/@id': {
      Page: ProductPage
    },
    '/products': {
      Page: ProductListPage
    },
    // ...
  ]
} satisfies Config

If you want this, feel free to write a comment down below explaining why. The more we know why users want this, the better we can prioritize accordingly.

brillout commented 1 year ago

Edit: Redacted outdated design.

redbar0n commented 1 year ago

One option could be to use a tagged template literal string. Which at first glance seems to have typescript support. It would also allow to eliminate the documentation/comments in the aforementioned example, since it would be more direct.

function Menu() {
  return <>
    <Link to={`/`}  />
    <Link to={`/about`} />
    <Link to={`/product/${42}`} />
    <Link to={`/products`} />
  </>
}
redbar0n commented 1 year ago

I see there’s some precedence to the API that I suggested: https://solito.dev/usage/link#wrapper-components

In all, Solito does some really nifty things with routing, which is very desirable to replicate on a non-NextJS stack. To enable universal apps cross platform.

The downside with Fernando’ stack (NextJS+Expo+Solito+Dripsy) is that it actually disables SSR on web to be able to work (due to issues with responsivity, that Tamagui solves).

So there’s a double opportunity there: universal apps on Vite and with SSR on web. I know the very competent and prolific author of Tamagui is looking into that, and has been eyeing vite-plugin-ssr since I gave word to him about it.

brillout commented 1 year ago

Open question: how to make it work with TypeScript? So that TypeScript complains upon <Link to="unknown-page" /> or <Link to="product-list" id={"42"} /> (because "42" is a string but should be a number).

Does Solito has such TypeScript support?

One option could be to use a tagged template literal string.

So far, I can't see how TypeScript's template literal strings would help.

One aspect of Single Route Files that is interesting is that the user can change the URL without having to modify each link. That's why I'm leaning towards <Link to="product" id={42} />. But I can see that to be a matter of taste and use case. Small apps don't need that.

redbar0n commented 1 year ago

Does Solito has such TypeScript support?

It doesn't seem like it. It simply replicates NextJS' API.

So far, I can't see how TypeScript's template literal strings would help.

On second glance, I think you're right. TypeScript Template Literal Types that I linked to are a different thing. What I had in mind - type checking inside tagged template literal strings - is still an open TypeScript issue (not very likely to get implemented, it seems): https://github.com/microsoft/TypeScript/issues/29432

change the URL without having to modify each link

I see. I think the general web native way of doing it - and what Rails did - is to simply provide a re-route to the new URL. That way, outdated links in ones app would work the same as the outdated links to one's page that had already been shared on the web: they would be re-routed to the new URL.

redbar0n commented 1 year ago

Open question: how to make it work with TypeScript? So that TypeScript complains upon or <Link to="product-list" id={"42"} /> (because "42" is a string but should be a number).

Did some more research into this, since you mentioned it is a blocker.

In addition to pathpida, check out next-type-safe-routes or nextjs-routes or next-typed-routes for inspiration.

The API of next-type-safe-routes seems the nicest (clearest, most straightforward/intuitive, least boilerplate). The automatic route listing (route autocomplete) is quite nice.

next-type-safe-routes parses the /pages folder in your Next.js app and generates types for all the pages and API routes in the application. These types can then be used to ensure that you only link to pages (and only use API routes) that actually exists in your application.

due to the strictness, we can also determine which parameters are needed for dynamic routes.

So, the only issue with using either of those directly is that they all presume filesystem routing (being for NextJS).

Maybe typesafe-routes is what you're looking for?

Actually, it turns out you can use TypeScript Template Literal Types to get type checking inside tagged template literal strings, like I had in mind. This library does it, see the GIF animation in that repo.

As long as it is possible to wrap the <Link> component that vite-plugin-ssr provides, so that a Solito.dev like library could be developed on top, for universal/crossplatform-app support. With the goal of having vite-plugin-ssr (Vite) instead of NextJS (Webpack) as the base for such crossplatform apps.

brillout commented 1 year ago

Neat. Indeed, infer with template literal types is a solution.

We can even make the route types defined by the user in route.ts available globally by using declare module (e.g. https://telefunc.com/typescript#getcontext).

I think we can make it work.

Although Route Functions routes cannot be typed, but that's expected.

As long as it is possible to wrap the component that vite-plugin-ssr provides

Yes, that's the idea. So far, I don't see a problem with that.

redbar0n commented 1 year ago

Came across this. Could be potential inspiration for typesafe routes.

solito’s useParam() hook takes in a parse() function to let you turn the string → number. i’ve found it to be nice for dealing with numbers and validating enums it comes with type safety + inference too: https://solito.dev/usage/params#parsing-values from Fernando's tweet

redbar0n commented 1 year ago

react-router-url-params is another one, for typesafe query parameters. It was made this summer.

brillout commented 1 year ago

My idea is that TypeScript for query params just works if we treat param serialization as implementation detail defined by the user.

// `order` is typesafe
<Link to="product-search" order={"desc"} />
// /routes.js

export default {
  routes: [
    {
      Page: '/pages/product-search.page.js',
      toUrl: ({ order }: { order: 'asc' | 'desc' }) => `/product/search?order={order}`,
      route: '/product/search'
    },
  ]
}
patryk-smc commented 1 year ago

I'm a big fan of Tanstack React Location and upcoming Tanstack Router. I think it's API is quite well thought. See this single route file example from Tanstack Router: https://github.com/TanStack/router/blob/572318b1b132307be02857b97ae05b2040363ccf/examples/kitchen-sink/src/index.tsx#L91

I really like how React components can be used instead of file path as proposed in this issue. Also, pending state feature sounds incredible.

redbar0n commented 1 year ago

The API of React Location / TanStack Router looks a lot like the API of (Remix and) React Router v6 : https://gist.github.com/redbar0n/f6ec12264ff9d58e243cc516e4e3f41b?permalink_comment_id=4312122#gistcomment-4312122

TanStack Router has the same issue as React Router mentioned there:

the component tree custody is duplicated: once in React and once in React Router... Because React Router directly drives React.

brillout commented 1 year ago

The overall design is starting to crystalize (it also includes better support for nested layouts).

One open question: https://stackoverflow.com/questions/73957822/get-import-path-of-dynamic-import-as-typescript-string-type, in case someone knows this. I don't think it's possible, but who knows with TypeScript 😅.

redbar0n commented 1 year ago

blitz-js has another way to achieve typesafe routing which looks quite neat:

https://twitter.com/aleksandrasays/status/1559157742559825920?s=20&t=uaV2ruG_UcOlWxDwGhk9yQ

brillout commented 1 year ago

blitz-js has another way to achieve typesafe routing which looks quite neat:

https://twitter.com/aleksandrasays/status/1559157742559825920?s=20&t=uaV2ruG_UcOlWxDwGhk9yQ

👍 Neat idea to make the link target a TS object, so you can definition-jump directly to the route file. Neat.

redbar0n commented 1 year ago

@patryk-smc @brillout here's an idea for a simplified API which presents the routes in a flat manner so you can more easily visually pattern-match to the URL in the browser (compared to the nesting in React Router and TanStack Router):

https://gist.github.com/redbar0n/f6ec12264ff9d58e243cc516e4e3f41b?permalink_comment_id=4343793#gistcomment-4343793

brillout commented 1 year ago

@redbar0n Yes I'm also not too fond of their verbosity. While I share the sentiment to keep things succinct, I'm thinking multi-dimensional arrays are a bit too extreme, i.e. not explicit enough. Let's see if the solution I come up with is succinct enough (so far I think so).

redbar0n commented 1 year ago

Ok, excited to see how it will look. I wonder how much explicitness is needed if we can have some Convention over Configuration.

robinelvin commented 1 year ago

Coming from React Router I use their Javascript Route Definitions which allows me to dynamically create and pre-filter routes, and render navigation from the definitions.

brillout commented 1 year ago

For folks who need/want Single Route File, please PM on discord.

redbar0n commented 1 year ago

FFR, here is a thread with people who don't like file based routing: https://twitter.com/t3dotgg/status/1583285667768827904

CanRau commented 1 year ago

I think SolidStart is taking a great approach with the dynamic <FileRoutes /> component https://start.solidjs.com/core-concepts/routing

redbar0n commented 1 year ago

I think SolidStart is taking a great approach with the dynamic <FileRoutes /> component https://start.solidjs.com/core-concepts/routing

Yeah, I agree, with one exception. To quote the SolidStart approach:

Under the hood, SolidStart traverses your routes directory, collects all the routes, and makes them accessible using the component. The component only includes your UI routes, and not your API routes. You can use it instead of manually entering all your Routes inside the component in root.tsx. Let the compiler do the boring work!

I think it makes more sense to expose the file routes through some other means than as a UI component. Maybe as a function (e.g. import getFileSystemRoutes() like the getRoute that next-type-safe-routes does).

You then use it to import the default routes derived from the file system, which you are then able to overwrite in the routes file. Or you could choose to not import it and rather manually write out all the routes if you'd like. I think it could be the best of both worlds: convenience of filesystem routing (aka. file based routes) + the power and customizability of a single route file.

Avoiding having it as a UI component would also allow vps to remain render library agnostic.

brillout commented 1 year ago

I'm actually thinking of increasingly focusing on single route files, and I'm increasingly thinking that it's superior than Next/Solid/Remix's approach. That said, so far, I think still supporting basic FS routing makes sense (like what VPS already does today), but I'll likely won't focus much on it.

redbar0n commented 1 year ago

TanStack Router vs. React Router DOM vs. Next.js comparison page is out, as per demand. It has a good overview of potentially desirable features for a routing solution.

brillout commented 1 year ago

TanStack Router vs. React Router DOM vs. Next.js comparison page is out, as per demand. It has a good overview of potentially desirable features for a routing solution.

Other than typesafe links (which is quite lovely), is there something you'd miss if using VPS's built-in router?

redbar0n commented 1 year ago

Not that I can think of (the search params API seems neat, but not sure if it's in scope).

The ability to easily swap out the router and use any router would be nice. Even if it required a one-time mapping from the vps router to that router (say, for SSR).

brillout commented 1 year ago

easily swap out the router

In principle, it should be possible to deeply integrate any router using the onBeforeRoute() hook.

But, tbh and AFAICT, I see VPS's router to be superior for its intended purpose than what I'm seeing so far.

The only thing missing is typesafe links, which require either code gen or opting-out of Filesystem Routing (i.e. what this ticket is about). (AFAICT TypeScript types can't be deduced from the mere presence of a file, so it's either or.) I think it's possible to do code gen in a seamless manner without the user even noticing.

brillout commented 1 year ago

Ticket for typesafe links: https://github.com/brillout/vite-plugin-ssr/issues/698.

brillout commented 1 year ago

Now that the V1 design is out, this ticket should be fairly easy to implement.

To increase the priority of this ticket, add a comment here with the reasons why you want this.

phiberber commented 1 year ago

I've had a small discussion with Brillout about this in the Discord. Type-safe links for are not that important due to the fact that my projects aren't always that big.

One thing other than the SFC in SvelteKit that made me stop using it was the +X files. I'm an openly hater of that approach. Often you'll see that meta-frameworks have different routing systems, Solid Start was the only one I've seen yet that has a way to "opt-out" of that routing system, as have been previously mentioned.

I'm not the only one who hates that File Routing System and as you know, it's impossible to please everyone. So here I'm, I love the idea of having route-based config files, and I'd love to have a way to define routes on one file without being compromised by not using the built-in routing.

brillout commented 1 year ago

@phiberber Thanks for sharing your feelings. Could you explain why you feel the way you do? I think we already thoroughly talked about the + thing arleady, but I'm interested in your rationale regarding Single Route File.

phiberber commented 1 year ago

My approach to the Single Route File would be to use it into my framework, I'd like to have a File Routing System but I like it in my own style, that's why I would probably in a Single Route File declared in the renderer folder, use import.meta.glob or something like Chokidar.

I like the idea of +config being the way to define routes, as I've seen in the documentation, you already can define the Page and if I remember well even onBeforeRender on the +config file, I'd think making it an object of paths would be an optimal way to make it.

// +config.js
export default {
  routes: {
    "/:id": {
     Page: UserPage,
     onBeforeRender: onBeforeRenderUser,

     "/activities": {
       Page: UserActivitiesPage,
       onBeforeRender: onBeforeRenderUserActivities
     }

     // ...etc
    }
  }
}

I'm not that sure about the naming of the property "routes" as I don't know how it would be to separate API Routes and UI Routes.

YannBirba commented 1 year ago

Hello,

I would be interested to get a single file routes definition to increase maintenability and also getting better git diff when updating routes. I think fsr Is a bad idea as I said for git diff but also it forces the ecosystem to use specific file convention and it's not that flexible.

It's also more user dev friendly to have a file to defined all routes, dev can quickly read it and understand what is going on at a specific url. I think vps is a plug and play ssr plugin for vite sooo imo it need to be as flexible as he can be.

I just want to be able to define my urls and get it strongly typed when I'm using a link. I also think child routes are not that good for readability I prefer to have a full relative url on each route.

Hope it can be helpful, sry for bad english. Also hope it's understandable.

brillout commented 1 year ago

it's not that flexible

Do you mind elaborating?

rossanmol commented 1 year ago

A good example would be, where routes are managed externally from the code, in applications with multi project setup. Having the flexibility to define route pages without being forced to use file based routing, would be a great feature that Vite-Plugin-SSR could implement.

brillout commented 1 year ago

Vite-Plugin-SSR could implement

Definitely and especially now with the V1 design header files.

where routes are managed externally from the code, in applications with multi project setup.

Curious: what's your motivation for organizing your code like that?

The more I know about use cases, the better.

rossanmol commented 1 year ago

Vite-Plugin-SSR could implement

Definitely and especially now with the V1 design header files.

where routes are managed externally from the code, in applications with multi project setup.

Curious: what's your motivation for organizing your code like that?

The more I know about use cases, the better.

It could be useful in projects where same codebase is used for multiple projects, and is managed externally via some kind of CMS. Pages, entire features are configurable without requiring a release.

rossanmol commented 1 year ago

Vite-Plugin-SSR could implement

Definitely and especially now with the V1 design header files.

where routes are managed externally from the code, in applications with multi project setup.

Curious: what's your motivation for organizing your code like that?

The more I know about use cases, the better.

Do you know of any short term solution which could allow me to configure routes without file based routing?

brillout commented 1 year ago

same codebase is used for multiple projects

Makes sense. What is it you're building? Let's PM. Let's also talk about sponsoring, which can make sense for your company as you'll be using a unique added value of VPS.

It's actually already implemented by config.extends. But you can't currently provide pages (i.e. routes). This ticket is precisely about enabling defining pages/routes in +config.h.js and therefore providing pages with extends.

For others reading this: if your company is up for sponsoring, definitely PM me. I prioritize features by the needs of sponsors.

brillout commented 1 year ago

I'm currently not considering implementing Single Route File for the https://github.com/brillout/vite-plugin-ssr/labels/v1%20release%20%3Astar2%3A.

AFAICT it's about two things:

  1. Being able to quickly glance over the entire routing of the app.
  2. Being able to define pages by external npm packages.

Are there other use cases? If so, let me know.

The thing is that I've a solution in mind that doesn't require Single Route File for both 1. and 2. and that's why, so far, I'm inclined to not implement this. But I'm happy to be convinced otherwise: let me know about your use case.

The alternative solution for 1. is a command $ vite-plugin-ssr routes that prints all routes. This command will automatically be run at the end of $ vite build.

snake-py commented 1 year ago

For me this would be an awesome feature for localization.

I could then dynamically change the routing based on the localization of the user.

For instance:

in english this would be

/home
/customer-data

in german

/home
/kunden-daten

It would make my life much easier to define this in a single file instead of in multiple files.

Of course within my app then I would need a helper, which resolves the routes by routes name.

So basically, the creation of a route would mean I define an unchangable name and a dynamic string which is what will be displayed in the url. The API I am picturing would be:

// in some route file
import route from 'vite-plugin-ssr';

type add = { (uri: string, name: string, component: ReactNode|FC<any>) => void }

route.add('/home', 'home', component)

// Not sure if the next to would also need consideration
route.add('/contract/{route-param}', 'contract-details', component)
route.add('/contract?query-param', 'contract-search', component)
// to refrence a route I would then need to call a resolve function with the name

<Link to={routeResolve('home')} />

// alternative VPS provides a link wrapper which does this under the hood for me
<Link to='home' />

Edit:

if there is already a plan to deliver this and just the manpower to do it is missing I would be willing to look into the matter. Is there a way to help or contribute?

brillout commented 1 year ago

I see. Most of it is actually unrelated to having a Single Route File. I created a new ticket for i18n: https://github.com/brillout/vite-plugin-ssr/issues/1000. FYI I'll be working on a couple of higher priority https://github.com/brillout/vite-plugin-ssr/labels/v1%20release%20%3Astar2%3A tickets before tackling i18n. I consider it a blocker for the v1 release, so you can expect enhancements around i18n fairly soon.

I suggest we first implement i18n then let's see if the need for a Single Route File is still there.

snake-py commented 1 year ago

@brillout my statement holds true though. I really would prefer to define my routes in a single file instead of having filesystem routing. If I can help to implement this let me know where I could get started.

brillout commented 1 year ago

A Single Route File also has its drawbacks. It isn't a perfect solution. I think I understand the appeal of it and I still have mixed feelings about it.

I'm thinking at this point the best is to first try to solve all issues without Single Route File and then later reconsider whether we want to add support for it.

That said, feel free to elaborate and why exactly you'd prefer a Single Route File over Filesystem Routing. (Your previous comment was more related to #1000. FYI I've a design in mind about i18n which I'll share if I'm still happy about it in a couple of days.)

snake-py commented 1 year ago

@brillout For me it just seems so much simpler to use a single file for defining routes. But I am comming from a server perspective here. Most frameworks I worked with offered me a router object onto which I can push routes. How I then generate the routes is entirely up to me. So for me the main benefit is that I can control route generation on run time.

File System routing is fine simple frontend apps which have a couple of slugs etc. But defining common middlewares, route groups, prefixes etc. seems just more simple for me in a single file. There I can have a typed object (API) and don't need to learn what kind of special file with a special hook I need to export.

I could have a single API, which in my opinion is much easier to understand. (But maybe this is just me. I have a hard time to do simple things with the file based routing right now.)

I'm thinking at this point the best is to first try to solve all issues without Single Route File and then later reconsider whether we want to add support for it.

May I ask why to address multiple issues with different solution when you could have one solution for all of them? If we have a single route file we can write our own solution around it. You don't need to solve all our problems :sweat_smile:

brillout commented 1 year ago

That makes a lot of sense.

I could have a single API

That's quite appealing indeed.

Actually, the V1 design needs a rudimentary implementation of a Single Route File. I'll see if I can get something useable for users.

snake-py commented 1 year ago

I'll see if I can get something useable for users

@brillout if it helps you can also point me into a direction where and maybe "how" I could get started on, since I am need of the feature I would be willing to help out to maybe build a first implementation of this. I guess you have a lot of work to do with the V1 Design. However, I also know that first contributors can often cost more time than they bring to the table.

So if contribution are welcome (and possible) I could have a look in coming up with an implementation if you have ideas for architecture maybe we can discuss them closer per pm (discord)? But I can also see that as maintainer you want to implemt this kind of core feature yourself.

brillout commented 1 year ago

I definitely appreciate the intention but, yeah, this one is quite a subtle one so I think it's best I implement it. (There are other tickets that are well suited for getting started with contributing: https://github.com/brillout/vite-plugin-ssr/labels/contribution-welcome%20%3Atwo_hearts%3A — in case any of it is appealing to you.)

doeixd commented 1 year ago

This would also be useful for any plugin that wants to build an abstraction above Vike/VPS . The Single Route File could be generated by them yet still be transparently visible/editable by the user. Be it an abstraction that provides the boilerplate for an API layer, internationalized routes, etc.