rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Auto Route - Automatically generated routes.

Open webnoob opened this issue 5 years ago • 34 comments

RFC (Request for Comments) Template

Please select your type of RFC (one or more):

Put an x in the boxes that apply

  • [x] Change ?
  • [ ] Method ?
  • [x] Behavior ?
  • [x] Research ?
  • [ ] Innovation ?

Other

  • Start Date: 2020-02-19
  • Target Major Version: 1 / 2
  • Reference Issues: https://github.com/quasarframework/quasar/issues/5378

Does this introduce a breaking change?

Put an x in the box that applies

  • [ ] Yes
  • [X] No

Proposed RFC

Generate route definitions automatically, by reading the folder structure of the pages directory with the option to override on a route level using meta files.

Requirements

Configurable as to whether it'll generate routes or not (default Off). quasar.conf.js

framework: {
  autoRoute: boolean | object
}

The following folder structure:

└── src/
│   └── pages/
│       ├── pages.vue
│       ├── dashboard/
│       │   └── dashboard.vue
│       ├── user.vue
│       ├── user/
│       │   ├── index.vue
│       │   ├── query.vue
│       │   └── __underConstruction.vue
│       ├── blog_category.vue 
│       ├── blog_category.meta.js 
│       ├── blog_category/
│       │   ├── index_[post-id].vue
│       │   └── index_[post-id].meta.js
│       ├── games!.vue
│       ├── games!.meta.js
│       ├── !.vue
│       └── !.meta.js
└── quasar.conf.js

Generates the following routes:

[
  {
    name: 'pages_index',
    path: '',
    component: () => import('pages/pages.vue')
  },
  {
    name: 'pages_dashboard_index',
    path: '/dashboard',
    component: () => import('pages/dashboard/dashboard.vue')
  },
  {
    name: 'user_index_parent',
    path: '/user',
    component: () => import('pages/user.vue'),
    children: [
      {
        name: 'user_index',
        path: '',
        component: () => import('pages/user/index.vue')
      },
      {
        name: 'user_query',
        path: 'query',
        component: () => import('pages/user/query.vue')
      },
      { /* NOTE: Route omitted from production build */
        name: 'user_underconstruction',
        path: 'underconstruction',
        component: () => import('pages/user/__undercontruction.vue')
      }
    ]
  },
  {
    name: 'blog_category_parent',
    path: '/blog/:category',
    component: () => import('pages/blog_category.vue'),
    children: [
      {
        name: 'blog_category_index',
        path: ':post-id?',
        component: () => import('pages/blog_category/index_[post-id].vue')
      }
    ]
  },
  {
    name: 'games_wildcard',
    path: '/games*',
    // Not lazy loaded as the meta file would say `lazyLoad: false`
    component: 'pages/games!.vue' 
  },
  {
    name: 'wildcard',
    path: '/*',
    component: () => import('pages/!.vue')
  }
]

Routing Conventions

  1. If a .vue file and a path below it match names (including brackets and underscores), the .vue file must be a router-view and the folder beneath it are its child pages. If in the children, there is an index.vue file, it is considered "pathless" and would be the first page to be shown when navigating to that route. If there is a .vue file which matches the parent folder, that'll be the default route otherwise (when no index vue file exists) and will also be considered pathless.

  2. _foo - an underscore in front of the word means the word is a parameter. So, in the example vue file baz_foo.vue, "foo" is a parameter for "baz". There can be multiple parameters such as baz_bar_foo.vue.

  3. [foo] - brackets around the word means it is optional. A path or a parameter can be optional. /[foo] would be an optional path. In baz_[bar].vue, "bar" would be an optional parameter for "baz".

  4. /foo! - an exclamation mark after a folder name means it is a wildcard path. So, a folder with the name /foo! will translate to /foo/*. In most cases, you'll also need to add a same named .meta.[js|ts] file below the wildcard path (folder) to add your specialized routing rules, including any reroutes to a 404 page, where necessary.

  5. A same-named .meta.[js|ts] file will be a container for any additional overriding, validation, guarding, etc. More details on this is below.

Example Meta File / Override route info

index_[post-id].meta.js

export default {
  lazyLoad: true, // Should this route be lazy loaded?
  route: {
    beforeEnter: (to, from, next) => {
      console.log('Im in a route guard!')
      next()
    }
  }
}

With the above meta file defined, the route for pages/blog_category/index_[post-id].vue will now look like:

{
  name: 'blog_category_index',
  path: ':post-id?',
  component: () => import('pages/blog_category/index_[post-id].vue'),
  beforeEnter: (to, from, next) => {
    console.log('Im in a route guard!')
    next()
  }
}

Everything under route: { } will be assumed as standard vue-router properties and will in turn be added directly to the route.

Additional properties will go on the same level as route: {}.

Meta File MeProperties

  • lazyLoad: boolean = true - Whether the route should be added as a lazy loading route or not.
  • parameters: ['param1', 'param2', 'param3'] - These parameters will be added to the route, for instance a file some-path.vue with this meta file property set would have a path generated like some-path/:param1/:param2/:param3. The idea for this is to avoid long file names when a route needs many params.
  • route: RouteConfig = {} - RouteConfig propertes which will be attached to the generated route i.e beforeEnter.

Layouts &

This is dealt with in point 1 of the Routing conventions above. It is up to the developer to chose what file they would like to use as a layout and also how that file is named. For instance, a layout file could be defined like so:

└── src/
│   └── pages/
│       ├── pagesLayout.vue
│       └── pagesLayout/
│           └── index.vue
└── quasar.conf.js

It would then be clear to the users this was a layout but because there is a folder matching the file name, it would know that there is a <router_view /> contained within that file and the files below the same-named folder are the router-view's child pages.

To index.vue or not to index.vue?

It would be up to the developer to decide if they would like to use index.vue as the route / pathless view of that route. Point 1 of the convention again gives us a way by allowing us to either use index.vue or a file with the same name as the parent folder. index.vue if specified will always take precedence over a same named file.

Dev only pages (__something.vue)

These will only be generated in the routes.[js|ts] file when running in dev mode e.g quasar dev.

Development Notes

We will refer to the UI Kit for code generation regarding fetching of files and routing (see: https://github.com/quasarframework/quasar-starter-kit-ui/blob/master/template/ui/dev/src/router/pages.js) however, we will need to make sure the call to require.context() uses the fourth param lazy so as to honor lazy loading components.

webnoob avatar Feb 19 '20 14:02 webnoob

The brief above is the original work put in by @lucasfernog

I'll need to review how best we want to implement this.

webnoob avatar Feb 19 '20 14:02 webnoob

Add a fourth parameter to the require.context() call: , 'lazy' otherwise goodbye lazy loading.

rstoenescu avatar Feb 19 '20 17:02 rstoenescu

For all the folks reading this RFC, we are interested in your opinion regarding the folder structure. Did we miss anything?

rstoenescu avatar Feb 19 '20 17:02 rstoenescu

Needs option to control.

framework: {
  autoRouting: true
}

Or perhaps:

framework: {
  autoRouting: {
    option1: ''
  }
}

The presence of the autoRouting option would make it enabled.

webnoob avatar Feb 20 '20 22:02 webnoob

Updated folder structure to include an option for required params.

webnoob avatar Feb 21 '20 01:02 webnoob

https://sapper.svelte.dev/docs#File_naming_rules

I think Sapper has a good file name rules

marcelo-gondim avatar Feb 21 '20 03:02 marcelo-gondim

Great, thanks @W3Web. I'll take a look.

webnoob avatar Feb 21 '20 09:02 webnoob

Added an option for ignored routes using double underscore.

webnoob avatar Feb 21 '20 09:02 webnoob

So it seems Sapper uses [someParam].ext for param based files. We could also nest params with destructuring, like so: /user/[...slug].vue Then we can do const [param1, param2] = this.$route.params.slug (or something like that.

I actually prefer this as it feels more natural - thoughts?

Something else we'll need to handle is error pages i.e we could have error{errocode}.vue so error404.vue.

webnoob avatar Feb 21 '20 09:02 webnoob

What about components where root element is dialog?

stgunholy avatar Feb 21 '20 18:02 stgunholy

What about components where root element is dialog?

I see that as being up to the component itself to handle. The routing could navigate you to that dialog but it'd be up to your coding to show it. It'd also have to be a child of another route.

webnoob avatar Feb 22 '20 10:02 webnoob

So my new line of thought re: routing and file naming convention.

Try and get the best of both worlds when it comes to the way others are doing this.

pages/
--| user/
------| index.vue
------| query.vue
------| _underConstruction.vue
--| index.vue
--| [optionalParam!].vue
--| [paramFolder]
------| [param].vue
--| blog
----- | index.vue
------| [category]
----------| index.vue
----------| post
--------------| [post_id].vue

Which would generate these routes:

const devOnlyBeforeEnter = (to, from next) => {
  // next(false) if we're in anything but dev mode.
  next(process.env.NODE_ENV === 'development')
}

const routes = [
  {
    name: 'index',
    path: '/',
    component: 'pages/index.vue'
  },
  {
    name: 'user',
    path: '/user',
    component: 'pages/user/index.vue',
    children: [
      {
        name: 'user-query',
        path: 'query',
        component: 'pages/user/query.vue'
      },
      {
        name: 'user',
        path: 'underConstruction',
        component: 'pages/user/_underConstruction.vue',
        beforeEnter: devOnlyBeforeEnter
      }
    ]
  },
  {
    name: 'index-param',
    path: '/:optionalParam?',
    component: 'pages/[optionalParam!].vue'
  },
  {
    name: 'paramFolder-param',
    path: '/:paramFolder/:param',
    component: 'pages/[paramFolder]/[param].vue'
  },
  {
    name: 'blog-index',
    path: '/blog/index',
    component: 'blog/index.vue',
    children: [
      {
        name: 'blog-category-index',
        path: 'category/index',
        component: 'blog/category/index.vue'
      },
      {
        name: 'blog-category-post-post_id',
        path: ':category/post/:post_id',
        component: 'blog/[category]/post/[post_id].vue'
      }
    ]
  }
]

Only thing I'm not 100% sure of at this point is if the [] will cause any issues on any file systems. I know they're Ok on Windows and pretty sure they're Ok on linux so assume they're Ok on Mac :)

Going forward we could allow some basic regex in the [ ] as well.

webnoob avatar Feb 22 '20 13:02 webnoob

layouts ? how to use then ?

marcelo-gondim avatar Feb 22 '20 18:02 marcelo-gondim

Personally I don't see a way to use the layouts with auto routes. If you're wanting that level of structure it should be with manual routes.

Thoughts?

p.s On second thought, a layout is nothing special. It's just another SFC. It's defined as a layout by the fact you end up putting a <q-layout> inside it. This means you'd just do this on your top level index.vue file. That being said, it would mean the above structure would need tweaking slightly so pages would end up being in the children of the index route.

webnoob avatar Feb 22 '20 20:02 webnoob

Some comments so far:

  1. Layouts are an important part of any app. The folder/file structure should allow integrating it easily. And not only for layouts, but for any SFC that contains a <router-view>.

  2. We should avoid naming SFCs with "index.vue" (except for root route, "/"). Imagine having a dozen "index.vue" files opened in the IDE.. which one do you actually want to edit? :) Not a great dev experience.

  3. There should be no "devOnlyBeforeEnter". Simply don't add it at all on production.

  4. Please update the require.context() call with a fourth param , 'lazy', otherwise this forces webpack to require all SFCs upfront. Which in turns means it won't matter if you specify the SFC to be lazy-loaded or not. Which brings us to the next point.

  5. We should have a way to configure if route is lazy-loaded or not. Maybe assume "lazy-load" by default and through point 7 tell when it shouldn't be lazy-loaded?

  6. The folder/file structure should allow converting empty paths (like below) too.

{
  path: '/',
  component: () => import('layouts/MainLayout.vue'),
  children: [
    {
      path: '', // <<<<<<<<<<<<<<
      component: () => import('pages/Index.vue') }
  ]
}
  1. I like the framework: { autoRouting: {} } approach in quasar.conf.js. We should have a way of hooking very custom logic for route creation through this Object. Something along the lines of "here on this path x/y/z, use this instead". Or through a _routeConfig.js file under folders to override what Quasar App CLI would create for those folders/files? Or maybe there's a better way? This is opened for discussion.

There are lots of cases to cover, however, we should try to keep everything as simple as possible (KISS) and as intuitive as possible.

rstoenescu avatar Feb 24 '20 09:02 rstoenescu

Hi All,

I've updated the main RFC post with a complete re-think based on @smolinari and I bashing it out in discord to try and cover all possible things that would be required.

Looking forward to the feedback.

webnoob avatar Mar 03 '20 16:03 webnoob

The most important is that this will be optional feature. None of these models can be easily adopted by any apps that we use quasar for.

stgunholy avatar Mar 03 '20 18:03 stgunholy

It will be configurable by a config option in quasar.conf.js. It seems I cleared that point in the update. Will add it back in.

webnoob avatar Mar 03 '20 18:03 webnoob

@stgunholy - If possible, could you you make a screenshot of the pages folder from one or two of your more complicated apps? If you want, you can DM me them on Discord?

Scott

smolinari avatar Mar 03 '20 18:03 smolinari

@stgunholy - If possible, could you you make a screenshot of the pages folder from one or two of your more complicated apps? If you want, you can DM me them on Discord?

Scott

Sent as PM at discord

stgunholy avatar Mar 03 '20 19:03 stgunholy

Hi all!

So my colleague @IlCallo asked me to share my opinion on this RFC since I've worked with Nuxt.js, and my thoughts are based on my experience with that framework . I'll try to be brief and only talk about things that, in my opinion, should be revised (btw, I love the Dev only pages feature).

Directory structure

My main concern here is that this type of structure can be a little bit confusing to the developer. An example: if the app has many pages, the page directory can get overcrowded very quickly, since each route can have a .vue component, its meta file and an associated folder.

I think it's better to have all logic regarding a single route inside its own folder. To me, this means that (using the RFC example):

  1. the blog_category.vue page component file should be contained inside the blog_category folder and be renamed to index_category.vue;
  2. also the blog_category-meta.vue should be moved inside the the blog_category folder;
  3. to specify a default child route component, the only option left is to use the .vue file which matches the parent folder as described above in the Routing Conventions 1.

Layout

I think it would be be nice to have a separate folder in which to store layouts, so that a single layout can be used in different route components. This would also enable the possibility to dynamically change the layout of a route given a particular state (maybe a logged in user, a user with different privileges, etc...).

.meta.[js|ts] vs -meta.js

A thing that I've not understood is the duality mentioned in the above title concerning the meta info for the route. I think that a user should be forced to use only one of the two ways (I'm assuming that the above syntax is used for the same purpose). If that's the case, I would drop the possibility to use the -meta.js version and force the usage of .meta.[js|ts].

SirAuron avatar Mar 11 '20 15:03 SirAuron

Hey!

Thanks very much for the feedback! We appreciate you taking the time for it.

Structure I agree with your points about the directory structure. One of the issues which was raised before moving down this road with mappings was that there would be a lot of index.vue files and hard for a developer to easily navigate to their chosen files using their IDE's file lookup but given you're adding the param to the index files i.e index_category.vue that'd get around that.

Layout Couldn't this be achieved using the meta file and set the component for that route? You could have a factory function which returns the correct component for layout based on whatever factors.

meta File Not sure I understand this. The [js|ts] just means it could be a someRoute-meta.js file or a someRoute-meta.ts file. Meaning it'll work as either Typescript or JS. Does that clear up your concern on that one?

webnoob avatar Mar 11 '20 15:03 webnoob

Talking about the last point: I think the problem is the .meta or -meta thing (notice the first char). It may also be a typo, is there a logical difference between the two?

IlCallo avatar Mar 11 '20 16:03 IlCallo

Ah, whoops. It should be someRoute.meta.js (or .ts). I changed it after adding so must have missed some examples. Will sort. Edit: Updated the RFC to show .meta.js now.

Just to clarify as well. We moved away from -meta because it would stop people adding a file like something-meta.js to that folder and we didn't want people to be limited. Having it as .meta.js seemed to be more fitting.

webnoob avatar Mar 11 '20 16:03 webnoob

Hi, about the last point, yeah that's exactly what I was thinking about and that change is perfect.

About the layout, I didn't though about that, but I think that yours could be a neat solution.

About the structure I see your point. A possible solution would be to move all the child views inside their own folder and let the main component, which should be rendered by default, keep its name instead of being renamed to index. But then you would have to deal with a lot of sub folders I guess...

SirAuron avatar Mar 11 '20 16:03 SirAuron

RE: Meta and Layout - Great!

But then you would have to deal with a lot of sub folders I guess...

I feel, when it comes to auto routing, that there is always going to be a dilemma like this because we're trying to automate something. When it comes to keeping its name, point 1 of the spec does allow for the default view having the same name as it's parent or using index.vue.

So it's about this point of nesting the children. One option could be to add an option to the meta file so that you can configure where the component(s) are: i.e { childDir: 'myChildFolder' } then if present the parser would look for the children in that folder tree. It would then be up to the dev to decide whether they want a long list of files or nested directories. I'm not sure.

With your experience in Nuxt, how do they avoid this kind of thing?

webnoob avatar Mar 11 '20 17:03 webnoob

Nuxt uses the same solution you proposed in the RFC: they use an example.vue file and then searches if there is a directory named example on the same directory level. If this directory exists, then the components inside are used as child components for the nested routes. But I do not like that system for the same motivations I've reported in this discussion.

The only difference is that they include the information you include in the .meta.[js|ts] directly inside the example.vue file in a specific object property which is later extracted and analyzed at compile time. Even thought this reduces the number of files you have to create, it does not give the developer a visual feedback on which components have custom routing properties or guards, so I prefer your system of using an external file to accomplish this task.

I think that another solution for the sub folder issue would be to just place all .vue components for nested routes directly inside a special folder such as (for example): [specialChildrenFolderPrefix][childrenFolderName] where [specialChildrenFolderPrefix] is a group of special chars used just for identifying the folder for nested routes components. In this way we have only a single folder for all child components and still use the other conventions inside the special folder.

What do you think about this?

SirAuron avatar Mar 11 '20 17:03 SirAuron

Right, I see.

We initially thought about adding the routing info into the vue file but didn't want to be parsing all the files just for the routing info but you make a good point, having the meta also allows easy visibility of routing data. Glad to see we went the right way with that.

RE: Children. EDIT: Removed. Misunderstood. Re-thinking :)

webnoob avatar Mar 11 '20 18:03 webnoob

Hi every one.

This idea is amazing to big projects to order and centralize these recurring actions. i'm working in a front side modular project using Quasar as base. beginning i was thinking the same, generate routes according to exiting files but bellow i was needed manage this actions manually for add or config more especific actions. So i was code a way to auto load not just Routes, too Stores (Vuex) and this it's working so well for my and my working team.

this is a example how i doing:

{ permission: '', //I'm using permissions to access activated: true, //Enable/disable page path: '/path/name', name: 'route.name', layout: () => import('src/layouts/master'), page: () => import('src/pages/index'), title: '<page.title>',//This is a title to final user, same i'm using to load as meta title icon: 'fas fa-chart-pie', authenticated: true,//Work with authention module, and require login to access breadcrumb: ['leads.leads'] //generate a breadcrum with pages names. }

With this method, I create and manage routes with more order and faster, I also allow this information in $ route.meta to be accessed when necessary.

What do you think about this?

msolanogithub avatar Mar 11 '20 20:03 msolanogithub

@michaelsoup Thanks for the thoughts. Whilst this might work for your team, I'm not sure of it's scalability for the needs of Quasar. That being said, it's not really on topic of this RFC - we are going down the file structure route, we're just trying to determine how the community would like us to do that.

webnoob avatar Mar 12 '20 10:03 webnoob