posva / unplugin-vue-router

Next Generation file based typed routing for Vue Router
https://uvr.esm.is
MIT License
1.69k stars 82 forks source link

HMR improvements #503

Open jods4 opened 2 months ago

jods4 commented 2 months ago

I would like to suggest a few improvements to HMR.

I observe updateRoutes is only invoked when adding or removing page.

In updatePage there's a comment:

// no need to manually trigger the update of vue-router/auto-routes because
// the change of the vue file will trigger HMR

That's not accurate. Vite will notice the module has changed and invalidate it, but it will not actively push it through HMR to clients. As auto-routes is typically not referenced by pages or components, only by the module that create routes, it might not be reloaded for a long time, if ever. It also looks that this "late update" is not considered HMR and just creates duplicate modules in the front-end, which led me to all sorts of confusing issues. I would suggest to trigger HMR when the routes are modified

Is this really the right place to do it though? Consider:

  1. extendRoutes and beforeWriteFiles can do a lot of shenanigans that may have different dependencies and are basically impossible to predict.
  2. Updates trigger basically on every page edit. That's often , and most of the time no route-impacting change is actually made.

For these reasons I would suggest a different approach altogether: after the auto-routes virtual file has been rebuilt, compute a hash. If the hash is different from previous generation, send HMR. This covers all possible changes (add/remove/update/extensibility changes) and never sends unnecessary HMR to front-end.

[!TIP] Some tips for people googling how to update more than the router itself: auto-route exports handleHotUpdate(router), which you can use to register your app router. With this, your router will automatically be updated whenever auto-routes is HMR. Very cool! If you want to update more than the router, for example if your navigation menu is built off of your route table... it's possible but not obvious. Because auto-routes self-accepts HMR, it won't propagate further: don't try to accept it with hot.accept("/__vue-router/auto-route"). The way to go is to subscribe to notifications with import.meta.hot.on('vite:afterUpdate', args =>...) and filter args.updates.some(u => u.acceptedPath === '/__vue-router/auto-routes') Note that you will not find the new module in args and the self-accepting HMR does not update the existing exports, only the router. So to get the updated routes, your only way is to get your router and call router.getRoutes().

@posva Maybe this process is worth simplifying? The obvious idea (to me) is a custom message hot.on('auto-routes:afterUpdate', routes => ..) but the bummer is that these messages can only be sent by server. Alternatively auto-routes could export a onRouteUpdated(routes => ..) function to register listeners that are called by accept?

posva commented 2 months ago

The process is definitely worth simplifying, maybe even fixed. Without the current implementation, there was no invalidation at all and things were even worse... The info you provided here is really helpful, thank you. I will definitely take a look