Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single Route File #341

Open
brillout opened this issue Jun 17, 2022 · 56 comments
Open

Single Route File #341

brillout opened this issue Jun 17, 2022 · 56 comments
Labels
enhancement ✨ New feature or request

Comments

@brillout
Copy link
Member

brillout commented Jun 17, 2022

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 brillout added the enhancement ✨ New feature or request label Jun 17, 2022
@brillout brillout changed the title Single File Route Single Route File Jul 28, 2022
@brillout

This comment was marked as outdated.

@redbar0n
Copy link
Contributor

redbar0n commented Sep 10, 2022

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
Copy link
Contributor

redbar0n commented Sep 10, 2022

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
Copy link
Member Author

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
Copy link
Contributor

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): microsoft/TypeScript#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
Copy link
Contributor

redbar0n commented Sep 16, 2022

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
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Contributor

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

@brillout
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Contributor

redbar0n commented Sep 27, 2022

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
Copy link
Member Author

brillout commented Oct 5, 2022

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 😅.

@brillout brillout mentioned this issue Oct 5, 2022
@redbar0n
Copy link
Contributor

redbar0n commented Oct 5, 2022

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
Copy link
Member Author

brillout commented Oct 7, 2022

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
Copy link
Contributor

@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
Copy link
Member Author

@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
Copy link
Contributor

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

@robinelvin
Copy link

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
Copy link
Member Author

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

@redbar0n
Copy link
Contributor

redbar0n commented Feb 1, 2023

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

@CanRau
Copy link
Contributor

CanRau commented Feb 2, 2023

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

@redbar0n
Copy link
Contributor

redbar0n commented Feb 3, 2023

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
Copy link
Member Author

brillout commented Feb 3, 2023

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
Copy link
Contributor

redbar0n commented Mar 8, 2023

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.

@redbar0n redbar0n mentioned this issue Mar 8, 2023
@brillout
Copy link
Member Author

brillout commented Mar 9, 2023

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?

@phiberber
Copy link

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
Copy link

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
Copy link
Member Author

brillout commented Jun 9, 2023

it's not that flexible

Do you mind elaborating?

@rossanmol
Copy link

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
Copy link
Member Author

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
Copy link

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
Copy link

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
Copy link
Member Author

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 brillout mentioned this issue Jun 22, 2023
@brillout
Copy link
Member Author

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
Copy link

snake-py commented Jul 5, 2023

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
Copy link
Member Author

brillout commented Jul 5, 2023

I see. Most of it is actually unrelated to having a Single Route File. I created a new ticket for i18n: #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
Copy link

snake-py commented Jul 5, 2023

@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
Copy link
Member Author

brillout commented Jul 6, 2023

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
Copy link

snake-py commented Jul 6, 2023

@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 😅

@brillout
Copy link
Member Author

brillout commented Jul 6, 2023

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
Copy link

snake-py commented Jul 6, 2023

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
Copy link
Member Author

brillout commented Jul 6, 2023

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
Copy link

doeixd commented Jul 7, 2023

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.

@brillout
Copy link
Member Author

brillout commented Jul 7, 2023

@doeixd You're right and that's the plan: https://vite-plugin-ssr.com/extends. (The rudimentary Single Route File implementation I mentioned earlier needed for the V1 design is actually precisely for enabling extensions to define pages.)

@redbar0n
Copy link
Contributor

redbar0n commented Aug 25, 2023

The ideal, imho, would be to have:

  • A single file for routes.
  • Glob import filesystem routes in the top of the single routes file based on a conventional file naming pattern (like suggested in V1 Design #578 (comment) ). So you don’t have to type out all the basics which can be derived from the file system by simple convention (less overhead / more DRY / convention over configuration).
  • Being able to override the imported filesystem routes for specific cases. Retains full power of configuration since done in JS (no need for fancy file name patterns / extensions in the file system). But being able to quickly get an overview of these overrides in a central place.
  • No need to navigate through the file system to find potentially overriding route config files for specific paths. Less config files gives less clutter and better overview.

Thumbs up or heart if you like or love this ideal.

Update: Another thing I thought of is that it would be very nice to have the Vike code for parsing the filesystem routes available in a file (not buried deep within some framework code). So you could easily override it if you wanted to parse the filesystem routes differently (if you wanted a different project folder structure for instance).

@brillout
Copy link
Member Author

brillout commented Dec 6, 2023

@redbar0n

Each + file can live in a different environment which was a primary motivation for the V1 design; some of your ideas aren't implementable without code extraction (wich I believe to be an anti-pattern).

As for increased flexibility around Filesystem Routing I agree that'd be nice, but it isn't a priority at the moment. That said, I'm happy to revisit prioritisation upon blockers and/or sponsoring.

@brillout
Copy link
Member Author

brillout commented Dec 6, 2023

Latest design for defining all routes in a single file: #341 (comment).

@redbar0n
Copy link
Contributor

some of your ideas aren't implementable without code extraction

I presume you mean «Code Extraction: The act of separating the code and creating server and client code bundles». But doesn’t Vike already do this to an extent?

If the problem with having all routes in a single file is that client side routing and server side routing would need to be extracted from that file, what about 2 files: a single route file for the client, and a single route file for the server? Could make it clearer. E.g. So if you want to SSR a particular page you’d move it to the single route file for the server.

@brillout
Copy link
Member Author

@redbar0n I'm afraid it isn't that simple. In general, design needs an holistic understanding of a lot of problems at once. I'd be happy to elaborate but that would require 2-3 pages of text. As you can imagine, prioritization is crucial for Vike to grow.

That said, it's paramount that users communicate their pain points and what they try to achieve (the goals), and I'm more than happy to hear (critical) feedback here. The resulting design (how to achieve the goals) is a work that only Vike maintainers can do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement ✨ New feature or request
Projects
None yet
Development

No branches or pull requests

10 participants