Skip to content

Commit

Permalink
feat: handle hmr for pages
Browse files Browse the repository at this point in the history
Fix #292

Close #404
  • Loading branch information
posva committed Jun 19, 2024
1 parent f5fc2ec commit 4925e7e
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 116 deletions.
8 changes: 7 additions & 1 deletion client.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
declare module 'vue-router/auto-routes' {
import type { RouteRecordRaw } from 'vue-router'
import type { RouteRecordRaw, Router } from 'vue-router'

/**
* Array of routes generated by unplugin-vue-router
*/
export const routes: RouteRecordRaw[]

/**
* Setups hot module replacement for routes.
* @param router - The router instance
*/
export function handleHotUpdate(router: Router): void
}

declare module 'vue-router' {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@
"yaml": "^2.4.5"
},
"peerDependencies": {
"vue-router": "^4.3.0"
"vue-router": "^4.4.0"
},
"peerDependenciesMeta": {
"vue-router": {
Expand Down Expand Up @@ -212,7 +212,7 @@
"vitepress": "1.2.3",
"vitest": "^1.6.0",
"vue": "^3.4.27",
"vue-router": "4.4.0-alpha.2",
"vue-router": "4.4.0-alpha.3",
"vue-router-mock": "^1.1.0",
"vue-tsc": "^2.0.21",
"vuefire": "^3.1.23",
Expand Down
4 changes: 2 additions & 2 deletions playground/index.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<a
href="/__inspect/#/module?id=virtual:vue-router/auto-routes&index=0"
href="/__inspect/#/module?id=/__vue-router/auto-routes&index=0"
target="_blank"
style="margin-top: 10px; display: block"
>visit /__inspect/ to inspect the intermediate state</a
Expand Down
2 changes: 1 addition & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
"mande": "^2.0.9",
"pinia": "^2.1.7",
"vue": "^3.4.27",
"vue-router": "4.4.0-alpha.2"
"vue-router": "4.4.0-alpha.3"
}
}
54 changes: 35 additions & 19 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,37 @@ import type {
import { ref } from 'vue'
import { routes } from 'vue-router/auto-routes'
function test(
a: RouteLocationResolved<'/[name]'>,
b: RouteLocationNormalizedLoaded<'/[name]'>,
c: RouteLocation<'/[name]'>
) {}
console.log(`We have ${routes.length} routes.`)
const router = useRouter()
const route = useRoute()
if (route.name === '/deep/nesting/works/[[files]]+') {
route.params.files
}
console.log(`We have ${routes.length} routes.`)
const targetRoute = ref('')
const router = useRouter()
function _test() {
function test(
a: RouteLocationResolved<'/[name]'>,
b: RouteLocationNormalizedLoaded<'/[name]'>,
c: RouteLocation<'/[name]'>
) {}
router.resolve('/:name')
router.resolve({ name: '/[name]', params: { name: 'hello' } }).params.name
if (route.name === '/deep/nesting/works/[[files]]+') {
route.params.files
}
useLink({ to: '/articles/2' }).route.value.name
useLink({ to: { path: '/articles/:id' } })
useLink({ to: { name: '/[name]', params: { name: 2 } } }).route.value.params
.name
// useLink({ name: '/[name]', params: { name: 2 } }).route.value.params.name
useLink({ to: ref({ name: '/[name]', params: { name: 2 } }) }).route.value.name
router.resolve('/:name')
router.resolve({ name: '/[name]', params: { name: 'hello' } }).params.name
const customRoute = useRoute('/deep/nesting/works/custom-path')
useLink({ to: '/articles/2' }).route.value.name
useLink({ to: { path: '/articles/:id' } })
useLink({ to: { name: '/[name]', params: { name: 2 } } }).route.value.params
.name
// useLink({ name: '/[name]', params: { name: 2 } }).route.value.params.name
useLink({ to: ref({ name: '/[name]', params: { name: 2 } }) }).route.value
.name
const customRoute = useRoute('/deep/nesting/works/custom-path')
}
</script>

<template>
Expand Down Expand Up @@ -93,8 +98,19 @@ const customRoute = useRoute('/deep/nesting/works/custom-path')
</RouterLink>
</nav>
</div>
<div>
<form @submit.prevent="router.push(targetRoute)">
<label>
Navigate to:
<input type="text" v-model="targetRoute" />
</label>
<button>Go</button>
</form>
</div>
</header>

<hr />

<RouterView />
<hr />
<RouterView name="named" />
Expand Down
7 changes: 6 additions & 1 deletion playground/src/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ definePage({
</template>

<route lang="json">
{ "name": "home" }
{
"name": "home",
"meta": {
"n": 5
}
}
</route>
43 changes: 2 additions & 41 deletions playground/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,14 @@
import { createRouter, createWebHistory } from 'vue-router'
import { routes } from 'vue-router/auto-routes'
import { routes, handleHotUpdate } from 'vue-router/auto-routes'
import type { RouteRecordInfo, ParamValue } from 'vue-router'

export const router = createRouter({
history: createWebHistory(),
routes,
})

// let removeRoutes: Array<() => void> = []
// for (const route of routes) {
// removeRoutes.push(router.addRoute(route))
// }

if (import.meta.hot) {
// How to trigger this? tried virtual: /@id/
const id = 'vue-router/auto-routes'
// import.meta.hot.accept('vue-router/auto-routes', (mod) => {
// console.log('✨ got new routes', mod)
// })
// import.meta.hot.accept(id, (mod) => {
// console.log('✨ got new routes', mod)
// })
// import.meta.hot.accept((mod) => {
// console.log('🔁 reloading routes from router...', mod)
// console.log(mod!.router.getRoutes())
// })

// NOTE: this approach doesn't make sense and doesn't work anyway: it seems to fetch an outdated version of the file
// import.meta.hot.on('vite:beforeUpdate', async (payload) => {
// console.log('🔁 vite:beforeUpdate', payload)
// const routesUpdate = payload.updates.find(
// (update) => update.path === 'virtual:vue-router/auto-routes'
// )
// if (routesUpdate) {
// console.log('🔁 reloading routes from router...')
// const { routes } = await import(
// '/@id/' + routesUpdate.path + `?t=${routesUpdate.timestamp}`
// )
// for (const removeRoute of removeRoutes) {
// console.log('Removing route...')
// removeRoute()
// }
// removeRoutes = []
// for (const route of routes) {
// removeRoutes.push(router.addRoute(route))
// }
// router.replace('')
// }
// })
handleHotUpdate(router)
}

// manual extension of route types
Expand Down
2 changes: 0 additions & 2 deletions playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ export default defineConfig({
// can add multiple routes folders
{
src: 'src/pages',
// can even add params
// path: '[lang]/',
},
{
src: 'src/docs',
Expand Down
22 changes: 11 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 36 additions & 22 deletions src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,42 +107,37 @@ export function createRoutesContext(options: ResolvedOptions) {

async function writeRouteInfoToNode(node: TreeNode, filePath: string) {
const content = await fs.readFile(filePath, 'utf8')
// TODO: cache the result of parsing the SFC so the transform can reuse the parsing
// TODO: cache the result of parsing the SFC (in the extractDefinePageAndName) so the transform can reuse the parsing
node.hasDefinePage ||= content.includes('definePage')
// TODO: track if it changed and to not always trigger HMR
const definedPageNameAndPath = extractDefinePageNameAndPath(
content,
filePath
)
// TODO: track if it changed and if generateRoutes should be called again
const routeBlock = getRouteBlock(filePath, content, options)
// TODO: should warn if hasDefinePage and customRouteBlock
// if (routeBlock) logger.log(routeBlock)
node.setCustomRouteBlock(filePath, {
...routeBlock,
...definedPageNameAndPath,
})

// TODO: if definePage changed
server?.invalidate(filePath + '?definePage&vue&lang.tsx')
server?.invalidate(asVirtualId(MODULE_ROUTES_PATH))

// TODO: only if needed
// server?.updateRoutes()
}

async function addPage(
{ filePath, routePath }: HandlerContext,
triggerExtendRoute = false
) {
logger.log(`added "${routePath}" for "${filePath}"`)
// TODO: handle top level named view HMR

const node = routeTree.insert(routePath, filePath)

await writeRouteInfoToNode(node, filePath)

if (triggerExtendRoute) {
await options.extendRoute?.(new EditableTreeNode(node))
}

// TODO: trigger HMR vue-router/auto
server?.updateRoutes()
}

async function updatePage({ filePath, routePath }: HandlerContext) {
Expand All @@ -154,11 +149,15 @@ export function createRoutesContext(options: ResolvedOptions) {
}
await writeRouteInfoToNode(node, filePath)
await options.extendRoute?.(new EditableTreeNode(node))
// no need to manually trigger the update of vue-router/auto-routes because
// the change of the vue file will trigger HMR
}

function removePage({ filePath, routePath }: HandlerContext) {
logger.log(`remove "${routePath}" for "${filePath}"`)
routeTree.removeChild(filePath)
// TODO: HMR vue-router/auto
server?.updateRoutes()
}

function setupWatcher(watcher: RoutesFolderWatcher) {
Expand All @@ -182,16 +181,37 @@ export function createRoutesContext(options: ResolvedOptions) {
// unlinkDir event
}

let lastAutoRoutes: string | undefined
function generateRoutes() {
const importsMap = new ImportsMap()

const routesExport = `export const routes = ${generateRouteRecord(
const routeList = `export const routes = ${generateRouteRecord(
routeTree,
options,
importsMap
)}`
// TODO: should we put some HMR code for routes here or should it be at the router creation level (that would be easier to replace the routes)
)}\n`

let hmr = `
export function handleHotUpdate(_router) {
if (import.meta.hot) {
import.meta.hot.data.router = _router
}
}
if (import.meta.hot) {
import.meta.hot.accept((mod) => {
const router = import.meta.hot.data.router
if (!router) {
console.error('❌ router not found')
return
}
router.clearRoutes()
for (const route of mod.routes) {
router.addRoute(route)
}
router.replace('')
})
}
`

// generate the list of imports
let imports = importsMap.toString()
Expand All @@ -200,13 +220,7 @@ export function createRoutesContext(options: ResolvedOptions) {
imports += '\n'
}

const newAutoRoutes = `${imports}${routesExport}\n`

if (lastAutoRoutes !== newAutoRoutes) {
// cache hit, triigger HMR (not working yet)
server?.updateRoutes()
lastAutoRoutes = newAutoRoutes
}
const newAutoRoutes = `${imports}${routeList}${hmr}\n`

// prepend it to the code
return newAutoRoutes
Expand Down
Loading

0 comments on commit 4925e7e

Please sign in to comment.